All of lore.kernel.org
 help / color / mirror / Atom feed
* [Nouveau] [PATCH drm-misc-next v8 00/12] [RFC] DRM GPUVM features
@ 2023-11-01 23:30 ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Currently GPUVM offers common infrastructure to track GPU VA allocations
and mappings, generically connect GPU VA mappings to their backing
buffers and perform more complex mapping operations on the GPU VA space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make GPUVM represent the
basis of a VM implementation. In this context, this patch series aims at
generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

The implementation introduces struct drm_gpuvm_bo, which serves as abstraction
combining a struct drm_gpuvm and struct drm_gem_object, similar to what
amdgpu does with struct amdgpu_bo_vm. While this adds a bit of complexity it
improves the efficiency of tracking external and evicted GEM objects.

This patch series is also available at [3].

[1] https://gitlab.freedesktop.org/nouvelles/kernel/-/commits/gpuvm-next

Changes in V2:
==============
  - rename 'drm_gpuva_manager' -> 'drm_gpuvm' which generally leads to more
    consistent naming
  - properly separate commits (introduce common dma-resv, drm_gpuvm_bo
    abstraction, etc.)
  - remove maple tree for tracking external objects, use a list drm_gpuvm_bos
    per drm_gpuvm instead
  - rework dma-resv locking helpers (Thomas)
  - add a locking helper for a given range of the VA space (Christian)
  - make the GPUVA manager buildable as module, rather than drm_exec
    builtin (Christian)

Changes in V3:
==============
  - rename missing function and files (Boris)
  - warn if vm_obj->obj != obj in drm_gpuva_link() (Boris)
  - don't expose drm_gpuvm_bo_destroy() (Boris)
  - unlink VM_BO from GEM in drm_gpuvm_bo_destroy() rather than
    drm_gpuva_unlink() and link within drm_gpuvm_bo_obtain() to keep
    drm_gpuvm_bo instances unique
  - add internal locking to external and evicted object lists to support drivers
    updating the VA space from within the fence signalling critical path (Boris)
  - unlink external objects and evicted objects from the GPUVM's list in
    drm_gpuvm_bo_destroy()
  - add more documentation and fix some kernel doc issues

Changes in V4:
==============
  - add a drm_gpuvm_resv() helper (Boris)
  - add a drm_gpuvm::<list_name>::local_list field (Boris)
  - remove drm_gpuvm_bo_get_unless_zero() helper (Boris)
  - fix missing NULL assignment in get_next_vm_bo_from_list() (Boris)
  - keep a drm_gem_object reference on potential vm_bo destroy (alternatively we
    could free the vm_bo and drop the vm_bo's drm_gem_object reference through
    async work)
  - introduce DRM_GPUVM_RESV_PROTECTED flag to indicate external locking through
    the corresponding dma-resv locks to optimize for drivers already holding
    them when needed; add the corresponding lock_assert_held() calls (Thomas)
  - make drm_gpuvm_bo_evict() per vm_bo and add a drm_gpuvm_bo_gem_evict()
    helper (Thomas)
  - pass a drm_gpuvm_bo in drm_gpuvm_ops::vm_bo_validate() (Thomas)
  - documentation fixes

Changes in V5:
==============
  - use a root drm_gem_object provided by the driver as a base for the VM's
    common dma-resv (Christian)
  - provide a helper to allocate a "dummy" root GEM object in case a driver
    specific root GEM object isn't available
  - add a dedicated patch for nouveau to make use of the GPUVM's shared dma-resv
  - improve documentation (Boris)
  - the following patches are removed from the series, since they already landed
    in drm-misc-next
    - f72c2db47080 ("drm/gpuvm: rename struct drm_gpuva_manager to struct drm_gpuvm")
    - fe7acaa727e1 ("drm/gpuvm: allow building as module")
    - 78f54469b871 ("drm/nouveau: uvmm: rename 'umgr' to 'base'")

Changes in V6:
==============
  - add drm_gpuvm_bo::evicted field protected by the drm_gem_object's dma-resv
    lock (Thomas)
    - additionally to the original proposal, always use drm_gpuvm_bo::evicted
      regardless of the used locking scheme and always keep it up to date
  - remove unneccesary get->put dance in drm_gpuva_unlink() (Thomas)
  - fix commit message wording (Thomas)
  - fix kernel doc warnings (kernel test robot)

Changes in V7:
==============
  - add a patch converting WARN() macros to drm_WARN() variants
  - allow drivers to pass the number of fences to reserve and the drm_exec flags
    through struct drm_gpuvm_exec
  - rename 'root' GEM object to 'resv' GEM object
  - fix order of private_usage and extobj_usage in drm_gpuvm_resv_add_fence()
  - always set drm_gpuvm_bo::evicted accordingly
  - explicitly clear drm_gpuvm_bo from evict list after successful validation
  - group reference get() calls with pointer assignments
  - call drm_gem_object_put() after vm_bo_free() callback
  - make lockdep checks explicit for drm_gpuvm_bo_* functions
  - improve documentation of struct drm_gpuvm_bo
  - fix a few documentation typos and style issues
  - use BIT() instead of shift ops for enum drm_gpuvm_flags

Changes in V8:
==============
  - do not always WARN on drm_gpuvm_check_overflow() to avoid userspace being
    able to spam the system log (Christian)
  - fix checkpath warning ENOTSUPP -> EOPNOTSUPP (Thomas)
  - fix another checkpatch error (assignment in if condition) (Thomas)
  - fix missing doc comment regarding a drm_gpuvm_bo's gpuva list (Thomas)
  - reference count struct drm_gpuvm, this avoids having (odd) rules when
    dealing with a drm_gpuvm_bo's gpuvm pointer

Danilo Krummrich (12):
  drm/gpuvm: convert WARN() to drm_WARN() variants
  drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
  drm/gpuvm: export drm_gpuvm_range_valid()
  drm/nouveau: make use of drm_gpuvm_range_valid()
  drm/gpuvm: add common dma-resv per struct drm_gpuvm
  drm/nouveau: make use of the GPUVM's shared dma-resv
  drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
  drm/nouveau: separately allocate struct nouveau_uvmm
  drm/gpuvm: reference count drm_gpuvm structures
  drm/gpuvm: add an abstraction for a VM / BO combination
  drm/gpuvm: track/lock/validate external/evicted objects
  drm/nouveau: use GPUVM common infrastructure

 drivers/gpu/drm/drm_gpuvm.c             | 1115 +++++++++++++++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.c    |   15 +-
 drivers/gpu/drm/nouveau/nouveau_bo.h    |    5 +
 drivers/gpu/drm/nouveau/nouveau_drm.c   |    5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  |   57 +-
 drivers/gpu/drm/nouveau/nouveau_exec.h  |    4 -
 drivers/gpu/drm/nouveau/nouveau_gem.c   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_sched.c |    9 +-
 drivers/gpu/drm/nouveau/nouveau_sched.h |    7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  |  269 +++---
 drivers/gpu/drm/nouveau/nouveau_uvmm.h  |    8 -
 include/drm/drm_gem.h                   |   32 +-
 include/drm/drm_gpuvm.h                 |  521 ++++++++++-
 14 files changed, 1796 insertions(+), 271 deletions(-)


base-commit: 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
-- 
2.41.0


^ permalink raw reply	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 00/12] [RFC] DRM GPUVM features
@ 2023-11-01 23:30 ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Currently GPUVM offers common infrastructure to track GPU VA allocations
and mappings, generically connect GPU VA mappings to their backing
buffers and perform more complex mapping operations on the GPU VA space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make GPUVM represent the
basis of a VM implementation. In this context, this patch series aims at
generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

The implementation introduces struct drm_gpuvm_bo, which serves as abstraction
combining a struct drm_gpuvm and struct drm_gem_object, similar to what
amdgpu does with struct amdgpu_bo_vm. While this adds a bit of complexity it
improves the efficiency of tracking external and evicted GEM objects.

This patch series is also available at [3].

[1] https://gitlab.freedesktop.org/nouvelles/kernel/-/commits/gpuvm-next

Changes in V2:
==============
  - rename 'drm_gpuva_manager' -> 'drm_gpuvm' which generally leads to more
    consistent naming
  - properly separate commits (introduce common dma-resv, drm_gpuvm_bo
    abstraction, etc.)
  - remove maple tree for tracking external objects, use a list drm_gpuvm_bos
    per drm_gpuvm instead
  - rework dma-resv locking helpers (Thomas)
  - add a locking helper for a given range of the VA space (Christian)
  - make the GPUVA manager buildable as module, rather than drm_exec
    builtin (Christian)

Changes in V3:
==============
  - rename missing function and files (Boris)
  - warn if vm_obj->obj != obj in drm_gpuva_link() (Boris)
  - don't expose drm_gpuvm_bo_destroy() (Boris)
  - unlink VM_BO from GEM in drm_gpuvm_bo_destroy() rather than
    drm_gpuva_unlink() and link within drm_gpuvm_bo_obtain() to keep
    drm_gpuvm_bo instances unique
  - add internal locking to external and evicted object lists to support drivers
    updating the VA space from within the fence signalling critical path (Boris)
  - unlink external objects and evicted objects from the GPUVM's list in
    drm_gpuvm_bo_destroy()
  - add more documentation and fix some kernel doc issues

Changes in V4:
==============
  - add a drm_gpuvm_resv() helper (Boris)
  - add a drm_gpuvm::<list_name>::local_list field (Boris)
  - remove drm_gpuvm_bo_get_unless_zero() helper (Boris)
  - fix missing NULL assignment in get_next_vm_bo_from_list() (Boris)
  - keep a drm_gem_object reference on potential vm_bo destroy (alternatively we
    could free the vm_bo and drop the vm_bo's drm_gem_object reference through
    async work)
  - introduce DRM_GPUVM_RESV_PROTECTED flag to indicate external locking through
    the corresponding dma-resv locks to optimize for drivers already holding
    them when needed; add the corresponding lock_assert_held() calls (Thomas)
  - make drm_gpuvm_bo_evict() per vm_bo and add a drm_gpuvm_bo_gem_evict()
    helper (Thomas)
  - pass a drm_gpuvm_bo in drm_gpuvm_ops::vm_bo_validate() (Thomas)
  - documentation fixes

Changes in V5:
==============
  - use a root drm_gem_object provided by the driver as a base for the VM's
    common dma-resv (Christian)
  - provide a helper to allocate a "dummy" root GEM object in case a driver
    specific root GEM object isn't available
  - add a dedicated patch for nouveau to make use of the GPUVM's shared dma-resv
  - improve documentation (Boris)
  - the following patches are removed from the series, since they already landed
    in drm-misc-next
    - f72c2db47080 ("drm/gpuvm: rename struct drm_gpuva_manager to struct drm_gpuvm")
    - fe7acaa727e1 ("drm/gpuvm: allow building as module")
    - 78f54469b871 ("drm/nouveau: uvmm: rename 'umgr' to 'base'")

Changes in V6:
==============
  - add drm_gpuvm_bo::evicted field protected by the drm_gem_object's dma-resv
    lock (Thomas)
    - additionally to the original proposal, always use drm_gpuvm_bo::evicted
      regardless of the used locking scheme and always keep it up to date
  - remove unneccesary get->put dance in drm_gpuva_unlink() (Thomas)
  - fix commit message wording (Thomas)
  - fix kernel doc warnings (kernel test robot)

Changes in V7:
==============
  - add a patch converting WARN() macros to drm_WARN() variants
  - allow drivers to pass the number of fences to reserve and the drm_exec flags
    through struct drm_gpuvm_exec
  - rename 'root' GEM object to 'resv' GEM object
  - fix order of private_usage and extobj_usage in drm_gpuvm_resv_add_fence()
  - always set drm_gpuvm_bo::evicted accordingly
  - explicitly clear drm_gpuvm_bo from evict list after successful validation
  - group reference get() calls with pointer assignments
  - call drm_gem_object_put() after vm_bo_free() callback
  - make lockdep checks explicit for drm_gpuvm_bo_* functions
  - improve documentation of struct drm_gpuvm_bo
  - fix a few documentation typos and style issues
  - use BIT() instead of shift ops for enum drm_gpuvm_flags

Changes in V8:
==============
  - do not always WARN on drm_gpuvm_check_overflow() to avoid userspace being
    able to spam the system log (Christian)
  - fix checkpath warning ENOTSUPP -> EOPNOTSUPP (Thomas)
  - fix another checkpatch error (assignment in if condition) (Thomas)
  - fix missing doc comment regarding a drm_gpuvm_bo's gpuva list (Thomas)
  - reference count struct drm_gpuvm, this avoids having (odd) rules when
    dealing with a drm_gpuvm_bo's gpuvm pointer

Danilo Krummrich (12):
  drm/gpuvm: convert WARN() to drm_WARN() variants
  drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
  drm/gpuvm: export drm_gpuvm_range_valid()
  drm/nouveau: make use of drm_gpuvm_range_valid()
  drm/gpuvm: add common dma-resv per struct drm_gpuvm
  drm/nouveau: make use of the GPUVM's shared dma-resv
  drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
  drm/nouveau: separately allocate struct nouveau_uvmm
  drm/gpuvm: reference count drm_gpuvm structures
  drm/gpuvm: add an abstraction for a VM / BO combination
  drm/gpuvm: track/lock/validate external/evicted objects
  drm/nouveau: use GPUVM common infrastructure

 drivers/gpu/drm/drm_gpuvm.c             | 1115 +++++++++++++++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.c    |   15 +-
 drivers/gpu/drm/nouveau/nouveau_bo.h    |    5 +
 drivers/gpu/drm/nouveau/nouveau_drm.c   |    5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  |   57 +-
 drivers/gpu/drm/nouveau/nouveau_exec.h  |    4 -
 drivers/gpu/drm/nouveau/nouveau_gem.c   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_sched.c |    9 +-
 drivers/gpu/drm/nouveau/nouveau_sched.h |    7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  |  269 +++---
 drivers/gpu/drm/nouveau/nouveau_uvmm.h  |    8 -
 include/drm/drm_gem.h                   |   32 +-
 include/drm/drm_gpuvm.h                 |  521 ++++++++++-
 14 files changed, 1796 insertions(+), 271 deletions(-)


base-commit: 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
-- 
2.41.0


^ permalink raw reply	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 00/12] [RFC] DRM GPUVM features
@ 2023-11-01 23:30 ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Currently GPUVM offers common infrastructure to track GPU VA allocations
and mappings, generically connect GPU VA mappings to their backing
buffers and perform more complex mapping operations on the GPU VA space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make GPUVM represent the
basis of a VM implementation. In this context, this patch series aims at
generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

The implementation introduces struct drm_gpuvm_bo, which serves as abstraction
combining a struct drm_gpuvm and struct drm_gem_object, similar to what
amdgpu does with struct amdgpu_bo_vm. While this adds a bit of complexity it
improves the efficiency of tracking external and evicted GEM objects.

This patch series is also available at [3].

[1] https://gitlab.freedesktop.org/nouvelles/kernel/-/commits/gpuvm-next

Changes in V2:
==============
  - rename 'drm_gpuva_manager' -> 'drm_gpuvm' which generally leads to more
    consistent naming
  - properly separate commits (introduce common dma-resv, drm_gpuvm_bo
    abstraction, etc.)
  - remove maple tree for tracking external objects, use a list drm_gpuvm_bos
    per drm_gpuvm instead
  - rework dma-resv locking helpers (Thomas)
  - add a locking helper for a given range of the VA space (Christian)
  - make the GPUVA manager buildable as module, rather than drm_exec
    builtin (Christian)

Changes in V3:
==============
  - rename missing function and files (Boris)
  - warn if vm_obj->obj != obj in drm_gpuva_link() (Boris)
  - don't expose drm_gpuvm_bo_destroy() (Boris)
  - unlink VM_BO from GEM in drm_gpuvm_bo_destroy() rather than
    drm_gpuva_unlink() and link within drm_gpuvm_bo_obtain() to keep
    drm_gpuvm_bo instances unique
  - add internal locking to external and evicted object lists to support drivers
    updating the VA space from within the fence signalling critical path (Boris)
  - unlink external objects and evicted objects from the GPUVM's list in
    drm_gpuvm_bo_destroy()
  - add more documentation and fix some kernel doc issues

Changes in V4:
==============
  - add a drm_gpuvm_resv() helper (Boris)
  - add a drm_gpuvm::<list_name>::local_list field (Boris)
  - remove drm_gpuvm_bo_get_unless_zero() helper (Boris)
  - fix missing NULL assignment in get_next_vm_bo_from_list() (Boris)
  - keep a drm_gem_object reference on potential vm_bo destroy (alternatively we
    could free the vm_bo and drop the vm_bo's drm_gem_object reference through
    async work)
  - introduce DRM_GPUVM_RESV_PROTECTED flag to indicate external locking through
    the corresponding dma-resv locks to optimize for drivers already holding
    them when needed; add the corresponding lock_assert_held() calls (Thomas)
  - make drm_gpuvm_bo_evict() per vm_bo and add a drm_gpuvm_bo_gem_evict()
    helper (Thomas)
  - pass a drm_gpuvm_bo in drm_gpuvm_ops::vm_bo_validate() (Thomas)
  - documentation fixes

Changes in V5:
==============
  - use a root drm_gem_object provided by the driver as a base for the VM's
    common dma-resv (Christian)
  - provide a helper to allocate a "dummy" root GEM object in case a driver
    specific root GEM object isn't available
  - add a dedicated patch for nouveau to make use of the GPUVM's shared dma-resv
  - improve documentation (Boris)
  - the following patches are removed from the series, since they already landed
    in drm-misc-next
    - f72c2db47080 ("drm/gpuvm: rename struct drm_gpuva_manager to struct drm_gpuvm")
    - fe7acaa727e1 ("drm/gpuvm: allow building as module")
    - 78f54469b871 ("drm/nouveau: uvmm: rename 'umgr' to 'base'")

Changes in V6:
==============
  - add drm_gpuvm_bo::evicted field protected by the drm_gem_object's dma-resv
    lock (Thomas)
    - additionally to the original proposal, always use drm_gpuvm_bo::evicted
      regardless of the used locking scheme and always keep it up to date
  - remove unneccesary get->put dance in drm_gpuva_unlink() (Thomas)
  - fix commit message wording (Thomas)
  - fix kernel doc warnings (kernel test robot)

Changes in V7:
==============
  - add a patch converting WARN() macros to drm_WARN() variants
  - allow drivers to pass the number of fences to reserve and the drm_exec flags
    through struct drm_gpuvm_exec
  - rename 'root' GEM object to 'resv' GEM object
  - fix order of private_usage and extobj_usage in drm_gpuvm_resv_add_fence()
  - always set drm_gpuvm_bo::evicted accordingly
  - explicitly clear drm_gpuvm_bo from evict list after successful validation
  - group reference get() calls with pointer assignments
  - call drm_gem_object_put() after vm_bo_free() callback
  - make lockdep checks explicit for drm_gpuvm_bo_* functions
  - improve documentation of struct drm_gpuvm_bo
  - fix a few documentation typos and style issues
  - use BIT() instead of shift ops for enum drm_gpuvm_flags

Changes in V8:
==============
  - do not always WARN on drm_gpuvm_check_overflow() to avoid userspace being
    able to spam the system log (Christian)
  - fix checkpath warning ENOTSUPP -> EOPNOTSUPP (Thomas)
  - fix another checkpatch error (assignment in if condition) (Thomas)
  - fix missing doc comment regarding a drm_gpuvm_bo's gpuva list (Thomas)
  - reference count struct drm_gpuvm, this avoids having (odd) rules when
    dealing with a drm_gpuvm_bo's gpuvm pointer

Danilo Krummrich (12):
  drm/gpuvm: convert WARN() to drm_WARN() variants
  drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
  drm/gpuvm: export drm_gpuvm_range_valid()
  drm/nouveau: make use of drm_gpuvm_range_valid()
  drm/gpuvm: add common dma-resv per struct drm_gpuvm
  drm/nouveau: make use of the GPUVM's shared dma-resv
  drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
  drm/nouveau: separately allocate struct nouveau_uvmm
  drm/gpuvm: reference count drm_gpuvm structures
  drm/gpuvm: add an abstraction for a VM / BO combination
  drm/gpuvm: track/lock/validate external/evicted objects
  drm/nouveau: use GPUVM common infrastructure

 drivers/gpu/drm/drm_gpuvm.c             | 1115 +++++++++++++++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.c    |   15 +-
 drivers/gpu/drm/nouveau/nouveau_bo.h    |    5 +
 drivers/gpu/drm/nouveau/nouveau_drm.c   |    5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  |   57 +-
 drivers/gpu/drm/nouveau/nouveau_exec.h  |    4 -
 drivers/gpu/drm/nouveau/nouveau_gem.c   |   10 +-
 drivers/gpu/drm/nouveau/nouveau_sched.c |    9 +-
 drivers/gpu/drm/nouveau/nouveau_sched.h |    7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  |  269 +++---
 drivers/gpu/drm/nouveau/nouveau_uvmm.h  |    8 -
 include/drm/drm_gem.h                   |   32 +-
 include/drm/drm_gpuvm.h                 |  521 ++++++++++-
 14 files changed, 1796 insertions(+), 271 deletions(-)


base-commit: 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
-- 
2.41.0


^ permalink raw reply	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 01/12] drm/gpuvm: convert WARN() to drm_WARN() variants
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Use drm_WARN() and drm_WARN_ON() variants to indicate drivers the
context the failing VM resides in.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 32 ++++++++++++++------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  3 ++-
 include/drm/drm_gpuvm.h                |  7 ++++++
 3 files changed, 26 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 08c088319652..d7367a202fee 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,12 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(u64 addr, u64 range)
+drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 {
 	u64 end;
 
-	return WARN(check_add_overflow(addr, range, &end),
-		    "GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
+			"GPUVA address limited to %zu bytes.\n", sizeof(end));
 }
 
 static bool
@@ -647,7 +647,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(addr, range) &&
+	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -656,6 +656,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @drm: the &drm_device this VM resides in
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -668,8 +669,8 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * &name is expected to be managed by the surrounding driver structures.
  */
 void
-drm_gpuvm_init(struct drm_gpuvm *gpuvm,
-	       const char *name,
+drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       struct drm_device *drm,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -677,20 +678,20 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
-	drm_gpuvm_check_overflow(start_offset, range);
-	gpuvm->mm_start = start_offset;
-	gpuvm->mm_range = range;
-
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
+	gpuvm->drm = drm;
 
-	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
+	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	gpuvm->mm_start = start_offset;
+	gpuvm->mm_range = range;
 
+	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
 	if (reserve_range) {
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(reserve_offset,
+		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
 						     reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
@@ -712,8 +713,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 	if (gpuvm->kernel_alloc_node.va.range)
 		__drm_gpuva_remove(&gpuvm->kernel_alloc_node);
 
-	WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
-	     "GPUVA tree is not empty, potentially leaking memory.");
+	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
+		 "GPUVA tree is not empty, potentially leaking memory.\n");
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
@@ -795,7 +796,8 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(va == &gpuvm->kernel_alloc_node)) {
-		WARN(1, "Can't destroy kernel reserved node.\n");
+		drm_WARN(gpuvm->drm, 1,
+			 "Can't destroy kernel reserved node.\n");
 		return;
 	}
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 5cf892c50f43..aaf5d28bd587 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1808,6 +1808,7 @@ int
 nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
+	struct drm_device *drm = cli->drm->dev;
 	int ret;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
 
@@ -1836,7 +1837,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	uvmm->kernel_managed_addr = kernel_managed_addr;
 	uvmm->kernel_managed_size = kernel_managed_size;
 
-	drm_gpuvm_init(&uvmm->base, cli->name,
+	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index bdfafc4a7705..687fd5893624 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -29,6 +29,7 @@
 #include <linux/rbtree.h>
 #include <linux/types.h>
 
+#include <drm/drm_device.h>
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
@@ -201,6 +202,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @drm: the &drm_device this VM lives in
+	 */
+	struct drm_device *drm;
+
 	/**
 	 * @mm_start: start of the VA space
 	 */
@@ -241,6 +247,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    struct drm_device *drm,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 01/12] drm/gpuvm: convert WARN() to drm_WARN() variants
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Use drm_WARN() and drm_WARN_ON() variants to indicate drivers the
context the failing VM resides in.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 32 ++++++++++++++------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  3 ++-
 include/drm/drm_gpuvm.h                |  7 ++++++
 3 files changed, 26 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 08c088319652..d7367a202fee 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,12 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(u64 addr, u64 range)
+drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 {
 	u64 end;
 
-	return WARN(check_add_overflow(addr, range, &end),
-		    "GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
+			"GPUVA address limited to %zu bytes.\n", sizeof(end));
 }
 
 static bool
@@ -647,7 +647,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(addr, range) &&
+	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -656,6 +656,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @drm: the &drm_device this VM resides in
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -668,8 +669,8 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * &name is expected to be managed by the surrounding driver structures.
  */
 void
-drm_gpuvm_init(struct drm_gpuvm *gpuvm,
-	       const char *name,
+drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       struct drm_device *drm,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -677,20 +678,20 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
-	drm_gpuvm_check_overflow(start_offset, range);
-	gpuvm->mm_start = start_offset;
-	gpuvm->mm_range = range;
-
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
+	gpuvm->drm = drm;
 
-	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
+	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	gpuvm->mm_start = start_offset;
+	gpuvm->mm_range = range;
 
+	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
 	if (reserve_range) {
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(reserve_offset,
+		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
 						     reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
@@ -712,8 +713,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 	if (gpuvm->kernel_alloc_node.va.range)
 		__drm_gpuva_remove(&gpuvm->kernel_alloc_node);
 
-	WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
-	     "GPUVA tree is not empty, potentially leaking memory.");
+	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
+		 "GPUVA tree is not empty, potentially leaking memory.\n");
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
@@ -795,7 +796,8 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(va == &gpuvm->kernel_alloc_node)) {
-		WARN(1, "Can't destroy kernel reserved node.\n");
+		drm_WARN(gpuvm->drm, 1,
+			 "Can't destroy kernel reserved node.\n");
 		return;
 	}
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 5cf892c50f43..aaf5d28bd587 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1808,6 +1808,7 @@ int
 nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
+	struct drm_device *drm = cli->drm->dev;
 	int ret;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
 
@@ -1836,7 +1837,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	uvmm->kernel_managed_addr = kernel_managed_addr;
 	uvmm->kernel_managed_size = kernel_managed_size;
 
-	drm_gpuvm_init(&uvmm->base, cli->name,
+	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index bdfafc4a7705..687fd5893624 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -29,6 +29,7 @@
 #include <linux/rbtree.h>
 #include <linux/types.h>
 
+#include <drm/drm_device.h>
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
@@ -201,6 +202,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @drm: the &drm_device this VM lives in
+	 */
+	struct drm_device *drm;
+
 	/**
 	 * @mm_start: start of the VA space
 	 */
@@ -241,6 +247,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    struct drm_device *drm,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 01/12] drm/gpuvm: convert WARN() to drm_WARN() variants
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Use drm_WARN() and drm_WARN_ON() variants to indicate drivers the
context the failing VM resides in.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 32 ++++++++++++++------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  3 ++-
 include/drm/drm_gpuvm.h                |  7 ++++++
 3 files changed, 26 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 08c088319652..d7367a202fee 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,12 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(u64 addr, u64 range)
+drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 {
 	u64 end;
 
-	return WARN(check_add_overflow(addr, range, &end),
-		    "GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
+			"GPUVA address limited to %zu bytes.\n", sizeof(end));
 }
 
 static bool
@@ -647,7 +647,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(addr, range) &&
+	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -656,6 +656,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @drm: the &drm_device this VM resides in
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -668,8 +669,8 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
  * &name is expected to be managed by the surrounding driver structures.
  */
 void
-drm_gpuvm_init(struct drm_gpuvm *gpuvm,
-	       const char *name,
+drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       struct drm_device *drm,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -677,20 +678,20 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
-	drm_gpuvm_check_overflow(start_offset, range);
-	gpuvm->mm_start = start_offset;
-	gpuvm->mm_range = range;
-
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
+	gpuvm->drm = drm;
 
-	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
+	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	gpuvm->mm_start = start_offset;
+	gpuvm->mm_range = range;
 
+	memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));
 	if (reserve_range) {
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(reserve_offset,
+		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
 						     reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
@@ -712,8 +713,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 	if (gpuvm->kernel_alloc_node.va.range)
 		__drm_gpuva_remove(&gpuvm->kernel_alloc_node);
 
-	WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
-	     "GPUVA tree is not empty, potentially leaking memory.");
+	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
+		 "GPUVA tree is not empty, potentially leaking memory.\n");
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
@@ -795,7 +796,8 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(va == &gpuvm->kernel_alloc_node)) {
-		WARN(1, "Can't destroy kernel reserved node.\n");
+		drm_WARN(gpuvm->drm, 1,
+			 "Can't destroy kernel reserved node.\n");
 		return;
 	}
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 5cf892c50f43..aaf5d28bd587 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1808,6 +1808,7 @@ int
 nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
+	struct drm_device *drm = cli->drm->dev;
 	int ret;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
 
@@ -1836,7 +1837,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	uvmm->kernel_managed_addr = kernel_managed_addr;
 	uvmm->kernel_managed_size = kernel_managed_size;
 
-	drm_gpuvm_init(&uvmm->base, cli->name,
+	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index bdfafc4a7705..687fd5893624 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -29,6 +29,7 @@
 #include <linux/rbtree.h>
 #include <linux/types.h>
 
+#include <drm/drm_device.h>
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
@@ -201,6 +202,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @drm: the &drm_device this VM lives in
+	 */
+	struct drm_device *drm;
+
 	/**
 	 * @mm_start: start of the VA space
 	 */
@@ -241,6 +247,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    struct drm_device *drm,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Don't always WARN in drm_gpuvm_check_overflow() and separate it into a
drm_gpuvm_check_overflow() and a dedicated
drm_gpuvm_warn_check_overflow() variant.

This avoids printing warnings due to invalid userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index d7367a202fee..445767f8fbc4 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+drm_gpuvm_check_overflow(u64 addr, u64 range)
 {
 	u64 end;
 
-	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
-			"GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return check_add_overflow(addr, range, &end);
+}
+
+static bool
+drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+{
+	return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr, range),
+			"GPUVA address limited to %zu bytes.\n", sizeof(addr));
 }
 
 static bool
@@ -647,7 +653,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
+	return !drm_gpuvm_check_overflow(addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 
-	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
 	gpuvm->mm_range = range;
 
@@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
-						     reserve_range)))
+		if (likely(!drm_gpuvm_warn_check_overflow(gpuvm, reserve_offset,
+							  reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
 }
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Don't always WARN in drm_gpuvm_check_overflow() and separate it into a
drm_gpuvm_check_overflow() and a dedicated
drm_gpuvm_warn_check_overflow() variant.

This avoids printing warnings due to invalid userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index d7367a202fee..445767f8fbc4 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+drm_gpuvm_check_overflow(u64 addr, u64 range)
 {
 	u64 end;
 
-	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
-			"GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return check_add_overflow(addr, range, &end);
+}
+
+static bool
+drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+{
+	return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr, range),
+			"GPUVA address limited to %zu bytes.\n", sizeof(addr));
 }
 
 static bool
@@ -647,7 +653,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
+	return !drm_gpuvm_check_overflow(addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 
-	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
 	gpuvm->mm_range = range;
 
@@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
-						     reserve_range)))
+		if (likely(!drm_gpuvm_warn_check_overflow(gpuvm, reserve_offset,
+							  reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
 }
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Don't always WARN in drm_gpuvm_check_overflow() and separate it into a
drm_gpuvm_check_overflow() and a dedicated
drm_gpuvm_warn_check_overflow() variant.

This avoids printing warnings due to invalid userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index d7367a202fee..445767f8fbc4 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 static void __drm_gpuva_remove(struct drm_gpuva *va);
 
 static bool
-drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+drm_gpuvm_check_overflow(u64 addr, u64 range)
 {
 	u64 end;
 
-	return drm_WARN(gpuvm->drm, check_add_overflow(addr, range, &end),
-			"GPUVA address limited to %zu bytes.\n", sizeof(end));
+	return check_add_overflow(addr, range, &end);
+}
+
+static bool
+drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
+{
+	return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr, range),
+			"GPUVA address limited to %zu bytes.\n", sizeof(addr));
 }
 
 static bool
@@ -647,7 +653,7 @@ static bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
-	return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
+	return !drm_gpuvm_check_overflow(addr, range) &&
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
@@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 
-	drm_gpuvm_check_overflow(gpuvm, start_offset, range);
+	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
 	gpuvm->mm_range = range;
 
@@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		gpuvm->kernel_alloc_node.va.addr = reserve_offset;
 		gpuvm->kernel_alloc_node.va.range = reserve_range;
 
-		if (likely(!drm_gpuvm_check_overflow(gpuvm, reserve_offset,
-						     reserve_range)))
+		if (likely(!drm_gpuvm_warn_check_overflow(gpuvm, reserve_offset,
+							  reserve_range)))
 			__drm_gpuva_insert(gpuvm, &gpuvm->kernel_alloc_node);
 	}
 }
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Drivers may use this function to validate userspace requests in advance,
hence export it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
 include/drm/drm_gpuvm.h     |  1 +
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 445767f8fbc4..2669f9bbc377 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 	return krange && addr < kend && kstart < end;
 }
 
-static bool
+/**
+ * drm_gpuvm_range_valid() - checks whether the given range is valid for the
+ * given &drm_gpuvm
+ * @gpuvm: the GPUVM to check the range for
+ * @addr: the base address
+ * @range: the range starting from the base address
+ *
+ * Checks whether the range is within the GPUVM's managed boundaries.
+ *
+ * Returns: true for a valid range, false otherwise
+ */
+bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
@@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
+EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 687fd5893624..13eac6f70061 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    const struct drm_gpuvm_ops *ops);
 void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 
+bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
 static inline struct drm_gpuva *
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Drivers may use this function to validate userspace requests in advance,
hence export it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
 include/drm/drm_gpuvm.h     |  1 +
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 445767f8fbc4..2669f9bbc377 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 	return krange && addr < kend && kstart < end;
 }
 
-static bool
+/**
+ * drm_gpuvm_range_valid() - checks whether the given range is valid for the
+ * given &drm_gpuvm
+ * @gpuvm: the GPUVM to check the range for
+ * @addr: the base address
+ * @range: the range starting from the base address
+ *
+ * Checks whether the range is within the GPUVM's managed boundaries.
+ *
+ * Returns: true for a valid range, false otherwise
+ */
+bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
@@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
+EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 687fd5893624..13eac6f70061 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    const struct drm_gpuvm_ops *ops);
 void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 
+bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
 static inline struct drm_gpuva *
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Drivers may use this function to validate userspace requests in advance,
hence export it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
 include/drm/drm_gpuvm.h     |  1 +
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 445767f8fbc4..2669f9bbc377 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm *gpuvm, u64 addr, u64 range)
 	return krange && addr < kend && kstart < end;
 }
 
-static bool
+/**
+ * drm_gpuvm_range_valid() - checks whether the given range is valid for the
+ * given &drm_gpuvm
+ * @gpuvm: the GPUVM to check the range for
+ * @addr: the base address
+ * @range: the range starting from the base address
+ *
+ * Checks whether the range is within the GPUVM's managed boundaries.
+ *
+ * Returns: true for a valid range, false otherwise
+ */
+bool
 drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 		      u64 addr, u64 range)
 {
@@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 	       drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
 	       !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
 }
+EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 687fd5893624..13eac6f70061 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    const struct drm_gpuvm_ops *ops);
 void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 
+bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
 static inline struct drm_gpuva *
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Use drm_gpuvm_range_valid() in order to validate userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 17 +----------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  3 ---
 2 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index aaf5d28bd587..641a911528db 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -929,25 +929,13 @@ nouveau_uvmm_sm_unmap_cleanup(struct nouveau_uvmm *uvmm,
 static int
 nouveau_uvmm_validate_range(struct nouveau_uvmm *uvmm, u64 addr, u64 range)
 {
-	u64 end = addr + range;
-	u64 kernel_managed_end = uvmm->kernel_managed_addr +
-				 uvmm->kernel_managed_size;
-
 	if (addr & ~PAGE_MASK)
 		return -EINVAL;
 
 	if (range & ~PAGE_MASK)
 		return -EINVAL;
 
-	if (end <= addr)
-		return -EINVAL;
-
-	if (addr < NOUVEAU_VA_SPACE_START ||
-	    end > NOUVEAU_VA_SPACE_END)
-		return -EINVAL;
-
-	if (addr < kernel_managed_end &&
-	    end > uvmm->kernel_managed_addr)
+	if (!drm_gpuvm_range_valid(&uvmm->base, addr, range))
 		return -EINVAL;
 
 	return 0;
@@ -1834,9 +1822,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	uvmm->kernel_managed_addr = kernel_managed_addr;
-	uvmm->kernel_managed_size = kernel_managed_size;
-
 	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index a308c59760a5..06a0c36de392 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -14,9 +14,6 @@ struct nouveau_uvmm {
 	struct mutex mutex;
 	struct dma_resv resv;
 
-	u64 kernel_managed_addr;
-	u64 kernel_managed_size;
-
 	bool disabled;
 };
 
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Use drm_gpuvm_range_valid() in order to validate userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 17 +----------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  3 ---
 2 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index aaf5d28bd587..641a911528db 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -929,25 +929,13 @@ nouveau_uvmm_sm_unmap_cleanup(struct nouveau_uvmm *uvmm,
 static int
 nouveau_uvmm_validate_range(struct nouveau_uvmm *uvmm, u64 addr, u64 range)
 {
-	u64 end = addr + range;
-	u64 kernel_managed_end = uvmm->kernel_managed_addr +
-				 uvmm->kernel_managed_size;
-
 	if (addr & ~PAGE_MASK)
 		return -EINVAL;
 
 	if (range & ~PAGE_MASK)
 		return -EINVAL;
 
-	if (end <= addr)
-		return -EINVAL;
-
-	if (addr < NOUVEAU_VA_SPACE_START ||
-	    end > NOUVEAU_VA_SPACE_END)
-		return -EINVAL;
-
-	if (addr < kernel_managed_end &&
-	    end > uvmm->kernel_managed_addr)
+	if (!drm_gpuvm_range_valid(&uvmm->base, addr, range))
 		return -EINVAL;
 
 	return 0;
@@ -1834,9 +1822,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	uvmm->kernel_managed_addr = kernel_managed_addr;
-	uvmm->kernel_managed_size = kernel_managed_size;
-
 	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index a308c59760a5..06a0c36de392 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -14,9 +14,6 @@ struct nouveau_uvmm {
 	struct mutex mutex;
 	struct dma_resv resv;
 
-	u64 kernel_managed_addr;
-	u64 kernel_managed_size;
-
 	bool disabled;
 };
 
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Use drm_gpuvm_range_valid() in order to validate userspace requests.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 17 +----------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  3 ---
 2 files changed, 1 insertion(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index aaf5d28bd587..641a911528db 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -929,25 +929,13 @@ nouveau_uvmm_sm_unmap_cleanup(struct nouveau_uvmm *uvmm,
 static int
 nouveau_uvmm_validate_range(struct nouveau_uvmm *uvmm, u64 addr, u64 range)
 {
-	u64 end = addr + range;
-	u64 kernel_managed_end = uvmm->kernel_managed_addr +
-				 uvmm->kernel_managed_size;
-
 	if (addr & ~PAGE_MASK)
 		return -EINVAL;
 
 	if (range & ~PAGE_MASK)
 		return -EINVAL;
 
-	if (end <= addr)
-		return -EINVAL;
-
-	if (addr < NOUVEAU_VA_SPACE_START ||
-	    end > NOUVEAU_VA_SPACE_END)
-		return -EINVAL;
-
-	if (addr < kernel_managed_end &&
-	    end > uvmm->kernel_managed_addr)
+	if (!drm_gpuvm_range_valid(&uvmm->base, addr, range))
 		return -EINVAL;
 
 	return 0;
@@ -1834,9 +1822,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	uvmm->kernel_managed_addr = kernel_managed_addr;
-	uvmm->kernel_managed_size = kernel_managed_size;
-
 	drm_gpuvm_init(&uvmm->base, cli->name, drm,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index a308c59760a5..06a0c36de392 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -14,9 +14,6 @@ struct nouveau_uvmm {
 	struct mutex mutex;
 	struct dma_resv resv;
 
-	u64 kernel_managed_addr;
-	u64 kernel_managed_size;
-
 	bool disabled;
 };
 
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 05/12] drm/gpuvm: add common dma-resv per struct drm_gpuvm
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Provide a common dma-resv for GEM objects not being used outside of this
GPU-VM. This is used in a subsequent patch to generalize dma-resv,
external and evicted object handling and GEM validation.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 53 ++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 ++++++-
 include/drm/drm_gpuvm.h                | 33 ++++++++++++++++
 3 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2669f9bbc377..af5805e4d7c9 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -61,6 +61,15 @@
  * contained within struct drm_gpuva already. Hence, for inserting &drm_gpuva
  * entries from within dma-fence signalling critical sections it is enough to
  * pre-allocate the &drm_gpuva structures.
+ *
+ * &drm_gem_objects which are private to a single VM can share a common
+ * &dma_resv in order to improve locking efficiency (e.g. with &drm_exec).
+ * For this purpose drivers must pass a &drm_gem_object to drm_gpuvm_init(), in
+ * the following called 'resv object', which serves as the container of the
+ * GPUVM's shared &dma_resv. This resv object can be a driver specific
+ * &drm_gem_object, such as the &drm_gem_object containing the root page table,
+ * but it can also be a 'dummy' object, which can be allocated with
+ * drm_gpuvm_resv_object_alloc().
  */
 
 /**
@@ -670,11 +679,49 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
+static void
+drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
+{
+	drm_gem_object_release(obj);
+	kfree(obj);
+}
+
+static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
+	.free = drm_gpuvm_gem_object_free,
+};
+
+/**
+ * drm_gpuvm_resv_object_alloc() - allocate a dummy &drm_gem_object
+ * @drm: the drivers &drm_device
+ *
+ * Allocates a dummy &drm_gem_object which can be passed to drm_gpuvm_init() in
+ * order to serve as root GEM object providing the &drm_resv shared across
+ * &drm_gem_objects local to a single GPUVM.
+ *
+ * Returns: the &drm_gem_object on success, NULL on failure
+ */
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm)
+{
+	struct drm_gem_object *obj;
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->funcs = &drm_gpuvm_object_funcs;
+	drm_gem_private_object_init(drm, obj, 0);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
+
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
  * @drm: the &drm_device this VM resides in
+ * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -689,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	       struct drm_device *drm,
+	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -699,6 +747,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
+	gpuvm->r_obj = r_obj;
+
+	drm_gem_object_get(r_obj);
 
 	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
@@ -733,6 +784,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
+
+	drm_gem_object_put(gpuvm->r_obj);
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 641a911528db..f74bf30bc683 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1797,8 +1797,9 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
 	struct drm_device *drm = cli->drm->dev;
-	int ret;
+	struct drm_gem_object *r_obj;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	int ret;
 
 	mutex_init(&uvmm->mutex);
 	dma_resv_init(&uvmm->resv);
@@ -1822,11 +1823,19 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm,
+	r_obj = drm_gpuvm_resv_object_alloc(drm);
+	if (!r_obj) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
 		       NULL);
+	/* GPUVM takes care from here on. */
+	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 13eac6f70061..ff3377cbfe52 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -244,10 +244,16 @@ struct drm_gpuvm {
 	 * @ops: &drm_gpuvm_ops providing the split/merge steps to drivers
 	 */
 	const struct drm_gpuvm_ops *ops;
+
+	/**
+	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
+	 */
+	struct drm_gem_object *r_obj;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    struct drm_device *drm,
+		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
@@ -256,6 +262,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm);
+
+/**
+ * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
+ */
+#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
+
+/**
+ * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the &drm_gpuvm's
+ * &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gem_object holding the &drm_gpuvm's shared
+ * &dma_resv
+ */
+#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
+
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 05/12] drm/gpuvm: add common dma-resv per struct drm_gpuvm
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Provide a common dma-resv for GEM objects not being used outside of this
GPU-VM. This is used in a subsequent patch to generalize dma-resv,
external and evicted object handling and GEM validation.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 53 ++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 ++++++-
 include/drm/drm_gpuvm.h                | 33 ++++++++++++++++
 3 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2669f9bbc377..af5805e4d7c9 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -61,6 +61,15 @@
  * contained within struct drm_gpuva already. Hence, for inserting &drm_gpuva
  * entries from within dma-fence signalling critical sections it is enough to
  * pre-allocate the &drm_gpuva structures.
+ *
+ * &drm_gem_objects which are private to a single VM can share a common
+ * &dma_resv in order to improve locking efficiency (e.g. with &drm_exec).
+ * For this purpose drivers must pass a &drm_gem_object to drm_gpuvm_init(), in
+ * the following called 'resv object', which serves as the container of the
+ * GPUVM's shared &dma_resv. This resv object can be a driver specific
+ * &drm_gem_object, such as the &drm_gem_object containing the root page table,
+ * but it can also be a 'dummy' object, which can be allocated with
+ * drm_gpuvm_resv_object_alloc().
  */
 
 /**
@@ -670,11 +679,49 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
+static void
+drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
+{
+	drm_gem_object_release(obj);
+	kfree(obj);
+}
+
+static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
+	.free = drm_gpuvm_gem_object_free,
+};
+
+/**
+ * drm_gpuvm_resv_object_alloc() - allocate a dummy &drm_gem_object
+ * @drm: the drivers &drm_device
+ *
+ * Allocates a dummy &drm_gem_object which can be passed to drm_gpuvm_init() in
+ * order to serve as root GEM object providing the &drm_resv shared across
+ * &drm_gem_objects local to a single GPUVM.
+ *
+ * Returns: the &drm_gem_object on success, NULL on failure
+ */
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm)
+{
+	struct drm_gem_object *obj;
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->funcs = &drm_gpuvm_object_funcs;
+	drm_gem_private_object_init(drm, obj, 0);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
+
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
  * @drm: the &drm_device this VM resides in
+ * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -689,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	       struct drm_device *drm,
+	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -699,6 +747,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
+	gpuvm->r_obj = r_obj;
+
+	drm_gem_object_get(r_obj);
 
 	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
@@ -733,6 +784,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
+
+	drm_gem_object_put(gpuvm->r_obj);
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 641a911528db..f74bf30bc683 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1797,8 +1797,9 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
 	struct drm_device *drm = cli->drm->dev;
-	int ret;
+	struct drm_gem_object *r_obj;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	int ret;
 
 	mutex_init(&uvmm->mutex);
 	dma_resv_init(&uvmm->resv);
@@ -1822,11 +1823,19 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm,
+	r_obj = drm_gpuvm_resv_object_alloc(drm);
+	if (!r_obj) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
 		       NULL);
+	/* GPUVM takes care from here on. */
+	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 13eac6f70061..ff3377cbfe52 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -244,10 +244,16 @@ struct drm_gpuvm {
 	 * @ops: &drm_gpuvm_ops providing the split/merge steps to drivers
 	 */
 	const struct drm_gpuvm_ops *ops;
+
+	/**
+	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
+	 */
+	struct drm_gem_object *r_obj;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    struct drm_device *drm,
+		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
@@ -256,6 +262,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm);
+
+/**
+ * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
+ */
+#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
+
+/**
+ * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the &drm_gpuvm's
+ * &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gem_object holding the &drm_gpuvm's shared
+ * &dma_resv
+ */
+#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
+
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 05/12] drm/gpuvm: add common dma-resv per struct drm_gpuvm
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Provide a common dma-resv for GEM objects not being used outside of this
GPU-VM. This is used in a subsequent patch to generalize dma-resv,
external and evicted object handling and GEM validation.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 53 ++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 ++++++-
 include/drm/drm_gpuvm.h                | 33 ++++++++++++++++
 3 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2669f9bbc377..af5805e4d7c9 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -61,6 +61,15 @@
  * contained within struct drm_gpuva already. Hence, for inserting &drm_gpuva
  * entries from within dma-fence signalling critical sections it is enough to
  * pre-allocate the &drm_gpuva structures.
+ *
+ * &drm_gem_objects which are private to a single VM can share a common
+ * &dma_resv in order to improve locking efficiency (e.g. with &drm_exec).
+ * For this purpose drivers must pass a &drm_gem_object to drm_gpuvm_init(), in
+ * the following called 'resv object', which serves as the container of the
+ * GPUVM's shared &dma_resv. This resv object can be a driver specific
+ * &drm_gem_object, such as the &drm_gem_object containing the root page table,
+ * but it can also be a 'dummy' object, which can be allocated with
+ * drm_gpuvm_resv_object_alloc().
  */
 
 /**
@@ -670,11 +679,49 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 
+static void
+drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
+{
+	drm_gem_object_release(obj);
+	kfree(obj);
+}
+
+static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
+	.free = drm_gpuvm_gem_object_free,
+};
+
+/**
+ * drm_gpuvm_resv_object_alloc() - allocate a dummy &drm_gem_object
+ * @drm: the drivers &drm_device
+ *
+ * Allocates a dummy &drm_gem_object which can be passed to drm_gpuvm_init() in
+ * order to serve as root GEM object providing the &drm_resv shared across
+ * &drm_gem_objects local to a single GPUVM.
+ *
+ * Returns: the &drm_gem_object on success, NULL on failure
+ */
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm)
+{
+	struct drm_gem_object *obj;
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->funcs = &drm_gpuvm_object_funcs;
+	drm_gem_private_object_init(drm, obj, 0);
+
+	return obj;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
+
 /**
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
  * @drm: the &drm_device this VM resides in
+ * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
  * @range: the size of the GPU VA space
  * @reserve_offset: the start of the kernel reserved GPU VA area
@@ -689,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	       struct drm_device *drm,
+	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
 	       u64 reserve_offset, u64 reserve_range,
 	       const struct drm_gpuvm_ops *ops)
@@ -699,6 +747,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
+	gpuvm->r_obj = r_obj;
+
+	drm_gem_object_get(r_obj);
 
 	drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
 	gpuvm->mm_start = start_offset;
@@ -733,6 +784,8 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
+
+	drm_gem_object_put(gpuvm->r_obj);
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 641a911528db..f74bf30bc683 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1797,8 +1797,9 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		  u64 kernel_managed_addr, u64 kernel_managed_size)
 {
 	struct drm_device *drm = cli->drm->dev;
-	int ret;
+	struct drm_gem_object *r_obj;
 	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	int ret;
 
 	mutex_init(&uvmm->mutex);
 	dma_resv_init(&uvmm->resv);
@@ -1822,11 +1823,19 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm,
+	r_obj = drm_gpuvm_resv_object_alloc(drm);
+	if (!r_obj) {
+		ret = -ENOMEM;
+		goto out_unlock;
+	}
+
+	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
 		       NULL);
+	/* GPUVM takes care from here on. */
+	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 13eac6f70061..ff3377cbfe52 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -244,10 +244,16 @@ struct drm_gpuvm {
 	 * @ops: &drm_gpuvm_ops providing the split/merge steps to drivers
 	 */
 	const struct drm_gpuvm_ops *ops;
+
+	/**
+	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
+	 */
+	struct drm_gem_object *r_obj;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    struct drm_device *drm,
+		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
@@ -256,6 +262,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 
+struct drm_gem_object *
+drm_gpuvm_resv_object_alloc(struct drm_device *drm);
+
+/**
+ * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
+ */
+#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
+
+/**
+ * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the &drm_gpuvm's
+ * &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gem_object holding the &drm_gpuvm's shared
+ * &dma_resv
+ */
+#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
+
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 06/12] drm/nouveau: make use of the GPUVM's shared dma-resv
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

DRM GEM objects private to a single GPUVM can use a shared dma-resv.
Make use of the shared dma-resv of GPUVM rather than a driver specific
one.

The shared dma-resv originates from a "root" GEM object serving as
container for the dma-resv to make it compatible with drm_exec.

In order to make sure the object proving the shared dma-resv can't be
freed up before the objects making use of it, let every such GEM object
take a reference on it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c   | 11 +++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.h   |  5 +++++
 drivers/gpu/drm/nouveau/nouveau_gem.c  | 10 ++++++++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  7 ++-----
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  1 -
 5 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 0f3bd187ede6..7afad86da64b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -148,10 +148,17 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
 	 * If nouveau_bo_new() allocated this buffer, the GEM object was never
 	 * initialized, so don't attempt to release it.
 	 */
-	if (bo->base.dev)
+	if (bo->base.dev) {
+		/* Gem objects not being shared with other VMs get their
+		 * dma_resv from a root GEM object.
+		 */
+		if (nvbo->no_share)
+			drm_gem_object_put(nvbo->r_obj);
+
 		drm_gem_object_release(&bo->base);
-	else
+	} else {
 		dma_resv_fini(&bo->base._resv);
+	}
 
 	kfree(nvbo);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.h b/drivers/gpu/drm/nouveau/nouveau_bo.h
index 07f671cf895e..70c551921a9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.h
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.h
@@ -26,6 +26,11 @@ struct nouveau_bo {
 	struct list_head entry;
 	int pbbo_index;
 	bool validate_mapped;
+
+	/* Root GEM object we derive the dma_resv of in case this BO is not
+	 * shared between VMs.
+	 */
+	struct drm_gem_object *r_obj;
 	bool no_share;
 
 	/* GPU address space is independent of CPU word size */
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index a0d303e5ce3d..49c2bcbef129 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -111,7 +111,8 @@ nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv)
 	if (vmm->vmm.object.oclass < NVIF_CLASS_VMM_NV50)
 		return 0;
 
-	if (nvbo->no_share && uvmm && &uvmm->resv != nvbo->bo.base.resv)
+	if (nvbo->no_share && uvmm &&
+	    drm_gpuvm_resv(&uvmm->base) != nvbo->bo.base.resv)
 		return -EPERM;
 
 	ret = ttm_bo_reserve(&nvbo->bo, false, false, NULL);
@@ -245,7 +246,7 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 		if (unlikely(!uvmm))
 			return -EINVAL;
 
-		resv = &uvmm->resv;
+		resv = drm_gpuvm_resv(&uvmm->base);
 	}
 
 	if (!(domain & (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART)))
@@ -288,6 +289,11 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 	if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA)
 		nvbo->valid_domains &= domain;
 
+	if (nvbo->no_share) {
+		nvbo->r_obj = drm_gpuvm_resv_obj(&uvmm->base);
+		drm_gem_object_get(nvbo->r_obj);
+	}
+
 	*pnvbo = nvbo;
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f74bf30bc683..8977a518de96 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1802,7 +1802,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	int ret;
 
 	mutex_init(&uvmm->mutex);
-	dma_resv_init(&uvmm->resv);
 	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
 	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
 
@@ -1842,14 +1841,14 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 			    kernel_managed_addr, kernel_managed_size,
 			    NULL, 0, &cli->uvmm.vmm.vmm);
 	if (ret)
-		goto out_free_gpuva_mgr;
+		goto out_gpuvm_fini;
 
 	cli->uvmm.vmm.cli = cli;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
-out_free_gpuva_mgr:
+out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
@@ -1907,6 +1906,4 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
 	mutex_unlock(&cli->mutex);
-
-	dma_resv_fini(&uvmm->resv);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 06a0c36de392..22607270fae0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,7 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-	struct dma_resv resv;
 
 	bool disabled;
 };
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 06/12] drm/nouveau: make use of the GPUVM's shared dma-resv
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

DRM GEM objects private to a single GPUVM can use a shared dma-resv.
Make use of the shared dma-resv of GPUVM rather than a driver specific
one.

The shared dma-resv originates from a "root" GEM object serving as
container for the dma-resv to make it compatible with drm_exec.

In order to make sure the object proving the shared dma-resv can't be
freed up before the objects making use of it, let every such GEM object
take a reference on it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c   | 11 +++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.h   |  5 +++++
 drivers/gpu/drm/nouveau/nouveau_gem.c  | 10 ++++++++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  7 ++-----
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  1 -
 5 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 0f3bd187ede6..7afad86da64b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -148,10 +148,17 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
 	 * If nouveau_bo_new() allocated this buffer, the GEM object was never
 	 * initialized, so don't attempt to release it.
 	 */
-	if (bo->base.dev)
+	if (bo->base.dev) {
+		/* Gem objects not being shared with other VMs get their
+		 * dma_resv from a root GEM object.
+		 */
+		if (nvbo->no_share)
+			drm_gem_object_put(nvbo->r_obj);
+
 		drm_gem_object_release(&bo->base);
-	else
+	} else {
 		dma_resv_fini(&bo->base._resv);
+	}
 
 	kfree(nvbo);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.h b/drivers/gpu/drm/nouveau/nouveau_bo.h
index 07f671cf895e..70c551921a9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.h
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.h
@@ -26,6 +26,11 @@ struct nouveau_bo {
 	struct list_head entry;
 	int pbbo_index;
 	bool validate_mapped;
+
+	/* Root GEM object we derive the dma_resv of in case this BO is not
+	 * shared between VMs.
+	 */
+	struct drm_gem_object *r_obj;
 	bool no_share;
 
 	/* GPU address space is independent of CPU word size */
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index a0d303e5ce3d..49c2bcbef129 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -111,7 +111,8 @@ nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv)
 	if (vmm->vmm.object.oclass < NVIF_CLASS_VMM_NV50)
 		return 0;
 
-	if (nvbo->no_share && uvmm && &uvmm->resv != nvbo->bo.base.resv)
+	if (nvbo->no_share && uvmm &&
+	    drm_gpuvm_resv(&uvmm->base) != nvbo->bo.base.resv)
 		return -EPERM;
 
 	ret = ttm_bo_reserve(&nvbo->bo, false, false, NULL);
@@ -245,7 +246,7 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 		if (unlikely(!uvmm))
 			return -EINVAL;
 
-		resv = &uvmm->resv;
+		resv = drm_gpuvm_resv(&uvmm->base);
 	}
 
 	if (!(domain & (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART)))
@@ -288,6 +289,11 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 	if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA)
 		nvbo->valid_domains &= domain;
 
+	if (nvbo->no_share) {
+		nvbo->r_obj = drm_gpuvm_resv_obj(&uvmm->base);
+		drm_gem_object_get(nvbo->r_obj);
+	}
+
 	*pnvbo = nvbo;
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f74bf30bc683..8977a518de96 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1802,7 +1802,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	int ret;
 
 	mutex_init(&uvmm->mutex);
-	dma_resv_init(&uvmm->resv);
 	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
 	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
 
@@ -1842,14 +1841,14 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 			    kernel_managed_addr, kernel_managed_size,
 			    NULL, 0, &cli->uvmm.vmm.vmm);
 	if (ret)
-		goto out_free_gpuva_mgr;
+		goto out_gpuvm_fini;
 
 	cli->uvmm.vmm.cli = cli;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
-out_free_gpuva_mgr:
+out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
@@ -1907,6 +1906,4 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
 	mutex_unlock(&cli->mutex);
-
-	dma_resv_fini(&uvmm->resv);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 06a0c36de392..22607270fae0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,7 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-	struct dma_resv resv;
 
 	bool disabled;
 };
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 06/12] drm/nouveau: make use of the GPUVM's shared dma-resv
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

DRM GEM objects private to a single GPUVM can use a shared dma-resv.
Make use of the shared dma-resv of GPUVM rather than a driver specific
one.

The shared dma-resv originates from a "root" GEM object serving as
container for the dma-resv to make it compatible with drm_exec.

In order to make sure the object proving the shared dma-resv can't be
freed up before the objects making use of it, let every such GEM object
take a reference on it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c   | 11 +++++++++--
 drivers/gpu/drm/nouveau/nouveau_bo.h   |  5 +++++
 drivers/gpu/drm/nouveau/nouveau_gem.c  | 10 ++++++++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  7 ++-----
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  1 -
 5 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 0f3bd187ede6..7afad86da64b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -148,10 +148,17 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
 	 * If nouveau_bo_new() allocated this buffer, the GEM object was never
 	 * initialized, so don't attempt to release it.
 	 */
-	if (bo->base.dev)
+	if (bo->base.dev) {
+		/* Gem objects not being shared with other VMs get their
+		 * dma_resv from a root GEM object.
+		 */
+		if (nvbo->no_share)
+			drm_gem_object_put(nvbo->r_obj);
+
 		drm_gem_object_release(&bo->base);
-	else
+	} else {
 		dma_resv_fini(&bo->base._resv);
+	}
 
 	kfree(nvbo);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.h b/drivers/gpu/drm/nouveau/nouveau_bo.h
index 07f671cf895e..70c551921a9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.h
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.h
@@ -26,6 +26,11 @@ struct nouveau_bo {
 	struct list_head entry;
 	int pbbo_index;
 	bool validate_mapped;
+
+	/* Root GEM object we derive the dma_resv of in case this BO is not
+	 * shared between VMs.
+	 */
+	struct drm_gem_object *r_obj;
 	bool no_share;
 
 	/* GPU address space is independent of CPU word size */
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index a0d303e5ce3d..49c2bcbef129 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -111,7 +111,8 @@ nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv)
 	if (vmm->vmm.object.oclass < NVIF_CLASS_VMM_NV50)
 		return 0;
 
-	if (nvbo->no_share && uvmm && &uvmm->resv != nvbo->bo.base.resv)
+	if (nvbo->no_share && uvmm &&
+	    drm_gpuvm_resv(&uvmm->base) != nvbo->bo.base.resv)
 		return -EPERM;
 
 	ret = ttm_bo_reserve(&nvbo->bo, false, false, NULL);
@@ -245,7 +246,7 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 		if (unlikely(!uvmm))
 			return -EINVAL;
 
-		resv = &uvmm->resv;
+		resv = drm_gpuvm_resv(&uvmm->base);
 	}
 
 	if (!(domain & (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART)))
@@ -288,6 +289,11 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
 	if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA)
 		nvbo->valid_domains &= domain;
 
+	if (nvbo->no_share) {
+		nvbo->r_obj = drm_gpuvm_resv_obj(&uvmm->base);
+		drm_gem_object_get(nvbo->r_obj);
+	}
+
 	*pnvbo = nvbo;
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f74bf30bc683..8977a518de96 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1802,7 +1802,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 	int ret;
 
 	mutex_init(&uvmm->mutex);
-	dma_resv_init(&uvmm->resv);
 	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
 	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
 
@@ -1842,14 +1841,14 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 			    kernel_managed_addr, kernel_managed_size,
 			    NULL, 0, &cli->uvmm.vmm.vmm);
 	if (ret)
-		goto out_free_gpuva_mgr;
+		goto out_gpuvm_fini;
 
 	cli->uvmm.vmm.cli = cli;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
-out_free_gpuva_mgr:
+out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
@@ -1907,6 +1906,4 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
 	mutex_unlock(&cli->mutex);
-
-	dma_resv_fini(&uvmm->resv);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 06a0c36de392..22607270fae0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,7 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-	struct dma_resv resv;
 
 	bool disabled;
 };
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 07/12] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:30   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Introduce flags for struct drm_gpuvm, this required by subsequent
commits.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            |  3 +++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  2 +-
 include/drm/drm_gpuvm.h                | 16 ++++++++++++++++
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index af5805e4d7c9..53e2c406fb04 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -720,6 +720,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @flags: the &drm_gpuvm_flags for this GPUVM
  * @drm: the &drm_device this VM resides in
  * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
@@ -735,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  */
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       enum drm_gpuvm_flags flags,
 	       struct drm_device *drm,
 	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
@@ -745,6 +747,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
 	gpuvm->name = name ? name : "unknown";
+	gpuvm->flags = flags;
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 	gpuvm->r_obj = r_obj;
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 8977a518de96..f765e3835306 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1828,7 +1828,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
+	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index ff3377cbfe52..0c2e24155a93 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -184,6 +184,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
 	return va->flags & DRM_GPUVA_INVALIDATED;
 }
 
+/**
+ * enum drm_gpuvm_flags - flags for struct drm_gpuvm
+ */
+enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_USERBITS: user defined bits
+	 */
+	DRM_GPUVM_USERBITS = BIT(0),
+};
+
 /**
  * struct drm_gpuvm - DRM GPU VA Manager
  *
@@ -202,6 +212,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @flags: the &drm_gpuvm_flags of this GPUVM
+	 */
+	enum drm_gpuvm_flags flags;
+
 	/**
 	 * @drm: the &drm_device this VM lives in
 	 */
@@ -252,6 +267,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    enum drm_gpuvm_flags flags,
 		    struct drm_device *drm,
 		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 07/12] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Introduce flags for struct drm_gpuvm, this required by subsequent
commits.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            |  3 +++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  2 +-
 include/drm/drm_gpuvm.h                | 16 ++++++++++++++++
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index af5805e4d7c9..53e2c406fb04 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -720,6 +720,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @flags: the &drm_gpuvm_flags for this GPUVM
  * @drm: the &drm_device this VM resides in
  * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
@@ -735,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  */
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       enum drm_gpuvm_flags flags,
 	       struct drm_device *drm,
 	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
@@ -745,6 +747,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
 	gpuvm->name = name ? name : "unknown";
+	gpuvm->flags = flags;
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 	gpuvm->r_obj = r_obj;
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 8977a518de96..f765e3835306 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1828,7 +1828,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
+	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index ff3377cbfe52..0c2e24155a93 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -184,6 +184,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
 	return va->flags & DRM_GPUVA_INVALIDATED;
 }
 
+/**
+ * enum drm_gpuvm_flags - flags for struct drm_gpuvm
+ */
+enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_USERBITS: user defined bits
+	 */
+	DRM_GPUVM_USERBITS = BIT(0),
+};
+
 /**
  * struct drm_gpuvm - DRM GPU VA Manager
  *
@@ -202,6 +212,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @flags: the &drm_gpuvm_flags of this GPUVM
+	 */
+	enum drm_gpuvm_flags flags;
+
 	/**
 	 * @drm: the &drm_device this VM lives in
 	 */
@@ -252,6 +267,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    enum drm_gpuvm_flags flags,
 		    struct drm_device *drm,
 		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 07/12] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
@ 2023-11-01 23:30   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:30 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Introduce flags for struct drm_gpuvm, this required by subsequent
commits.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            |  3 +++
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  2 +-
 include/drm/drm_gpuvm.h                | 16 ++++++++++++++++
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index af5805e4d7c9..53e2c406fb04 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -720,6 +720,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  * drm_gpuvm_init() - initialize a &drm_gpuvm
  * @gpuvm: pointer to the &drm_gpuvm to initialize
  * @name: the name of the GPU VA space
+ * @flags: the &drm_gpuvm_flags for this GPUVM
  * @drm: the &drm_device this VM resides in
  * @r_obj: the resv &drm_gem_object providing the GPUVM's common &dma_resv
  * @start_offset: the start offset of the GPU VA space
@@ -735,6 +736,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_resv_object_alloc);
  */
 void
 drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+	       enum drm_gpuvm_flags flags,
 	       struct drm_device *drm,
 	       struct drm_gem_object *r_obj,
 	       u64 start_offset, u64 range,
@@ -745,6 +747,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
 	gpuvm->name = name ? name : "unknown";
+	gpuvm->flags = flags;
 	gpuvm->ops = ops;
 	gpuvm->drm = drm;
 	gpuvm->r_obj = r_obj;
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 8977a518de96..f765e3835306 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1828,7 +1828,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	drm_gpuvm_init(&uvmm->base, cli->name, drm, r_obj,
+	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
 		       kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index ff3377cbfe52..0c2e24155a93 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -184,6 +184,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
 	return va->flags & DRM_GPUVA_INVALIDATED;
 }
 
+/**
+ * enum drm_gpuvm_flags - flags for struct drm_gpuvm
+ */
+enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_USERBITS: user defined bits
+	 */
+	DRM_GPUVM_USERBITS = BIT(0),
+};
+
 /**
  * struct drm_gpuvm - DRM GPU VA Manager
  *
@@ -202,6 +212,11 @@ struct drm_gpuvm {
 	 */
 	const char *name;
 
+	/**
+	 * @flags: the &drm_gpuvm_flags of this GPUVM
+	 */
+	enum drm_gpuvm_flags flags;
+
 	/**
 	 * @drm: the &drm_device this VM lives in
 	 */
@@ -252,6 +267,7 @@ struct drm_gpuvm {
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+		    enum drm_gpuvm_flags flags,
 		    struct drm_device *drm,
 		    struct drm_gem_object *r_obj,
 		    u64 start_offset, u64 range,
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:31   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Allocate struct nouveau_uvmm separately in preparation for subsequent
commits introducing reference counting for struct drm_gpuvm.

While at it, get rid of nouveau_uvmm_init() as indirection of
nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_drm.c  |  5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h  | 10 ++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 63 +++++++++++++-------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  4 --
 4 files changed, 40 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 50589f982d1a..f603eaef1560 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -190,6 +190,8 @@ nouveau_cli_work_queue(struct nouveau_cli *cli, struct dma_fence *fence,
 static void
 nouveau_cli_fini(struct nouveau_cli *cli)
 {
+	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm_locked(cli);
+
 	/* All our channels are dead now, which means all the fences they
 	 * own are signalled, and all callback functions have been called.
 	 *
@@ -199,7 +201,8 @@ nouveau_cli_fini(struct nouveau_cli *cli)
 	WARN_ON(!list_empty(&cli->worker));
 
 	usif_client_fini(cli);
-	nouveau_uvmm_fini(&cli->uvmm);
+	if (uvmm)
+		nouveau_uvmm_fini(uvmm);
 	nouveau_sched_entity_fini(&cli->sched_entity);
 	nouveau_vmm_fini(&cli->svm);
 	nouveau_vmm_fini(&cli->vmm);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 3666a7403e47..e514110bf391 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -93,7 +93,10 @@ struct nouveau_cli {
 	struct nvif_mmu mmu;
 	struct nouveau_vmm vmm;
 	struct nouveau_vmm svm;
-	struct nouveau_uvmm uvmm;
+	struct {
+		struct nouveau_uvmm *ptr;
+		bool disabled;
+	} uvmm;
 
 	struct nouveau_sched_entity sched_entity;
 
@@ -121,10 +124,7 @@ struct nouveau_cli_work {
 static inline struct nouveau_uvmm *
 nouveau_cli_uvmm(struct nouveau_cli *cli)
 {
-	if (!cli || !cli->uvmm.vmm.cli)
-		return NULL;
-
-	return &cli->uvmm;
+	return cli ? cli->uvmm.ptr : NULL;
 }
 
 static inline struct nouveau_uvmm *
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f765e3835306..54be12c1272f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1636,18 +1636,6 @@ nouveau_uvmm_bind_job_init(struct nouveau_uvmm_bind_job **pjob,
 	return ret;
 }
 
-int
-nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
-			   void *data,
-			   struct drm_file *file_priv)
-{
-	struct nouveau_cli *cli = nouveau_cli(file_priv);
-	struct drm_nouveau_vm_init *init = data;
-
-	return nouveau_uvmm_init(&cli->uvmm, cli, init->kernel_managed_addr,
-				 init->kernel_managed_size);
-}
-
 static int
 nouveau_uvmm_vm_bind(struct nouveau_uvmm_bind_job_args *args)
 {
@@ -1793,17 +1781,25 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 }
 
 int
-nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		  u64 kernel_managed_addr, u64 kernel_managed_size)
+nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
+			   void *data,
+			   struct drm_file *file_priv)
 {
+	struct nouveau_uvmm *uvmm;
+	struct nouveau_cli *cli = nouveau_cli(file_priv);
 	struct drm_device *drm = cli->drm->dev;
 	struct drm_gem_object *r_obj;
-	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	struct drm_nouveau_vm_init *init = data;
+	u64 kernel_managed_end;
 	int ret;
 
-	mutex_init(&uvmm->mutex);
-	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
-	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+	if (check_add_overflow(init->kernel_managed_addr,
+			       init->kernel_managed_size,
+			       &kernel_managed_end))
+		return -EINVAL;
+
+	if (kernel_managed_end > NOUVEAU_VA_SPACE_END)
+		return -EINVAL;
 
 	mutex_lock(&cli->mutex);
 
@@ -1812,44 +1808,49 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	if (kernel_managed_end <= kernel_managed_addr) {
-		ret = -EINVAL;
-		goto out_unlock;
-	}
-
-	if (kernel_managed_end > NOUVEAU_VA_SPACE_END) {
-		ret = -EINVAL;
+	uvmm = kzalloc(sizeof(*uvmm), GFP_KERNEL);
+	if (!uvmm) {
+		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
 	r_obj = drm_gpuvm_resv_object_alloc(drm);
 	if (!r_obj) {
+		kfree(uvmm);
 		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
+	mutex_init(&uvmm->mutex);
+	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
+	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+
 	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
-		       kernel_managed_addr, kernel_managed_size,
+		       init->kernel_managed_addr,
+		       init->kernel_managed_size,
 		       NULL);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
-			    kernel_managed_addr, kernel_managed_size,
-			    NULL, 0, &cli->uvmm.vmm.vmm);
+			    init->kernel_managed_addr,
+			    init->kernel_managed_size,
+			    NULL, 0, &uvmm->vmm.vmm);
 	if (ret)
 		goto out_gpuvm_fini;
 
-	cli->uvmm.vmm.cli = cli;
+	uvmm->vmm.cli = cli;
+	cli->uvmm.ptr = uvmm;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
 out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1864,9 +1865,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	struct nouveau_sched_entity *entity = &cli->sched_entity;
 	struct drm_gpuva *va, *next;
 
-	if (!cli)
-		return;
-
 	rmb(); /* for list_empty to work without lock */
 	wait_event(entity->job.wq, list_empty(&entity->job.list.head));
 
@@ -1905,5 +1903,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 22607270fae0..f0a6d98ace4f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,8 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-
-	bool disabled;
 };
 
 struct nouveau_uvma_region {
@@ -78,8 +76,6 @@ struct nouveau_uvmm_bind_job_args {
 
 #define to_uvmm_bind_job(job) container_of((job), struct nouveau_uvmm_bind_job, base)
 
-int nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		      u64 kernel_managed_addr, u64 kernel_managed_size);
 void nouveau_uvmm_fini(struct nouveau_uvmm *uvmm);
 
 void nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbov, struct nouveau_mem *mem);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Allocate struct nouveau_uvmm separately in preparation for subsequent
commits introducing reference counting for struct drm_gpuvm.

While at it, get rid of nouveau_uvmm_init() as indirection of
nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_drm.c  |  5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h  | 10 ++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 63 +++++++++++++-------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  4 --
 4 files changed, 40 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 50589f982d1a..f603eaef1560 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -190,6 +190,8 @@ nouveau_cli_work_queue(struct nouveau_cli *cli, struct dma_fence *fence,
 static void
 nouveau_cli_fini(struct nouveau_cli *cli)
 {
+	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm_locked(cli);
+
 	/* All our channels are dead now, which means all the fences they
 	 * own are signalled, and all callback functions have been called.
 	 *
@@ -199,7 +201,8 @@ nouveau_cli_fini(struct nouveau_cli *cli)
 	WARN_ON(!list_empty(&cli->worker));
 
 	usif_client_fini(cli);
-	nouveau_uvmm_fini(&cli->uvmm);
+	if (uvmm)
+		nouveau_uvmm_fini(uvmm);
 	nouveau_sched_entity_fini(&cli->sched_entity);
 	nouveau_vmm_fini(&cli->svm);
 	nouveau_vmm_fini(&cli->vmm);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 3666a7403e47..e514110bf391 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -93,7 +93,10 @@ struct nouveau_cli {
 	struct nvif_mmu mmu;
 	struct nouveau_vmm vmm;
 	struct nouveau_vmm svm;
-	struct nouveau_uvmm uvmm;
+	struct {
+		struct nouveau_uvmm *ptr;
+		bool disabled;
+	} uvmm;
 
 	struct nouveau_sched_entity sched_entity;
 
@@ -121,10 +124,7 @@ struct nouveau_cli_work {
 static inline struct nouveau_uvmm *
 nouveau_cli_uvmm(struct nouveau_cli *cli)
 {
-	if (!cli || !cli->uvmm.vmm.cli)
-		return NULL;
-
-	return &cli->uvmm;
+	return cli ? cli->uvmm.ptr : NULL;
 }
 
 static inline struct nouveau_uvmm *
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f765e3835306..54be12c1272f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1636,18 +1636,6 @@ nouveau_uvmm_bind_job_init(struct nouveau_uvmm_bind_job **pjob,
 	return ret;
 }
 
-int
-nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
-			   void *data,
-			   struct drm_file *file_priv)
-{
-	struct nouveau_cli *cli = nouveau_cli(file_priv);
-	struct drm_nouveau_vm_init *init = data;
-
-	return nouveau_uvmm_init(&cli->uvmm, cli, init->kernel_managed_addr,
-				 init->kernel_managed_size);
-}
-
 static int
 nouveau_uvmm_vm_bind(struct nouveau_uvmm_bind_job_args *args)
 {
@@ -1793,17 +1781,25 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 }
 
 int
-nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		  u64 kernel_managed_addr, u64 kernel_managed_size)
+nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
+			   void *data,
+			   struct drm_file *file_priv)
 {
+	struct nouveau_uvmm *uvmm;
+	struct nouveau_cli *cli = nouveau_cli(file_priv);
 	struct drm_device *drm = cli->drm->dev;
 	struct drm_gem_object *r_obj;
-	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	struct drm_nouveau_vm_init *init = data;
+	u64 kernel_managed_end;
 	int ret;
 
-	mutex_init(&uvmm->mutex);
-	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
-	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+	if (check_add_overflow(init->kernel_managed_addr,
+			       init->kernel_managed_size,
+			       &kernel_managed_end))
+		return -EINVAL;
+
+	if (kernel_managed_end > NOUVEAU_VA_SPACE_END)
+		return -EINVAL;
 
 	mutex_lock(&cli->mutex);
 
@@ -1812,44 +1808,49 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	if (kernel_managed_end <= kernel_managed_addr) {
-		ret = -EINVAL;
-		goto out_unlock;
-	}
-
-	if (kernel_managed_end > NOUVEAU_VA_SPACE_END) {
-		ret = -EINVAL;
+	uvmm = kzalloc(sizeof(*uvmm), GFP_KERNEL);
+	if (!uvmm) {
+		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
 	r_obj = drm_gpuvm_resv_object_alloc(drm);
 	if (!r_obj) {
+		kfree(uvmm);
 		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
+	mutex_init(&uvmm->mutex);
+	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
+	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+
 	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
-		       kernel_managed_addr, kernel_managed_size,
+		       init->kernel_managed_addr,
+		       init->kernel_managed_size,
 		       NULL);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
-			    kernel_managed_addr, kernel_managed_size,
-			    NULL, 0, &cli->uvmm.vmm.vmm);
+			    init->kernel_managed_addr,
+			    init->kernel_managed_size,
+			    NULL, 0, &uvmm->vmm.vmm);
 	if (ret)
 		goto out_gpuvm_fini;
 
-	cli->uvmm.vmm.cli = cli;
+	uvmm->vmm.cli = cli;
+	cli->uvmm.ptr = uvmm;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
 out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1864,9 +1865,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	struct nouveau_sched_entity *entity = &cli->sched_entity;
 	struct drm_gpuva *va, *next;
 
-	if (!cli)
-		return;
-
 	rmb(); /* for list_empty to work without lock */
 	wait_event(entity->job.wq, list_empty(&entity->job.list.head));
 
@@ -1905,5 +1903,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 22607270fae0..f0a6d98ace4f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,8 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-
-	bool disabled;
 };
 
 struct nouveau_uvma_region {
@@ -78,8 +76,6 @@ struct nouveau_uvmm_bind_job_args {
 
 #define to_uvmm_bind_job(job) container_of((job), struct nouveau_uvmm_bind_job, base)
 
-int nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		      u64 kernel_managed_addr, u64 kernel_managed_size);
 void nouveau_uvmm_fini(struct nouveau_uvmm *uvmm);
 
 void nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbov, struct nouveau_mem *mem);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Allocate struct nouveau_uvmm separately in preparation for subsequent
commits introducing reference counting for struct drm_gpuvm.

While at it, get rid of nouveau_uvmm_init() as indirection of
nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_drm.c  |  5 +-
 drivers/gpu/drm/nouveau/nouveau_drv.h  | 10 ++--
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 63 +++++++++++++-------------
 drivers/gpu/drm/nouveau/nouveau_uvmm.h |  4 --
 4 files changed, 40 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c
index 50589f982d1a..f603eaef1560 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_drm.c
@@ -190,6 +190,8 @@ nouveau_cli_work_queue(struct nouveau_cli *cli, struct dma_fence *fence,
 static void
 nouveau_cli_fini(struct nouveau_cli *cli)
 {
+	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm_locked(cli);
+
 	/* All our channels are dead now, which means all the fences they
 	 * own are signalled, and all callback functions have been called.
 	 *
@@ -199,7 +201,8 @@ nouveau_cli_fini(struct nouveau_cli *cli)
 	WARN_ON(!list_empty(&cli->worker));
 
 	usif_client_fini(cli);
-	nouveau_uvmm_fini(&cli->uvmm);
+	if (uvmm)
+		nouveau_uvmm_fini(uvmm);
 	nouveau_sched_entity_fini(&cli->sched_entity);
 	nouveau_vmm_fini(&cli->svm);
 	nouveau_vmm_fini(&cli->vmm);
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 3666a7403e47..e514110bf391 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -93,7 +93,10 @@ struct nouveau_cli {
 	struct nvif_mmu mmu;
 	struct nouveau_vmm vmm;
 	struct nouveau_vmm svm;
-	struct nouveau_uvmm uvmm;
+	struct {
+		struct nouveau_uvmm *ptr;
+		bool disabled;
+	} uvmm;
 
 	struct nouveau_sched_entity sched_entity;
 
@@ -121,10 +124,7 @@ struct nouveau_cli_work {
 static inline struct nouveau_uvmm *
 nouveau_cli_uvmm(struct nouveau_cli *cli)
 {
-	if (!cli || !cli->uvmm.vmm.cli)
-		return NULL;
-
-	return &cli->uvmm;
+	return cli ? cli->uvmm.ptr : NULL;
 }
 
 static inline struct nouveau_uvmm *
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f765e3835306..54be12c1272f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1636,18 +1636,6 @@ nouveau_uvmm_bind_job_init(struct nouveau_uvmm_bind_job **pjob,
 	return ret;
 }
 
-int
-nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
-			   void *data,
-			   struct drm_file *file_priv)
-{
-	struct nouveau_cli *cli = nouveau_cli(file_priv);
-	struct drm_nouveau_vm_init *init = data;
-
-	return nouveau_uvmm_init(&cli->uvmm, cli, init->kernel_managed_addr,
-				 init->kernel_managed_size);
-}
-
 static int
 nouveau_uvmm_vm_bind(struct nouveau_uvmm_bind_job_args *args)
 {
@@ -1793,17 +1781,25 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 }
 
 int
-nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		  u64 kernel_managed_addr, u64 kernel_managed_size)
+nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
+			   void *data,
+			   struct drm_file *file_priv)
 {
+	struct nouveau_uvmm *uvmm;
+	struct nouveau_cli *cli = nouveau_cli(file_priv);
 	struct drm_device *drm = cli->drm->dev;
 	struct drm_gem_object *r_obj;
-	u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+	struct drm_nouveau_vm_init *init = data;
+	u64 kernel_managed_end;
 	int ret;
 
-	mutex_init(&uvmm->mutex);
-	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
-	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+	if (check_add_overflow(init->kernel_managed_addr,
+			       init->kernel_managed_size,
+			       &kernel_managed_end))
+		return -EINVAL;
+
+	if (kernel_managed_end > NOUVEAU_VA_SPACE_END)
+		return -EINVAL;
 
 	mutex_lock(&cli->mutex);
 
@@ -1812,44 +1808,49 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
 		goto out_unlock;
 	}
 
-	if (kernel_managed_end <= kernel_managed_addr) {
-		ret = -EINVAL;
-		goto out_unlock;
-	}
-
-	if (kernel_managed_end > NOUVEAU_VA_SPACE_END) {
-		ret = -EINVAL;
+	uvmm = kzalloc(sizeof(*uvmm), GFP_KERNEL);
+	if (!uvmm) {
+		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
 	r_obj = drm_gpuvm_resv_object_alloc(drm);
 	if (!r_obj) {
+		kfree(uvmm);
 		ret = -ENOMEM;
 		goto out_unlock;
 	}
 
+	mutex_init(&uvmm->mutex);
+	mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
+	mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);
+
 	drm_gpuvm_init(&uvmm->base, cli->name, 0, drm, r_obj,
 		       NOUVEAU_VA_SPACE_START,
 		       NOUVEAU_VA_SPACE_END,
-		       kernel_managed_addr, kernel_managed_size,
+		       init->kernel_managed_addr,
+		       init->kernel_managed_size,
 		       NULL);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
 	ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
 			    cli->vmm.vmm.object.oclass, RAW,
-			    kernel_managed_addr, kernel_managed_size,
-			    NULL, 0, &cli->uvmm.vmm.vmm);
+			    init->kernel_managed_addr,
+			    init->kernel_managed_size,
+			    NULL, 0, &uvmm->vmm.vmm);
 	if (ret)
 		goto out_gpuvm_fini;
 
-	cli->uvmm.vmm.cli = cli;
+	uvmm->vmm.cli = cli;
+	cli->uvmm.ptr = uvmm;
 	mutex_unlock(&cli->mutex);
 
 	return 0;
 
 out_gpuvm_fini:
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1864,9 +1865,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	struct nouveau_sched_entity *entity = &cli->sched_entity;
 	struct drm_gpuva *va, *next;
 
-	if (!cli)
-		return;
-
 	rmb(); /* for list_empty to work without lock */
 	wait_event(entity->job.wq, list_empty(&entity->job.list.head));
 
@@ -1905,5 +1903,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
 	drm_gpuvm_destroy(&uvmm->base);
+	kfree(uvmm);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index 22607270fae0..f0a6d98ace4f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,8 +12,6 @@ struct nouveau_uvmm {
 	struct nouveau_vmm vmm;
 	struct maple_tree region_mt;
 	struct mutex mutex;
-
-	bool disabled;
 };
 
 struct nouveau_uvma_region {
@@ -78,8 +76,6 @@ struct nouveau_uvmm_bind_job_args {
 
 #define to_uvmm_bind_job(job) container_of((job), struct nouveau_uvmm_bind_job, base)
 
-int nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
-		      u64 kernel_managed_addr, u64 kernel_managed_size);
 void nouveau_uvmm_fini(struct nouveau_uvmm *uvmm);
 
 void nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbov, struct nouveau_mem *mem);
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:31   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Implement reference counting for struct drm_gpuvm.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
 include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
 3 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 53e2c406fb04..6a88eafc5229 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	kref_init(&gpuvm->kref);
+
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->flags = flags;
 	gpuvm->ops = ops;
@@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_init);
 
-/**
- * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
- * @gpuvm: pointer to the &drm_gpuvm to clean up
- *
- * Note that it is a bug to call this function on a manager that still
- * holds GPU VA mappings.
- */
-void
-drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
+static void
+drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 {
 	gpuvm->name = NULL;
 
@@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_gem_object_put(gpuvm->r_obj);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
+
+static void
+drm_gpuvm_free(struct kref *kref)
+{
+	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
+
+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;
+
+	drm_gpuvm_fini(gpuvm);
+
+	gpuvm->ops->vm_free(gpuvm);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to release the reference of
+ *
+ * This releases a reference to @gpuvm.
+ */
+void
+drm_gpuvm_put(struct drm_gpuvm *gpuvm)
+{
+	if (gpuvm)
+		kref_put(&gpuvm->kref, drm_gpuvm_free);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
@@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
 		return -EINVAL;
 
-	return __drm_gpuva_insert(gpuvm, va);
+	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_insert);
 
@@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	}
 
 	__drm_gpuva_remove(va);
+	drm_gpuvm_put(va->vm);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 54be12c1272f..cb2f06565c46 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 	}
 }
 
+static void
+nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
+{
+	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
+
+	kfree(uvmm);
+}
+
+static const struct drm_gpuvm_ops gpuvm_ops = {
+	.vm_free = nouveau_uvmm_free,
+};
+
 int
 nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 			   void *data,
@@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 		       NOUVEAU_VA_SPACE_END,
 		       init->kernel_managed_addr,
 		       init->kernel_managed_size,
-		       NULL);
+		       &gpuvm_ops);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
@@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 	return 0;
 
 out_gpuvm_fini:
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 0c2e24155a93..4e6e1fd3485a 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -247,6 +247,11 @@ struct drm_gpuvm {
 		struct list_head list;
 	} rb;
 
+	/**
+	 * @kref: reference count of this object
+	 */
+	struct kref kref;
+
 	/**
 	 * @kernel_alloc_node:
 	 *
@@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
+
+/**
+ * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to acquire the reference of
+ *
+ * This function acquires an additional reference to @gpuvm. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm *
+drm_gpuvm_get(struct drm_gpuvm *gpuvm)
+{
+	kref_get(&gpuvm->kref);
+
+	return gpuvm;
+}
+
+void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
 
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
@@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
  * operations to drivers.
  */
 struct drm_gpuvm_ops {
+	/**
+	 * @vm_free: called when the last reference of a struct drm_gpuvm is
+	 * dropped
+	 *
+	 * This callback is mandatory.
+	 */
+	void (*vm_free)(struct drm_gpuvm *gpuvm);
+
 	/**
 	 * @op_alloc: called when the &drm_gpuvm allocates
 	 * a struct drm_gpuva_op
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Implement reference counting for struct drm_gpuvm.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
 include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
 3 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 53e2c406fb04..6a88eafc5229 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	kref_init(&gpuvm->kref);
+
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->flags = flags;
 	gpuvm->ops = ops;
@@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_init);
 
-/**
- * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
- * @gpuvm: pointer to the &drm_gpuvm to clean up
- *
- * Note that it is a bug to call this function on a manager that still
- * holds GPU VA mappings.
- */
-void
-drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
+static void
+drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 {
 	gpuvm->name = NULL;
 
@@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_gem_object_put(gpuvm->r_obj);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
+
+static void
+drm_gpuvm_free(struct kref *kref)
+{
+	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
+
+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;
+
+	drm_gpuvm_fini(gpuvm);
+
+	gpuvm->ops->vm_free(gpuvm);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to release the reference of
+ *
+ * This releases a reference to @gpuvm.
+ */
+void
+drm_gpuvm_put(struct drm_gpuvm *gpuvm)
+{
+	if (gpuvm)
+		kref_put(&gpuvm->kref, drm_gpuvm_free);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
@@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
 		return -EINVAL;
 
-	return __drm_gpuva_insert(gpuvm, va);
+	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_insert);
 
@@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	}
 
 	__drm_gpuva_remove(va);
+	drm_gpuvm_put(va->vm);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 54be12c1272f..cb2f06565c46 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 	}
 }
 
+static void
+nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
+{
+	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
+
+	kfree(uvmm);
+}
+
+static const struct drm_gpuvm_ops gpuvm_ops = {
+	.vm_free = nouveau_uvmm_free,
+};
+
 int
 nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 			   void *data,
@@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 		       NOUVEAU_VA_SPACE_END,
 		       init->kernel_managed_addr,
 		       init->kernel_managed_size,
-		       NULL);
+		       &gpuvm_ops);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
@@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 	return 0;
 
 out_gpuvm_fini:
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 0c2e24155a93..4e6e1fd3485a 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -247,6 +247,11 @@ struct drm_gpuvm {
 		struct list_head list;
 	} rb;
 
+	/**
+	 * @kref: reference count of this object
+	 */
+	struct kref kref;
+
 	/**
 	 * @kernel_alloc_node:
 	 *
@@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
+
+/**
+ * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to acquire the reference of
+ *
+ * This function acquires an additional reference to @gpuvm. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm *
+drm_gpuvm_get(struct drm_gpuvm *gpuvm)
+{
+	kref_get(&gpuvm->kref);
+
+	return gpuvm;
+}
+
+void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
 
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
@@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
  * operations to drivers.
  */
 struct drm_gpuvm_ops {
+	/**
+	 * @vm_free: called when the last reference of a struct drm_gpuvm is
+	 * dropped
+	 *
+	 * This callback is mandatory.
+	 */
+	void (*vm_free)(struct drm_gpuvm *gpuvm);
+
 	/**
 	 * @op_alloc: called when the &drm_gpuvm allocates
 	 * a struct drm_gpuva_op
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Implement reference counting for struct drm_gpuvm.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
 include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
 3 files changed, 78 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 53e2c406fb04..6a88eafc5229 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	kref_init(&gpuvm->kref);
+
 	gpuvm->name = name ? name : "unknown";
 	gpuvm->flags = flags;
 	gpuvm->ops = ops;
@@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_init);
 
-/**
- * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
- * @gpuvm: pointer to the &drm_gpuvm to clean up
- *
- * Note that it is a bug to call this function on a manager that still
- * holds GPU VA mappings.
- */
-void
-drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
+static void
+drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 {
 	gpuvm->name = NULL;
 
@@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
 	drm_gem_object_put(gpuvm->r_obj);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
+
+static void
+drm_gpuvm_free(struct kref *kref)
+{
+	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
+
+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;
+
+	drm_gpuvm_fini(gpuvm);
+
+	gpuvm->ops->vm_free(gpuvm);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to release the reference of
+ *
+ * This releases a reference to @gpuvm.
+ */
+void
+drm_gpuvm_put(struct drm_gpuvm *gpuvm)
+{
+	if (gpuvm)
+		kref_put(&gpuvm->kref, drm_gpuvm_free);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
@@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
 		return -EINVAL;
 
-	return __drm_gpuva_insert(gpuvm, va);
+	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_insert);
 
@@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
 	}
 
 	__drm_gpuva_remove(va);
+	drm_gpuvm_put(va->vm);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 54be12c1272f..cb2f06565c46 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 	}
 }
 
+static void
+nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
+{
+	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
+
+	kfree(uvmm);
+}
+
+static const struct drm_gpuvm_ops gpuvm_ops = {
+	.vm_free = nouveau_uvmm_free,
+};
+
 int
 nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 			   void *data,
@@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 		       NOUVEAU_VA_SPACE_END,
 		       init->kernel_managed_addr,
 		       init->kernel_managed_size,
-		       NULL);
+		       &gpuvm_ops);
 	/* GPUVM takes care from here on. */
 	drm_gem_object_put(r_obj);
 
@@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
 	return 0;
 
 out_gpuvm_fini:
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 out_unlock:
 	mutex_unlock(&cli->mutex);
 	return ret;
@@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 
 	mutex_lock(&cli->mutex);
 	nouveau_vmm_fini(&uvmm->vmm);
-	drm_gpuvm_destroy(&uvmm->base);
-	kfree(uvmm);
+	drm_gpuvm_put(&uvmm->base);
 	mutex_unlock(&cli->mutex);
 }
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 0c2e24155a93..4e6e1fd3485a 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -247,6 +247,11 @@ struct drm_gpuvm {
 		struct list_head list;
 	} rb;
 
+	/**
+	 * @kref: reference count of this object
+	 */
+	struct kref kref;
+
 	/**
 	 * @kernel_alloc_node:
 	 *
@@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 		    u64 start_offset, u64 range,
 		    u64 reserve_offset, u64 reserve_range,
 		    const struct drm_gpuvm_ops *ops);
-void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
+
+/**
+ * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to acquire the reference of
+ *
+ * This function acquires an additional reference to @gpuvm. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm *
+drm_gpuvm_get(struct drm_gpuvm *gpuvm)
+{
+	kref_get(&gpuvm->kref);
+
+	return gpuvm;
+}
+
+void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
 
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
@@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
  * operations to drivers.
  */
 struct drm_gpuvm_ops {
+	/**
+	 * @vm_free: called when the last reference of a struct drm_gpuvm is
+	 * dropped
+	 *
+	 * This callback is mandatory.
+	 */
+	void (*vm_free)(struct drm_gpuvm *gpuvm);
+
 	/**
 	 * @op_alloc: called when the &drm_gpuvm allocates
 	 * a struct drm_gpuva_op
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:31   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Add an abstraction layer between the drm_gpuva mappings of a particular
drm_gem_object and this GEM object itself. The abstraction represents a
combination of a drm_gem_object and drm_gpuvm. The drm_gem_object holds
a list of drm_gpuvm_bo structures (the structure representing this
abstraction), while each drm_gpuvm_bo contains list of mappings of this
GEM object.

This has multiple advantages:

1) We can use the drm_gpuvm_bo structure to attach it to various lists
   of the drm_gpuvm. This is useful for tracking external and evicted
   objects per VM, which is introduced in subsequent patches.

2) Finding mappings of a certain drm_gem_object mapped in a certain
   drm_gpuvm becomes much cheaper.

3) Drivers can derive and extend the structure to easily represent
   driver specific states of a BO for a certain GPUVM.

The idea of this abstraction was taken from amdgpu, hence the credit for
this idea goes to the developers of amdgpu.

Cc: Christian König <christian.koenig@amd.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++----
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
 include/drm/drm_gem.h                  |  32 +--
 include/drm/drm_gpuvm.h                | 185 +++++++++++++-
 4 files changed, 530 insertions(+), 86 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 6a88eafc5229..2c8fdefb19f0 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -70,6 +70,18 @@
  * &drm_gem_object, such as the &drm_gem_object containing the root page table,
  * but it can also be a 'dummy' object, which can be allocated with
  * drm_gpuvm_resv_object_alloc().
+ *
+ * In order to connect a struct drm_gpuva its backing &drm_gem_object each
+ * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and each
+ * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
+ *
+ * A &drm_gpuvm_bo is an abstraction that represents a combination of a
+ * &drm_gpuvm and a &drm_gem_object. Every such combination should be unique.
+ * This is ensured by the API through drm_gpuvm_bo_obtain() and
+ * drm_gpuvm_bo_obtain_prealloc() which first look into the corresponding
+ * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
+ * particular combination. If not existent a new instance is created and linked
+ * to the &drm_gem_object.
  */
 
 /**
@@ -395,21 +407,28 @@
 /**
  * DOC: Locking
  *
- * Generally, the GPU VA manager does not take care of locking itself, it is
- * the drivers responsibility to take care about locking. Drivers might want to
- * protect the following operations: inserting, removing and iterating
- * &drm_gpuva objects as well as generating all kinds of operations, such as
- * split / merge or prefetch.
- *
- * The GPU VA manager also does not take care of the locking of the backing
- * &drm_gem_object buffers GPU VA lists by itself; drivers are responsible to
- * enforce mutual exclusion using either the GEMs dma_resv lock or alternatively
- * a driver specific external lock. For the latter see also
- * drm_gem_gpuva_set_lock().
- *
- * However, the GPU VA manager contains lockdep checks to ensure callers of its
- * API hold the corresponding lock whenever the &drm_gem_objects GPU VA list is
- * accessed by functions such as drm_gpuva_link() or drm_gpuva_unlink().
+ * In terms of managing &drm_gpuva entries DRM GPUVM does not take care of
+ * locking itself, it is the drivers responsibility to take care about locking.
+ * Drivers might want to protect the following operations: inserting, removing
+ * and iterating &drm_gpuva objects as well as generating all kinds of
+ * operations, such as split / merge or prefetch.
+ *
+ * DRM GPUVM also does not take care of the locking of the backing
+ * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo abstractions by
+ * itself; drivers are responsible to enforce mutual exclusion using either the
+ * GEMs dma_resv lock or alternatively a driver specific external lock. For the
+ * latter see also drm_gem_gpuva_set_lock().
+ *
+ * However, DRM GPUVM contains lockdep checks to ensure callers of its API hold
+ * the corresponding lock whenever the &drm_gem_objects GPU VA list is accessed
+ * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but also
+ * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
+ *
+ * The latter is required since on creation and destruction of a &drm_gpuvm_bo
+ * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects gpuva list.
+ * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
+ * &drm_gem_object must be able to observe previous creations and destructions
+ * of &drm_gpuvm_bos in order to keep instances unique.
  */
 
 /**
@@ -439,6 +458,7 @@
  *	{
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op
+ *		struct drm_gpuvm_bo *vm_bo;
  *
  *		driver_lock_va_space();
  *		ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
@@ -446,6 +466,10 @@
  *		if (IS_ERR(ops))
  *			return PTR_ERR(ops);
  *
+ *		vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
+ *		if (IS_ERR(vm_bo))
+ *			return PTR_ERR(vm_bo);
+ *
  *		drm_gpuva_for_each_op(op, ops) {
  *			struct drm_gpuva *va;
  *
@@ -458,7 +482,7 @@
  *
  *				driver_vm_map();
  *				drm_gpuva_map(gpuvm, va, &op->map);
- *				drm_gpuva_link(va);
+ *				drm_gpuva_link(va, vm_bo);
  *
  *				break;
  *			case DRM_GPUVA_OP_REMAP: {
@@ -485,11 +509,11 @@
  *				driver_vm_remap();
  *				drm_gpuva_remap(prev, next, &op->remap);
  *
- *				drm_gpuva_unlink(va);
  *				if (prev)
- *					drm_gpuva_link(prev);
+ *					drm_gpuva_link(prev, va->vm_bo);
  *				if (next)
- *					drm_gpuva_link(next);
+ *					drm_gpuva_link(next, va->vm_bo);
+ *				drm_gpuva_unlink(va);
  *
  *				break;
  *			}
@@ -505,6 +529,7 @@
  *				break;
  *			}
  *		}
+ *		drm_gpuvm_bo_put(vm_bo);
  *		driver_unlock_va_space();
  *
  *		return 0;
@@ -514,6 +539,7 @@
  *
  *	struct driver_context {
  *		struct drm_gpuvm *gpuvm;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva *new_va;
  *		struct drm_gpuva *prev_va;
  *		struct drm_gpuva *next_va;
@@ -534,6 +560,7 @@
  *				  struct drm_gem_object *obj, u64 offset)
  *	{
  *		struct driver_context ctx;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op;
  *		int ret = 0;
@@ -543,16 +570,23 @@
  *		ctx.new_va = kzalloc(sizeof(*ctx.new_va), GFP_KERNEL);
  *		ctx.prev_va = kzalloc(sizeof(*ctx.prev_va), GFP_KERNEL);
  *		ctx.next_va = kzalloc(sizeof(*ctx.next_va), GFP_KERNEL);
- *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
+ *		ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+ *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va || !vm_bo) {
  *			ret = -ENOMEM;
  *			goto out;
  *		}
  *
+ *		// Typically protected with a driver specific GEM gpuva lock
+ *		// used in the fence signaling path for drm_gpuva_link() and
+ *		// drm_gpuva_unlink(), hence pre-allocate.
+ *		ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
+ *
  *		driver_lock_va_space();
  *		ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj, offset);
  *		driver_unlock_va_space();
  *
  *	out:
+ *		drm_gpuvm_bo_put(ctx.vm_bo);
  *		kfree(ctx.new_va);
  *		kfree(ctx.prev_va);
  *		kfree(ctx.next_va);
@@ -565,7 +599,7 @@
  *
  *		drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
  *
- *		drm_gpuva_link(ctx->new_va);
+ *		drm_gpuva_link(ctx->new_va, ctx->vm_bo);
  *
  *		// prevent the new GPUVA from being freed in
  *		// driver_mapping_create()
@@ -577,22 +611,23 @@
  *	int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
  *	{
  *		struct driver_context *ctx = __ctx;
+ *		struct drm_gpuva *va = op->remap.unmap->va;
  *
  *		drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op->remap);
  *
- *		drm_gpuva_unlink(op->remap.unmap->va);
- *		kfree(op->remap.unmap->va);
- *
  *		if (op->remap.prev) {
- *			drm_gpuva_link(ctx->prev_va);
+ *			drm_gpuva_link(ctx->prev_va, va->vm_bo);
  *			ctx->prev_va = NULL;
  *		}
  *
  *		if (op->remap.next) {
- *			drm_gpuva_link(ctx->next_va);
+ *			drm_gpuva_link(ctx->next_va, va->vm_bo);
  *			ctx->next_va = NULL;
  *		}
  *
+ *		drm_gpuva_unlink(va);
+ *		kfree(va);
+ *
  *		return 0;
  *	}
  *
@@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+/**
+ * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * If provided by the driver, this function uses the &drm_gpuvm_ops
+ * vm_bo_alloc() callback to allocate.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+
+	if (ops && ops->vm_bo_alloc)
+		vm_bo = ops->vm_bo_alloc();
+	else
+		vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
+
+	if (unlikely(!vm_bo))
+		return NULL;
+
+	vm_bo->vm = drm_gpuvm_get(gpuvm);
+	vm_bo->obj = obj;
+	drm_gem_object_get(obj);
+
+	kref_init(&vm_bo->kref);
+	INIT_LIST_HEAD(&vm_bo->list.gpuva);
+	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
+
+static void
+drm_gpuvm_bo_destroy(struct kref *kref)
+{
+	struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
+						  kref);
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_del(&vm_bo->list.entry.gem);
+
+	if (ops && ops->vm_bo_free)
+		ops->vm_bo_free(vm_bo);
+	else
+		kfree(vm_bo);
+
+	drm_gpuvm_put(gpuvm);
+	drm_gem_object_put(obj);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to release the reference of
+ *
+ * This releases a reference to @vm_bo.
+ *
+ * If the reference count drops to zero, the &gpuvm_bo is destroyed, which
+ * includes removing it from the GEMs gpuva list. Hence, if a call to this
+ * function can potentially let the reference count to zero the caller must
+ * hold the dma-resv or driver specific GEM gpuva lock.
+ */
+void
+drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
+{
+	if (vm_bo)
+		kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
+
+static struct drm_gpuvm_bo *
+__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		if (vm_bo->vm == gpuvm)
+			return vm_bo;
+
+	return NULL;
+}
+
+/**
+ * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
+ * &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
+
+	return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
+
+/**
+ * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo for the
+ * given &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly. If not found, allocates a new
+ * &drm_gpuvm_bo.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo)
+		return vm_bo;
+
+	vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+	if (!vm_bo)
+		return ERR_PTR(-ENOMEM);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
+
+/**
+ * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the &drm_gpuvm_bo
+ * for the given &drm_gpuvm and &drm_gem_object
+ * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo reference
+ * count is decreased. If not found @__vm_bo is returned without further
+ * increase of the reference count.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no existing
+ * &drm_gpuvm_bo was found
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
+{
+	struct drm_gpuvm *gpuvm = __vm_bo->vm;
+	struct drm_gem_object *obj = __vm_bo->obj;
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo) {
+		drm_gpuvm_bo_put(__vm_bo);
+		return vm_bo;
+	}
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return __vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
@@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 /**
  * drm_gpuva_link() - link a &drm_gpuva
  * @va: the &drm_gpuva to link
+ * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
  *
- * This adds the given &va to the GPU VA list of the &drm_gem_object it is
- * associated with.
+ * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo and the
+ * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
+ *
+ * For every &drm_gpuva entry added to the &drm_gpuvm_bo an additional
+ * reference of the latter is taken.
  *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
-drm_gpuva_link(struct drm_gpuva *va)
+drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(!obj))
 		return;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
 
-	list_add_tail(&va->gem.entry, &obj->gpuva.list);
+	va->vm_bo = drm_gpuvm_bo_get(vm_bo);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_link);
 
@@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
  * This removes the given &va from the GPU VA list of the &drm_gem_object it is
  * associated with.
  *
+ * This removes the given &va from the GPU VA list of the &drm_gpuvm_bo and
+ * the &drm_gpuvm_bo from the &drm_gem_object it is associated with in case
+ * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
+ *
+ * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a reference of
+ * the latter is dropped.
+ *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
 drm_gpuva_unlink(struct drm_gpuva *va)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm_bo *vm_bo = va->vm_bo;
 
 	if (unlikely(!obj))
 		return;
 
 	drm_gem_gpuva_assert_lock_held(obj);
-
 	list_del_init(&va->gem.entry);
+
+	va->vm_bo = NULL;
+	drm_gpuvm_bo_put(vm_bo);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
 
@@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
 		struct drm_gpuva *next,
 		struct drm_gpuva_op_remap *op)
 {
-	struct drm_gpuva *curr = op->unmap->va;
-	struct drm_gpuvm *gpuvm = curr->vm;
+	struct drm_gpuva *va = op->unmap->va;
+	struct drm_gpuvm *gpuvm = va->vm;
 
-	drm_gpuva_remove(curr);
+	drm_gpuva_remove(va);
 
 	if (op->prev) {
 		drm_gpuva_init_from_op(prev, op->prev);
@@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
 
 /**
- * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
- * @gpuvm: the &drm_gpuvm representing the GPU VA space
- * @obj: the &drm_gem_object to unmap
+ * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
+ * @vm_bo: the &drm_gpuvm_bo abstraction
  *
  * This function creates a list of operations to perform unmapping for every
  * GPUVA attached to a GEM.
@@ -1754,15 +1997,14 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
  * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
  */
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj)
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gpuva_ops *ops;
 	struct drm_gpuva_op *op;
 	struct drm_gpuva *va;
 	int ret;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_gpuva_assert_lock_held(vm_bo->obj);
 
 	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
 	if (!ops)
@@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 
 	INIT_LIST_HEAD(&ops->list);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		op = gpuva_op_alloc(gpuvm);
+	drm_gpuvm_bo_for_each_va(va, vm_bo) {
+		op = gpuva_op_alloc(vm_bo->vm);
 		if (!op) {
 			ret = -ENOMEM;
 			goto err_free_ops;
@@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 	return ops;
 
 err_free_ops:
-	drm_gpuva_ops_free(gpuvm, ops);
+	drm_gpuva_ops_free(vm_bo->vm, ops);
 	return ERR_PTR(ret);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
 
 /**
  * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index cb2f06565c46..eda7bb8624f1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -62,6 +62,8 @@ struct bind_job_op {
 	enum vm_bind_op op;
 	u32 flags;
 
+	struct drm_gpuvm_bo *vm_bo;
+
 	struct {
 		u64 addr;
 		u64 range;
@@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
 }
 
 static void
-bind_link_gpuvas(struct drm_gpuva_ops *ops, struct nouveau_uvma_prealloc *new)
+bind_link_gpuvas(struct bind_job_op *bop)
 {
+	struct nouveau_uvma_prealloc *new = &bop->new;
+	struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
+	struct drm_gpuva_ops *ops = bop->ops;
 	struct drm_gpuva_op *op;
 
 	drm_gpuva_for_each_op(op, ops) {
 		switch (op->op) {
 		case DRM_GPUVA_OP_MAP:
-			drm_gpuva_link(&new->map->va);
+			drm_gpuva_link(&new->map->va, vm_bo);
 			break;
-		case DRM_GPUVA_OP_REMAP:
+		case DRM_GPUVA_OP_REMAP: {
+			struct drm_gpuva *va = op->remap.unmap->va;
+
 			if (op->remap.prev)
-				drm_gpuva_link(&new->prev->va);
+				drm_gpuva_link(&new->prev->va, va->vm_bo);
 			if (op->remap.next)
-				drm_gpuva_link(&new->next->va);
-			drm_gpuva_unlink(op->remap.unmap->va);
+				drm_gpuva_link(&new->next->va, va->vm_bo);
+			drm_gpuva_unlink(va);
 			break;
+		}
 		case DRM_GPUVA_OP_UNMAP:
 			drm_gpuva_unlink(op->unmap.va);
 			break;
@@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 
 	list_for_each_op(op, &bind_job->ops) {
 		if (op->op == OP_MAP) {
-			op->gem.obj = drm_gem_object_lookup(job->file_priv,
-							    op->gem.handle);
-			if (!op->gem.obj)
+			struct drm_gem_object *obj = op->gem.obj =
+				drm_gem_object_lookup(job->file_priv,
+						      op->gem.handle);
+			if (!obj)
 				return -ENOENT;
+
+			dma_resv_lock(obj->resv, NULL);
+			op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base, obj);
+			dma_resv_unlock(obj->resv);
+			if (IS_ERR(op->vm_bo))
+				return PTR_ERR(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		case OP_UNMAP_SPARSE:
 		case OP_MAP:
 		case OP_UNMAP:
-			bind_link_gpuvas(op->ops, &op->new);
+			bind_link_gpuvas(op);
 			break;
 		default:
 			break;
@@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct work_struct *work)
 		if (!IS_ERR_OR_NULL(op->ops))
 			drm_gpuva_ops_free(&uvmm->base, op->ops);
 
+		if (!IS_ERR_OR_NULL(op->vm_bo)) {
+			dma_resv_lock(obj->resv, NULL);
+			drm_gpuvm_bo_put(op->vm_bo);
+			dma_resv_unlock(obj->resv);
+		}
+
 		if (obj)
 			drm_gem_object_put(obj);
 	}
@@ -1752,15 +1773,18 @@ void
 nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem *mem)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_map(uvma, mem);
-		drm_gpuva_invalidate(va, false);
+			nouveau_uvma_map(uvma, mem);
+			drm_gpuva_invalidate(va, false);
+		}
 	}
 }
 
@@ -1768,15 +1792,18 @@ void
 nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_unmap(uvma);
-		drm_gpuva_invalidate(va, true);
+			nouveau_uvma_unmap(uvma);
+			drm_gpuva_invalidate(va, true);
+		}
 	}
 }
 
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index 16364487fde9..369505447acd 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
  * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
  * @obj: the &drm_gem_object
  *
- * This initializes the &drm_gem_object's &drm_gpuva list.
+ * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
  *
  * Calling this function is only necessary for drivers intending to support the
  * &drm_driver_feature DRIVER_GEM_GPUVA.
@@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct drm_gem_object *obj)
 }
 
 /**
- * drm_gem_for_each_gpuva() - iternator to walk over a list of gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bo structure to assign to in each iteration step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
- * &drm_gpuva_manager.
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
+ * &drm_gem_object.
  */
-#define drm_gem_for_each_gpuva(entry__, obj__) \
-	list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
+	list_for_each_entry(entry__, &(obj__)->gpuva.list, list.entry.gem)
 
 /**
- * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a list of
- * gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @next__: &next &drm_gpuva to store the next step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a list of
+ * &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bostructure to assign to in each iteration step
+ * @next__: &next &drm_gpuvm_bo to store the next step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
  * &drm_gem_object. It is implemented with list_for_each_entry_safe(), hence
  * it is save against removal of elements.
  */
-#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
-	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
+	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, list.entry.gem)
 
 #endif /* __DRM_GEM_H__ */
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 4e6e1fd3485a..b12fb22b0e22 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -25,6 +25,7 @@
  * OTHER DEALINGS IN THE SOFTWARE.
  */
 
+#include <linux/dma-resv.h>
 #include <linux/list.h>
 #include <linux/rbtree.h>
 #include <linux/types.h>
@@ -33,6 +34,7 @@
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
+struct drm_gpuvm_bo;
 struct drm_gpuvm_ops;
 
 /**
@@ -73,6 +75,12 @@ struct drm_gpuva {
 	 */
 	struct drm_gpuvm *vm;
 
+	/**
+	 * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
+	 * &drm_gem_object
+	 */
+	struct drm_gpuvm_bo *vm_bo;
+
 	/**
 	 * @flags: the &drm_gpuva_flags for this mapping
 	 */
@@ -108,7 +116,7 @@ struct drm_gpuva {
 		struct drm_gem_object *obj;
 
 		/**
-		 * @entry: the &list_head to attach this object to a &drm_gem_object
+		 * @entry: the &list_head to attach this object to a &drm_gpuvm_bo
 		 */
 		struct list_head entry;
 	} gem;
@@ -141,7 +149,7 @@ struct drm_gpuva {
 int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
 void drm_gpuva_remove(struct drm_gpuva *va);
 
-void drm_gpuva_link(struct drm_gpuva *va);
+void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
 void drm_gpuva_unlink(struct drm_gpuva *va);
 
 struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
@@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
  * enum drm_gpuvm_flags - flags for struct drm_gpuvm
  */
 enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally by the
+	 * GPUVM's &dma_resv lock
+	 */
+	DRM_GPUVM_RESV_PROTECTED = BIT(0),
+
 	/**
 	 * @DRM_GPUVM_USERBITS: user defined bits
 	 */
-	DRM_GPUVM_USERBITS = BIT(0),
+	DRM_GPUVM_USERBITS = BIT(1),
 };
 
 /**
@@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 struct drm_gem_object *
 drm_gpuvm_resv_object_alloc(struct drm_device *drm);
 
+/**
+ * drm_gpuvm_resv_protected() - indicates whether &DRM_GPUVM_RESV_PROTECTED is
+ * set
+ * @gpuvm: the &drm_gpuvm
+ *
+ * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false otherwise.
+ */
+static inline bool
+drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
+{
+	return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
+}
+
 /**
  * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
  * @gpuvm__: the &drm_gpuvm
@@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device *drm);
  */
 #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
 
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 #define drm_gpuvm_resv_held(gpuvm__) \
 	dma_resv_held(drm_gpuvm_resv(gpuvm__))
 
@@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
+ * &drm_gem_object combination
+ *
+ * This structure is an abstraction representing a &drm_gpuvm and
+ * &drm_gem_object combination. It serves as an indirection to accelerate
+ * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
+ * &drm_gem_object.
+ *
+ * Furthermore it is used cache evicted GEM objects for a certain GPU-VM to
+ * accelerate validation.
+ *
+ * Typically, drivers want to create an instance of a struct drm_gpuvm_bo once
+ * a GEM object is mapped first in a GPU-VM and release the instance once the
+ * last mapping of the GEM object in this GPU-VM is unmapped.
+ */
+struct drm_gpuvm_bo {
+	/**
+	 * @vm: The &drm_gpuvm the @obj is mapped in. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @obj: The &drm_gem_object being mapped in @vm. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gem_object *obj;
+
+	/**
+	 * @kref: The reference count for this &drm_gpuvm_bo.
+	 */
+	struct kref kref;
+
+	/**
+	 * @list: Structure containing all &list_heads.
+	 */
+	struct {
+		/**
+		 * @gpuva: The list of linked &drm_gpuvas.
+		 *
+		 * It is safe to access entries from this list as long as the
+		 * GEM's gpuva lock is held. See also struct drm_gem_object.
+		 */
+		struct list_head gpuva;
+
+		/**
+		 * @entry: Structure containing all &list_heads serving as
+		 * entry.
+		 */
+		struct {
+			/**
+			 * @gem: List entry to attach to the &drm_gem_objects
+			 * gpuva list.
+			 */
+			struct list_head gem;
+		} entry;
+	} list;
+};
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
+
+/**
+ * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
+ *
+ * This function acquires an additional reference to @vm_bo. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm_bo *
+drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+	kref_get(&vm_bo->kref);
+	return vm_bo;
+}
+
+void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj);
+
+/**
+ * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
+	list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
+
+/**
+ * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a list of
+ * &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @next__: &next &drm_gpuva to store the next step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(), hence
+ * it is save against removal of elements.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
+	list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva, gem.entry)
+
 /**
  * enum drm_gpuva_op_type - GPU VA operation type
  *
@@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 				 u64 addr, u64 range);
 
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj);
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
 
 void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
 			struct drm_gpuva_ops *ops);
@@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
 	 */
 	void (*op_free)(struct drm_gpuva_op *op);
 
+	/**
+	 * @vm_bo_alloc: called when the &drm_gpuvm allocates
+	 * a struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * allocate memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
+
+	/**
+	 * @vm_bo_free: called when the &drm_gpuvm frees a
+	 * struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * free the previously allocated memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Add an abstraction layer between the drm_gpuva mappings of a particular
drm_gem_object and this GEM object itself. The abstraction represents a
combination of a drm_gem_object and drm_gpuvm. The drm_gem_object holds
a list of drm_gpuvm_bo structures (the structure representing this
abstraction), while each drm_gpuvm_bo contains list of mappings of this
GEM object.

This has multiple advantages:

1) We can use the drm_gpuvm_bo structure to attach it to various lists
   of the drm_gpuvm. This is useful for tracking external and evicted
   objects per VM, which is introduced in subsequent patches.

2) Finding mappings of a certain drm_gem_object mapped in a certain
   drm_gpuvm becomes much cheaper.

3) Drivers can derive and extend the structure to easily represent
   driver specific states of a BO for a certain GPUVM.

The idea of this abstraction was taken from amdgpu, hence the credit for
this idea goes to the developers of amdgpu.

Cc: Christian König <christian.koenig@amd.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++----
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
 include/drm/drm_gem.h                  |  32 +--
 include/drm/drm_gpuvm.h                | 185 +++++++++++++-
 4 files changed, 530 insertions(+), 86 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 6a88eafc5229..2c8fdefb19f0 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -70,6 +70,18 @@
  * &drm_gem_object, such as the &drm_gem_object containing the root page table,
  * but it can also be a 'dummy' object, which can be allocated with
  * drm_gpuvm_resv_object_alloc().
+ *
+ * In order to connect a struct drm_gpuva its backing &drm_gem_object each
+ * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and each
+ * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
+ *
+ * A &drm_gpuvm_bo is an abstraction that represents a combination of a
+ * &drm_gpuvm and a &drm_gem_object. Every such combination should be unique.
+ * This is ensured by the API through drm_gpuvm_bo_obtain() and
+ * drm_gpuvm_bo_obtain_prealloc() which first look into the corresponding
+ * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
+ * particular combination. If not existent a new instance is created and linked
+ * to the &drm_gem_object.
  */
 
 /**
@@ -395,21 +407,28 @@
 /**
  * DOC: Locking
  *
- * Generally, the GPU VA manager does not take care of locking itself, it is
- * the drivers responsibility to take care about locking. Drivers might want to
- * protect the following operations: inserting, removing and iterating
- * &drm_gpuva objects as well as generating all kinds of operations, such as
- * split / merge or prefetch.
- *
- * The GPU VA manager also does not take care of the locking of the backing
- * &drm_gem_object buffers GPU VA lists by itself; drivers are responsible to
- * enforce mutual exclusion using either the GEMs dma_resv lock or alternatively
- * a driver specific external lock. For the latter see also
- * drm_gem_gpuva_set_lock().
- *
- * However, the GPU VA manager contains lockdep checks to ensure callers of its
- * API hold the corresponding lock whenever the &drm_gem_objects GPU VA list is
- * accessed by functions such as drm_gpuva_link() or drm_gpuva_unlink().
+ * In terms of managing &drm_gpuva entries DRM GPUVM does not take care of
+ * locking itself, it is the drivers responsibility to take care about locking.
+ * Drivers might want to protect the following operations: inserting, removing
+ * and iterating &drm_gpuva objects as well as generating all kinds of
+ * operations, such as split / merge or prefetch.
+ *
+ * DRM GPUVM also does not take care of the locking of the backing
+ * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo abstractions by
+ * itself; drivers are responsible to enforce mutual exclusion using either the
+ * GEMs dma_resv lock or alternatively a driver specific external lock. For the
+ * latter see also drm_gem_gpuva_set_lock().
+ *
+ * However, DRM GPUVM contains lockdep checks to ensure callers of its API hold
+ * the corresponding lock whenever the &drm_gem_objects GPU VA list is accessed
+ * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but also
+ * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
+ *
+ * The latter is required since on creation and destruction of a &drm_gpuvm_bo
+ * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects gpuva list.
+ * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
+ * &drm_gem_object must be able to observe previous creations and destructions
+ * of &drm_gpuvm_bos in order to keep instances unique.
  */
 
 /**
@@ -439,6 +458,7 @@
  *	{
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op
+ *		struct drm_gpuvm_bo *vm_bo;
  *
  *		driver_lock_va_space();
  *		ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
@@ -446,6 +466,10 @@
  *		if (IS_ERR(ops))
  *			return PTR_ERR(ops);
  *
+ *		vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
+ *		if (IS_ERR(vm_bo))
+ *			return PTR_ERR(vm_bo);
+ *
  *		drm_gpuva_for_each_op(op, ops) {
  *			struct drm_gpuva *va;
  *
@@ -458,7 +482,7 @@
  *
  *				driver_vm_map();
  *				drm_gpuva_map(gpuvm, va, &op->map);
- *				drm_gpuva_link(va);
+ *				drm_gpuva_link(va, vm_bo);
  *
  *				break;
  *			case DRM_GPUVA_OP_REMAP: {
@@ -485,11 +509,11 @@
  *				driver_vm_remap();
  *				drm_gpuva_remap(prev, next, &op->remap);
  *
- *				drm_gpuva_unlink(va);
  *				if (prev)
- *					drm_gpuva_link(prev);
+ *					drm_gpuva_link(prev, va->vm_bo);
  *				if (next)
- *					drm_gpuva_link(next);
+ *					drm_gpuva_link(next, va->vm_bo);
+ *				drm_gpuva_unlink(va);
  *
  *				break;
  *			}
@@ -505,6 +529,7 @@
  *				break;
  *			}
  *		}
+ *		drm_gpuvm_bo_put(vm_bo);
  *		driver_unlock_va_space();
  *
  *		return 0;
@@ -514,6 +539,7 @@
  *
  *	struct driver_context {
  *		struct drm_gpuvm *gpuvm;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva *new_va;
  *		struct drm_gpuva *prev_va;
  *		struct drm_gpuva *next_va;
@@ -534,6 +560,7 @@
  *				  struct drm_gem_object *obj, u64 offset)
  *	{
  *		struct driver_context ctx;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op;
  *		int ret = 0;
@@ -543,16 +570,23 @@
  *		ctx.new_va = kzalloc(sizeof(*ctx.new_va), GFP_KERNEL);
  *		ctx.prev_va = kzalloc(sizeof(*ctx.prev_va), GFP_KERNEL);
  *		ctx.next_va = kzalloc(sizeof(*ctx.next_va), GFP_KERNEL);
- *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
+ *		ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+ *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va || !vm_bo) {
  *			ret = -ENOMEM;
  *			goto out;
  *		}
  *
+ *		// Typically protected with a driver specific GEM gpuva lock
+ *		// used in the fence signaling path for drm_gpuva_link() and
+ *		// drm_gpuva_unlink(), hence pre-allocate.
+ *		ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
+ *
  *		driver_lock_va_space();
  *		ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj, offset);
  *		driver_unlock_va_space();
  *
  *	out:
+ *		drm_gpuvm_bo_put(ctx.vm_bo);
  *		kfree(ctx.new_va);
  *		kfree(ctx.prev_va);
  *		kfree(ctx.next_va);
@@ -565,7 +599,7 @@
  *
  *		drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
  *
- *		drm_gpuva_link(ctx->new_va);
+ *		drm_gpuva_link(ctx->new_va, ctx->vm_bo);
  *
  *		// prevent the new GPUVA from being freed in
  *		// driver_mapping_create()
@@ -577,22 +611,23 @@
  *	int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
  *	{
  *		struct driver_context *ctx = __ctx;
+ *		struct drm_gpuva *va = op->remap.unmap->va;
  *
  *		drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op->remap);
  *
- *		drm_gpuva_unlink(op->remap.unmap->va);
- *		kfree(op->remap.unmap->va);
- *
  *		if (op->remap.prev) {
- *			drm_gpuva_link(ctx->prev_va);
+ *			drm_gpuva_link(ctx->prev_va, va->vm_bo);
  *			ctx->prev_va = NULL;
  *		}
  *
  *		if (op->remap.next) {
- *			drm_gpuva_link(ctx->next_va);
+ *			drm_gpuva_link(ctx->next_va, va->vm_bo);
  *			ctx->next_va = NULL;
  *		}
  *
+ *		drm_gpuva_unlink(va);
+ *		kfree(va);
+ *
  *		return 0;
  *	}
  *
@@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+/**
+ * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * If provided by the driver, this function uses the &drm_gpuvm_ops
+ * vm_bo_alloc() callback to allocate.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+
+	if (ops && ops->vm_bo_alloc)
+		vm_bo = ops->vm_bo_alloc();
+	else
+		vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
+
+	if (unlikely(!vm_bo))
+		return NULL;
+
+	vm_bo->vm = drm_gpuvm_get(gpuvm);
+	vm_bo->obj = obj;
+	drm_gem_object_get(obj);
+
+	kref_init(&vm_bo->kref);
+	INIT_LIST_HEAD(&vm_bo->list.gpuva);
+	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
+
+static void
+drm_gpuvm_bo_destroy(struct kref *kref)
+{
+	struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
+						  kref);
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_del(&vm_bo->list.entry.gem);
+
+	if (ops && ops->vm_bo_free)
+		ops->vm_bo_free(vm_bo);
+	else
+		kfree(vm_bo);
+
+	drm_gpuvm_put(gpuvm);
+	drm_gem_object_put(obj);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to release the reference of
+ *
+ * This releases a reference to @vm_bo.
+ *
+ * If the reference count drops to zero, the &gpuvm_bo is destroyed, which
+ * includes removing it from the GEMs gpuva list. Hence, if a call to this
+ * function can potentially let the reference count to zero the caller must
+ * hold the dma-resv or driver specific GEM gpuva lock.
+ */
+void
+drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
+{
+	if (vm_bo)
+		kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
+
+static struct drm_gpuvm_bo *
+__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		if (vm_bo->vm == gpuvm)
+			return vm_bo;
+
+	return NULL;
+}
+
+/**
+ * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
+ * &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
+
+	return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
+
+/**
+ * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo for the
+ * given &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly. If not found, allocates a new
+ * &drm_gpuvm_bo.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo)
+		return vm_bo;
+
+	vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+	if (!vm_bo)
+		return ERR_PTR(-ENOMEM);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
+
+/**
+ * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the &drm_gpuvm_bo
+ * for the given &drm_gpuvm and &drm_gem_object
+ * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo reference
+ * count is decreased. If not found @__vm_bo is returned without further
+ * increase of the reference count.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no existing
+ * &drm_gpuvm_bo was found
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
+{
+	struct drm_gpuvm *gpuvm = __vm_bo->vm;
+	struct drm_gem_object *obj = __vm_bo->obj;
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo) {
+		drm_gpuvm_bo_put(__vm_bo);
+		return vm_bo;
+	}
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return __vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
@@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 /**
  * drm_gpuva_link() - link a &drm_gpuva
  * @va: the &drm_gpuva to link
+ * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
  *
- * This adds the given &va to the GPU VA list of the &drm_gem_object it is
- * associated with.
+ * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo and the
+ * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
+ *
+ * For every &drm_gpuva entry added to the &drm_gpuvm_bo an additional
+ * reference of the latter is taken.
  *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
-drm_gpuva_link(struct drm_gpuva *va)
+drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(!obj))
 		return;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
 
-	list_add_tail(&va->gem.entry, &obj->gpuva.list);
+	va->vm_bo = drm_gpuvm_bo_get(vm_bo);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_link);
 
@@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
  * This removes the given &va from the GPU VA list of the &drm_gem_object it is
  * associated with.
  *
+ * This removes the given &va from the GPU VA list of the &drm_gpuvm_bo and
+ * the &drm_gpuvm_bo from the &drm_gem_object it is associated with in case
+ * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
+ *
+ * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a reference of
+ * the latter is dropped.
+ *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
 drm_gpuva_unlink(struct drm_gpuva *va)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm_bo *vm_bo = va->vm_bo;
 
 	if (unlikely(!obj))
 		return;
 
 	drm_gem_gpuva_assert_lock_held(obj);
-
 	list_del_init(&va->gem.entry);
+
+	va->vm_bo = NULL;
+	drm_gpuvm_bo_put(vm_bo);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
 
@@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
 		struct drm_gpuva *next,
 		struct drm_gpuva_op_remap *op)
 {
-	struct drm_gpuva *curr = op->unmap->va;
-	struct drm_gpuvm *gpuvm = curr->vm;
+	struct drm_gpuva *va = op->unmap->va;
+	struct drm_gpuvm *gpuvm = va->vm;
 
-	drm_gpuva_remove(curr);
+	drm_gpuva_remove(va);
 
 	if (op->prev) {
 		drm_gpuva_init_from_op(prev, op->prev);
@@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
 
 /**
- * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
- * @gpuvm: the &drm_gpuvm representing the GPU VA space
- * @obj: the &drm_gem_object to unmap
+ * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
+ * @vm_bo: the &drm_gpuvm_bo abstraction
  *
  * This function creates a list of operations to perform unmapping for every
  * GPUVA attached to a GEM.
@@ -1754,15 +1997,14 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
  * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
  */
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj)
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gpuva_ops *ops;
 	struct drm_gpuva_op *op;
 	struct drm_gpuva *va;
 	int ret;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_gpuva_assert_lock_held(vm_bo->obj);
 
 	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
 	if (!ops)
@@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 
 	INIT_LIST_HEAD(&ops->list);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		op = gpuva_op_alloc(gpuvm);
+	drm_gpuvm_bo_for_each_va(va, vm_bo) {
+		op = gpuva_op_alloc(vm_bo->vm);
 		if (!op) {
 			ret = -ENOMEM;
 			goto err_free_ops;
@@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 	return ops;
 
 err_free_ops:
-	drm_gpuva_ops_free(gpuvm, ops);
+	drm_gpuva_ops_free(vm_bo->vm, ops);
 	return ERR_PTR(ret);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
 
 /**
  * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index cb2f06565c46..eda7bb8624f1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -62,6 +62,8 @@ struct bind_job_op {
 	enum vm_bind_op op;
 	u32 flags;
 
+	struct drm_gpuvm_bo *vm_bo;
+
 	struct {
 		u64 addr;
 		u64 range;
@@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
 }
 
 static void
-bind_link_gpuvas(struct drm_gpuva_ops *ops, struct nouveau_uvma_prealloc *new)
+bind_link_gpuvas(struct bind_job_op *bop)
 {
+	struct nouveau_uvma_prealloc *new = &bop->new;
+	struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
+	struct drm_gpuva_ops *ops = bop->ops;
 	struct drm_gpuva_op *op;
 
 	drm_gpuva_for_each_op(op, ops) {
 		switch (op->op) {
 		case DRM_GPUVA_OP_MAP:
-			drm_gpuva_link(&new->map->va);
+			drm_gpuva_link(&new->map->va, vm_bo);
 			break;
-		case DRM_GPUVA_OP_REMAP:
+		case DRM_GPUVA_OP_REMAP: {
+			struct drm_gpuva *va = op->remap.unmap->va;
+
 			if (op->remap.prev)
-				drm_gpuva_link(&new->prev->va);
+				drm_gpuva_link(&new->prev->va, va->vm_bo);
 			if (op->remap.next)
-				drm_gpuva_link(&new->next->va);
-			drm_gpuva_unlink(op->remap.unmap->va);
+				drm_gpuva_link(&new->next->va, va->vm_bo);
+			drm_gpuva_unlink(va);
 			break;
+		}
 		case DRM_GPUVA_OP_UNMAP:
 			drm_gpuva_unlink(op->unmap.va);
 			break;
@@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 
 	list_for_each_op(op, &bind_job->ops) {
 		if (op->op == OP_MAP) {
-			op->gem.obj = drm_gem_object_lookup(job->file_priv,
-							    op->gem.handle);
-			if (!op->gem.obj)
+			struct drm_gem_object *obj = op->gem.obj =
+				drm_gem_object_lookup(job->file_priv,
+						      op->gem.handle);
+			if (!obj)
 				return -ENOENT;
+
+			dma_resv_lock(obj->resv, NULL);
+			op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base, obj);
+			dma_resv_unlock(obj->resv);
+			if (IS_ERR(op->vm_bo))
+				return PTR_ERR(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		case OP_UNMAP_SPARSE:
 		case OP_MAP:
 		case OP_UNMAP:
-			bind_link_gpuvas(op->ops, &op->new);
+			bind_link_gpuvas(op);
 			break;
 		default:
 			break;
@@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct work_struct *work)
 		if (!IS_ERR_OR_NULL(op->ops))
 			drm_gpuva_ops_free(&uvmm->base, op->ops);
 
+		if (!IS_ERR_OR_NULL(op->vm_bo)) {
+			dma_resv_lock(obj->resv, NULL);
+			drm_gpuvm_bo_put(op->vm_bo);
+			dma_resv_unlock(obj->resv);
+		}
+
 		if (obj)
 			drm_gem_object_put(obj);
 	}
@@ -1752,15 +1773,18 @@ void
 nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem *mem)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_map(uvma, mem);
-		drm_gpuva_invalidate(va, false);
+			nouveau_uvma_map(uvma, mem);
+			drm_gpuva_invalidate(va, false);
+		}
 	}
 }
 
@@ -1768,15 +1792,18 @@ void
 nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_unmap(uvma);
-		drm_gpuva_invalidate(va, true);
+			nouveau_uvma_unmap(uvma);
+			drm_gpuva_invalidate(va, true);
+		}
 	}
 }
 
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index 16364487fde9..369505447acd 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
  * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
  * @obj: the &drm_gem_object
  *
- * This initializes the &drm_gem_object's &drm_gpuva list.
+ * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
  *
  * Calling this function is only necessary for drivers intending to support the
  * &drm_driver_feature DRIVER_GEM_GPUVA.
@@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct drm_gem_object *obj)
 }
 
 /**
- * drm_gem_for_each_gpuva() - iternator to walk over a list of gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bo structure to assign to in each iteration step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
- * &drm_gpuva_manager.
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
+ * &drm_gem_object.
  */
-#define drm_gem_for_each_gpuva(entry__, obj__) \
-	list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
+	list_for_each_entry(entry__, &(obj__)->gpuva.list, list.entry.gem)
 
 /**
- * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a list of
- * gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @next__: &next &drm_gpuva to store the next step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a list of
+ * &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bostructure to assign to in each iteration step
+ * @next__: &next &drm_gpuvm_bo to store the next step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
  * &drm_gem_object. It is implemented with list_for_each_entry_safe(), hence
  * it is save against removal of elements.
  */
-#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
-	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
+	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, list.entry.gem)
 
 #endif /* __DRM_GEM_H__ */
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 4e6e1fd3485a..b12fb22b0e22 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -25,6 +25,7 @@
  * OTHER DEALINGS IN THE SOFTWARE.
  */
 
+#include <linux/dma-resv.h>
 #include <linux/list.h>
 #include <linux/rbtree.h>
 #include <linux/types.h>
@@ -33,6 +34,7 @@
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
+struct drm_gpuvm_bo;
 struct drm_gpuvm_ops;
 
 /**
@@ -73,6 +75,12 @@ struct drm_gpuva {
 	 */
 	struct drm_gpuvm *vm;
 
+	/**
+	 * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
+	 * &drm_gem_object
+	 */
+	struct drm_gpuvm_bo *vm_bo;
+
 	/**
 	 * @flags: the &drm_gpuva_flags for this mapping
 	 */
@@ -108,7 +116,7 @@ struct drm_gpuva {
 		struct drm_gem_object *obj;
 
 		/**
-		 * @entry: the &list_head to attach this object to a &drm_gem_object
+		 * @entry: the &list_head to attach this object to a &drm_gpuvm_bo
 		 */
 		struct list_head entry;
 	} gem;
@@ -141,7 +149,7 @@ struct drm_gpuva {
 int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
 void drm_gpuva_remove(struct drm_gpuva *va);
 
-void drm_gpuva_link(struct drm_gpuva *va);
+void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
 void drm_gpuva_unlink(struct drm_gpuva *va);
 
 struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
@@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
  * enum drm_gpuvm_flags - flags for struct drm_gpuvm
  */
 enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally by the
+	 * GPUVM's &dma_resv lock
+	 */
+	DRM_GPUVM_RESV_PROTECTED = BIT(0),
+
 	/**
 	 * @DRM_GPUVM_USERBITS: user defined bits
 	 */
-	DRM_GPUVM_USERBITS = BIT(0),
+	DRM_GPUVM_USERBITS = BIT(1),
 };
 
 /**
@@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 struct drm_gem_object *
 drm_gpuvm_resv_object_alloc(struct drm_device *drm);
 
+/**
+ * drm_gpuvm_resv_protected() - indicates whether &DRM_GPUVM_RESV_PROTECTED is
+ * set
+ * @gpuvm: the &drm_gpuvm
+ *
+ * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false otherwise.
+ */
+static inline bool
+drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
+{
+	return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
+}
+
 /**
  * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
  * @gpuvm__: the &drm_gpuvm
@@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device *drm);
  */
 #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
 
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 #define drm_gpuvm_resv_held(gpuvm__) \
 	dma_resv_held(drm_gpuvm_resv(gpuvm__))
 
@@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
+ * &drm_gem_object combination
+ *
+ * This structure is an abstraction representing a &drm_gpuvm and
+ * &drm_gem_object combination. It serves as an indirection to accelerate
+ * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
+ * &drm_gem_object.
+ *
+ * Furthermore it is used cache evicted GEM objects for a certain GPU-VM to
+ * accelerate validation.
+ *
+ * Typically, drivers want to create an instance of a struct drm_gpuvm_bo once
+ * a GEM object is mapped first in a GPU-VM and release the instance once the
+ * last mapping of the GEM object in this GPU-VM is unmapped.
+ */
+struct drm_gpuvm_bo {
+	/**
+	 * @vm: The &drm_gpuvm the @obj is mapped in. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @obj: The &drm_gem_object being mapped in @vm. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gem_object *obj;
+
+	/**
+	 * @kref: The reference count for this &drm_gpuvm_bo.
+	 */
+	struct kref kref;
+
+	/**
+	 * @list: Structure containing all &list_heads.
+	 */
+	struct {
+		/**
+		 * @gpuva: The list of linked &drm_gpuvas.
+		 *
+		 * It is safe to access entries from this list as long as the
+		 * GEM's gpuva lock is held. See also struct drm_gem_object.
+		 */
+		struct list_head gpuva;
+
+		/**
+		 * @entry: Structure containing all &list_heads serving as
+		 * entry.
+		 */
+		struct {
+			/**
+			 * @gem: List entry to attach to the &drm_gem_objects
+			 * gpuva list.
+			 */
+			struct list_head gem;
+		} entry;
+	} list;
+};
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
+
+/**
+ * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
+ *
+ * This function acquires an additional reference to @vm_bo. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm_bo *
+drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+	kref_get(&vm_bo->kref);
+	return vm_bo;
+}
+
+void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj);
+
+/**
+ * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
+	list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
+
+/**
+ * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a list of
+ * &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @next__: &next &drm_gpuva to store the next step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(), hence
+ * it is save against removal of elements.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
+	list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva, gem.entry)
+
 /**
  * enum drm_gpuva_op_type - GPU VA operation type
  *
@@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 				 u64 addr, u64 range);
 
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj);
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
 
 void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
 			struct drm_gpuva_ops *ops);
@@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
 	 */
 	void (*op_free)(struct drm_gpuva_op *op);
 
+	/**
+	 * @vm_bo_alloc: called when the &drm_gpuvm allocates
+	 * a struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * allocate memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
+
+	/**
+	 * @vm_bo_free: called when the &drm_gpuvm frees a
+	 * struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * free the previously allocated memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Add an abstraction layer between the drm_gpuva mappings of a particular
drm_gem_object and this GEM object itself. The abstraction represents a
combination of a drm_gem_object and drm_gpuvm. The drm_gem_object holds
a list of drm_gpuvm_bo structures (the structure representing this
abstraction), while each drm_gpuvm_bo contains list of mappings of this
GEM object.

This has multiple advantages:

1) We can use the drm_gpuvm_bo structure to attach it to various lists
   of the drm_gpuvm. This is useful for tracking external and evicted
   objects per VM, which is introduced in subsequent patches.

2) Finding mappings of a certain drm_gem_object mapped in a certain
   drm_gpuvm becomes much cheaper.

3) Drivers can derive and extend the structure to easily represent
   driver specific states of a BO for a certain GPUVM.

The idea of this abstraction was taken from amdgpu, hence the credit for
this idea goes to the developers of amdgpu.

Cc: Christian König <christian.koenig@amd.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++----
 drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
 include/drm/drm_gem.h                  |  32 +--
 include/drm/drm_gpuvm.h                | 185 +++++++++++++-
 4 files changed, 530 insertions(+), 86 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 6a88eafc5229..2c8fdefb19f0 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -70,6 +70,18 @@
  * &drm_gem_object, such as the &drm_gem_object containing the root page table,
  * but it can also be a 'dummy' object, which can be allocated with
  * drm_gpuvm_resv_object_alloc().
+ *
+ * In order to connect a struct drm_gpuva its backing &drm_gem_object each
+ * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and each
+ * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
+ *
+ * A &drm_gpuvm_bo is an abstraction that represents a combination of a
+ * &drm_gpuvm and a &drm_gem_object. Every such combination should be unique.
+ * This is ensured by the API through drm_gpuvm_bo_obtain() and
+ * drm_gpuvm_bo_obtain_prealloc() which first look into the corresponding
+ * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
+ * particular combination. If not existent a new instance is created and linked
+ * to the &drm_gem_object.
  */
 
 /**
@@ -395,21 +407,28 @@
 /**
  * DOC: Locking
  *
- * Generally, the GPU VA manager does not take care of locking itself, it is
- * the drivers responsibility to take care about locking. Drivers might want to
- * protect the following operations: inserting, removing and iterating
- * &drm_gpuva objects as well as generating all kinds of operations, such as
- * split / merge or prefetch.
- *
- * The GPU VA manager also does not take care of the locking of the backing
- * &drm_gem_object buffers GPU VA lists by itself; drivers are responsible to
- * enforce mutual exclusion using either the GEMs dma_resv lock or alternatively
- * a driver specific external lock. For the latter see also
- * drm_gem_gpuva_set_lock().
- *
- * However, the GPU VA manager contains lockdep checks to ensure callers of its
- * API hold the corresponding lock whenever the &drm_gem_objects GPU VA list is
- * accessed by functions such as drm_gpuva_link() or drm_gpuva_unlink().
+ * In terms of managing &drm_gpuva entries DRM GPUVM does not take care of
+ * locking itself, it is the drivers responsibility to take care about locking.
+ * Drivers might want to protect the following operations: inserting, removing
+ * and iterating &drm_gpuva objects as well as generating all kinds of
+ * operations, such as split / merge or prefetch.
+ *
+ * DRM GPUVM also does not take care of the locking of the backing
+ * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo abstractions by
+ * itself; drivers are responsible to enforce mutual exclusion using either the
+ * GEMs dma_resv lock or alternatively a driver specific external lock. For the
+ * latter see also drm_gem_gpuva_set_lock().
+ *
+ * However, DRM GPUVM contains lockdep checks to ensure callers of its API hold
+ * the corresponding lock whenever the &drm_gem_objects GPU VA list is accessed
+ * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but also
+ * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
+ *
+ * The latter is required since on creation and destruction of a &drm_gpuvm_bo
+ * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects gpuva list.
+ * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
+ * &drm_gem_object must be able to observe previous creations and destructions
+ * of &drm_gpuvm_bos in order to keep instances unique.
  */
 
 /**
@@ -439,6 +458,7 @@
  *	{
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op
+ *		struct drm_gpuvm_bo *vm_bo;
  *
  *		driver_lock_va_space();
  *		ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
@@ -446,6 +466,10 @@
  *		if (IS_ERR(ops))
  *			return PTR_ERR(ops);
  *
+ *		vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
+ *		if (IS_ERR(vm_bo))
+ *			return PTR_ERR(vm_bo);
+ *
  *		drm_gpuva_for_each_op(op, ops) {
  *			struct drm_gpuva *va;
  *
@@ -458,7 +482,7 @@
  *
  *				driver_vm_map();
  *				drm_gpuva_map(gpuvm, va, &op->map);
- *				drm_gpuva_link(va);
+ *				drm_gpuva_link(va, vm_bo);
  *
  *				break;
  *			case DRM_GPUVA_OP_REMAP: {
@@ -485,11 +509,11 @@
  *				driver_vm_remap();
  *				drm_gpuva_remap(prev, next, &op->remap);
  *
- *				drm_gpuva_unlink(va);
  *				if (prev)
- *					drm_gpuva_link(prev);
+ *					drm_gpuva_link(prev, va->vm_bo);
  *				if (next)
- *					drm_gpuva_link(next);
+ *					drm_gpuva_link(next, va->vm_bo);
+ *				drm_gpuva_unlink(va);
  *
  *				break;
  *			}
@@ -505,6 +529,7 @@
  *				break;
  *			}
  *		}
+ *		drm_gpuvm_bo_put(vm_bo);
  *		driver_unlock_va_space();
  *
  *		return 0;
@@ -514,6 +539,7 @@
  *
  *	struct driver_context {
  *		struct drm_gpuvm *gpuvm;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva *new_va;
  *		struct drm_gpuva *prev_va;
  *		struct drm_gpuva *next_va;
@@ -534,6 +560,7 @@
  *				  struct drm_gem_object *obj, u64 offset)
  *	{
  *		struct driver_context ctx;
+ *		struct drm_gpuvm_bo *vm_bo;
  *		struct drm_gpuva_ops *ops;
  *		struct drm_gpuva_op *op;
  *		int ret = 0;
@@ -543,16 +570,23 @@
  *		ctx.new_va = kzalloc(sizeof(*ctx.new_va), GFP_KERNEL);
  *		ctx.prev_va = kzalloc(sizeof(*ctx.prev_va), GFP_KERNEL);
  *		ctx.next_va = kzalloc(sizeof(*ctx.next_va), GFP_KERNEL);
- *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
+ *		ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+ *		if (!ctx.new_va || !ctx.prev_va || !ctx.next_va || !vm_bo) {
  *			ret = -ENOMEM;
  *			goto out;
  *		}
  *
+ *		// Typically protected with a driver specific GEM gpuva lock
+ *		// used in the fence signaling path for drm_gpuva_link() and
+ *		// drm_gpuva_unlink(), hence pre-allocate.
+ *		ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
+ *
  *		driver_lock_va_space();
  *		ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj, offset);
  *		driver_unlock_va_space();
  *
  *	out:
+ *		drm_gpuvm_bo_put(ctx.vm_bo);
  *		kfree(ctx.new_va);
  *		kfree(ctx.prev_va);
  *		kfree(ctx.next_va);
@@ -565,7 +599,7 @@
  *
  *		drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
  *
- *		drm_gpuva_link(ctx->new_va);
+ *		drm_gpuva_link(ctx->new_va, ctx->vm_bo);
  *
  *		// prevent the new GPUVA from being freed in
  *		// driver_mapping_create()
@@ -577,22 +611,23 @@
  *	int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
  *	{
  *		struct driver_context *ctx = __ctx;
+ *		struct drm_gpuva *va = op->remap.unmap->va;
  *
  *		drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op->remap);
  *
- *		drm_gpuva_unlink(op->remap.unmap->va);
- *		kfree(op->remap.unmap->va);
- *
  *		if (op->remap.prev) {
- *			drm_gpuva_link(ctx->prev_va);
+ *			drm_gpuva_link(ctx->prev_va, va->vm_bo);
  *			ctx->prev_va = NULL;
  *		}
  *
  *		if (op->remap.next) {
- *			drm_gpuva_link(ctx->next_va);
+ *			drm_gpuva_link(ctx->next_va, va->vm_bo);
  *			ctx->next_va = NULL;
  *		}
  *
+ *		drm_gpuva_unlink(va);
+ *		kfree(va);
+ *
  *		return 0;
  *	}
  *
@@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+/**
+ * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * If provided by the driver, this function uses the &drm_gpuvm_ops
+ * vm_bo_alloc() callback to allocate.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+
+	if (ops && ops->vm_bo_alloc)
+		vm_bo = ops->vm_bo_alloc();
+	else
+		vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
+
+	if (unlikely(!vm_bo))
+		return NULL;
+
+	vm_bo->vm = drm_gpuvm_get(gpuvm);
+	vm_bo->obj = obj;
+	drm_gem_object_get(obj);
+
+	kref_init(&vm_bo->kref);
+	INIT_LIST_HEAD(&vm_bo->list.gpuva);
+	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
+
+static void
+drm_gpuvm_bo_destroy(struct kref *kref)
+{
+	struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
+						  kref);
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_del(&vm_bo->list.entry.gem);
+
+	if (ops && ops->vm_bo_free)
+		ops->vm_bo_free(vm_bo);
+	else
+		kfree(vm_bo);
+
+	drm_gpuvm_put(gpuvm);
+	drm_gem_object_put(obj);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to release the reference of
+ *
+ * This releases a reference to @vm_bo.
+ *
+ * If the reference count drops to zero, the &gpuvm_bo is destroyed, which
+ * includes removing it from the GEMs gpuva list. Hence, if a call to this
+ * function can potentially let the reference count to zero the caller must
+ * hold the dma-resv or driver specific GEM gpuva lock.
+ */
+void
+drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
+{
+	if (vm_bo)
+		kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
+
+static struct drm_gpuvm_bo *
+__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		if (vm_bo->vm == gpuvm)
+			return vm_bo;
+
+	return NULL;
+}
+
+/**
+ * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
+ * &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
+
+	return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
+
+/**
+ * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo for the
+ * given &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly. If not found, allocates a new
+ * &drm_gpuvm_bo.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo)
+		return vm_bo;
+
+	vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+	if (!vm_bo)
+		return ERR_PTR(-ENOMEM);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
+
+/**
+ * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the &drm_gpuvm_bo
+ * for the given &drm_gpuvm and &drm_gem_object
+ * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo reference
+ * count is decreased. If not found @__vm_bo is returned without further
+ * increase of the reference count.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no existing
+ * &drm_gpuvm_bo was found
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
+{
+	struct drm_gpuvm *gpuvm = __vm_bo->vm;
+	struct drm_gem_object *obj = __vm_bo->obj;
+	struct drm_gpuvm_bo *vm_bo;
+
+	vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+	if (vm_bo) {
+		drm_gpuvm_bo_put(__vm_bo);
+		return vm_bo;
+	}
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
+
+	return __vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
@@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 /**
  * drm_gpuva_link() - link a &drm_gpuva
  * @va: the &drm_gpuva to link
+ * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
  *
- * This adds the given &va to the GPU VA list of the &drm_gem_object it is
- * associated with.
+ * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo and the
+ * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
+ *
+ * For every &drm_gpuva entry added to the &drm_gpuvm_bo an additional
+ * reference of the latter is taken.
  *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
-drm_gpuva_link(struct drm_gpuva *va)
+drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm *gpuvm = va->vm;
 
 	if (unlikely(!obj))
 		return;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
 
-	list_add_tail(&va->gem.entry, &obj->gpuva.list);
+	va->vm_bo = drm_gpuvm_bo_get(vm_bo);
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_link);
 
@@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
  * This removes the given &va from the GPU VA list of the &drm_gem_object it is
  * associated with.
  *
+ * This removes the given &va from the GPU VA list of the &drm_gpuvm_bo and
+ * the &drm_gpuvm_bo from the &drm_gem_object it is associated with in case
+ * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
+ *
+ * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a reference of
+ * the latter is dropped.
+ *
  * This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
  */
 void
 drm_gpuva_unlink(struct drm_gpuva *va)
 {
 	struct drm_gem_object *obj = va->gem.obj;
+	struct drm_gpuvm_bo *vm_bo = va->vm_bo;
 
 	if (unlikely(!obj))
 		return;
 
 	drm_gem_gpuva_assert_lock_held(obj);
-
 	list_del_init(&va->gem.entry);
+
+	va->vm_bo = NULL;
+	drm_gpuvm_bo_put(vm_bo);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
 
@@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
 		struct drm_gpuva *next,
 		struct drm_gpuva_op_remap *op)
 {
-	struct drm_gpuva *curr = op->unmap->va;
-	struct drm_gpuvm *gpuvm = curr->vm;
+	struct drm_gpuva *va = op->unmap->va;
+	struct drm_gpuvm *gpuvm = va->vm;
 
-	drm_gpuva_remove(curr);
+	drm_gpuva_remove(va);
 
 	if (op->prev) {
 		drm_gpuva_init_from_op(prev, op->prev);
@@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
 
 /**
- * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
- * @gpuvm: the &drm_gpuvm representing the GPU VA space
- * @obj: the &drm_gem_object to unmap
+ * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
+ * @vm_bo: the &drm_gpuvm_bo abstraction
  *
  * This function creates a list of operations to perform unmapping for every
  * GPUVA attached to a GEM.
@@ -1754,15 +1997,14 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
  * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
  */
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj)
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
 {
 	struct drm_gpuva_ops *ops;
 	struct drm_gpuva_op *op;
 	struct drm_gpuva *va;
 	int ret;
 
-	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_gpuva_assert_lock_held(vm_bo->obj);
 
 	ops = kzalloc(sizeof(*ops), GFP_KERNEL);
 	if (!ops)
@@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 
 	INIT_LIST_HEAD(&ops->list);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		op = gpuva_op_alloc(gpuvm);
+	drm_gpuvm_bo_for_each_va(va, vm_bo) {
+		op = gpuva_op_alloc(vm_bo->vm);
 		if (!op) {
 			ret = -ENOMEM;
 			goto err_free_ops;
@@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
 	return ops;
 
 err_free_ops:
-	drm_gpuva_ops_free(gpuvm, ops);
+	drm_gpuva_ops_free(vm_bo->vm, ops);
 	return ERR_PTR(ret);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
 
 /**
  * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index cb2f06565c46..eda7bb8624f1 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -62,6 +62,8 @@ struct bind_job_op {
 	enum vm_bind_op op;
 	u32 flags;
 
+	struct drm_gpuvm_bo *vm_bo;
+
 	struct {
 		u64 addr;
 		u64 range;
@@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
 }
 
 static void
-bind_link_gpuvas(struct drm_gpuva_ops *ops, struct nouveau_uvma_prealloc *new)
+bind_link_gpuvas(struct bind_job_op *bop)
 {
+	struct nouveau_uvma_prealloc *new = &bop->new;
+	struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
+	struct drm_gpuva_ops *ops = bop->ops;
 	struct drm_gpuva_op *op;
 
 	drm_gpuva_for_each_op(op, ops) {
 		switch (op->op) {
 		case DRM_GPUVA_OP_MAP:
-			drm_gpuva_link(&new->map->va);
+			drm_gpuva_link(&new->map->va, vm_bo);
 			break;
-		case DRM_GPUVA_OP_REMAP:
+		case DRM_GPUVA_OP_REMAP: {
+			struct drm_gpuva *va = op->remap.unmap->va;
+
 			if (op->remap.prev)
-				drm_gpuva_link(&new->prev->va);
+				drm_gpuva_link(&new->prev->va, va->vm_bo);
 			if (op->remap.next)
-				drm_gpuva_link(&new->next->va);
-			drm_gpuva_unlink(op->remap.unmap->va);
+				drm_gpuva_link(&new->next->va, va->vm_bo);
+			drm_gpuva_unlink(va);
 			break;
+		}
 		case DRM_GPUVA_OP_UNMAP:
 			drm_gpuva_unlink(op->unmap.va);
 			break;
@@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 
 	list_for_each_op(op, &bind_job->ops) {
 		if (op->op == OP_MAP) {
-			op->gem.obj = drm_gem_object_lookup(job->file_priv,
-							    op->gem.handle);
-			if (!op->gem.obj)
+			struct drm_gem_object *obj = op->gem.obj =
+				drm_gem_object_lookup(job->file_priv,
+						      op->gem.handle);
+			if (!obj)
 				return -ENOENT;
+
+			dma_resv_lock(obj->resv, NULL);
+			op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base, obj);
+			dma_resv_unlock(obj->resv);
+			if (IS_ERR(op->vm_bo))
+				return PTR_ERR(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		case OP_UNMAP_SPARSE:
 		case OP_MAP:
 		case OP_UNMAP:
-			bind_link_gpuvas(op->ops, &op->new);
+			bind_link_gpuvas(op);
 			break;
 		default:
 			break;
@@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct work_struct *work)
 		if (!IS_ERR_OR_NULL(op->ops))
 			drm_gpuva_ops_free(&uvmm->base, op->ops);
 
+		if (!IS_ERR_OR_NULL(op->vm_bo)) {
+			dma_resv_lock(obj->resv, NULL);
+			drm_gpuvm_bo_put(op->vm_bo);
+			dma_resv_unlock(obj->resv);
+		}
+
 		if (obj)
 			drm_gem_object_put(obj);
 	}
@@ -1752,15 +1773,18 @@ void
 nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem *mem)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_map(uvma, mem);
-		drm_gpuva_invalidate(va, false);
+			nouveau_uvma_map(uvma, mem);
+			drm_gpuva_invalidate(va, false);
+		}
 	}
 }
 
@@ -1768,15 +1792,18 @@ void
 nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
 {
 	struct drm_gem_object *obj = &nvbo->bo.base;
+	struct drm_gpuvm_bo *vm_bo;
 	struct drm_gpuva *va;
 
 	dma_resv_assert_held(obj->resv);
 
-	drm_gem_for_each_gpuva(va, obj) {
-		struct nouveau_uvma *uvma = uvma_from_va(va);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+		drm_gpuvm_bo_for_each_va(va, vm_bo) {
+			struct nouveau_uvma *uvma = uvma_from_va(va);
 
-		nouveau_uvma_unmap(uvma);
-		drm_gpuva_invalidate(va, true);
+			nouveau_uvma_unmap(uvma);
+			drm_gpuva_invalidate(va, true);
+		}
 	}
 }
 
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index 16364487fde9..369505447acd 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
  * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
  * @obj: the &drm_gem_object
  *
- * This initializes the &drm_gem_object's &drm_gpuva list.
+ * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
  *
  * Calling this function is only necessary for drivers intending to support the
  * &drm_driver_feature DRIVER_GEM_GPUVA.
@@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct drm_gem_object *obj)
 }
 
 /**
- * drm_gem_for_each_gpuva() - iternator to walk over a list of gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bo structure to assign to in each iteration step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
- * &drm_gpuva_manager.
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
+ * &drm_gem_object.
  */
-#define drm_gem_for_each_gpuva(entry__, obj__) \
-	list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
+	list_for_each_entry(entry__, &(obj__)->gpuva.list, list.entry.gem)
 
 /**
- * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a list of
- * gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @next__: &next &drm_gpuva to store the next step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a list of
+ * &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bostructure to assign to in each iteration step
+ * @next__: &next &drm_gpuvm_bo to store the next step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
  *
- * This iterator walks over all &drm_gpuva structures associated with the
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
  * &drm_gem_object. It is implemented with list_for_each_entry_safe(), hence
  * it is save against removal of elements.
  */
-#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
-	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
+	list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, list.entry.gem)
 
 #endif /* __DRM_GEM_H__ */
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 4e6e1fd3485a..b12fb22b0e22 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -25,6 +25,7 @@
  * OTHER DEALINGS IN THE SOFTWARE.
  */
 
+#include <linux/dma-resv.h>
 #include <linux/list.h>
 #include <linux/rbtree.h>
 #include <linux/types.h>
@@ -33,6 +34,7 @@
 #include <drm/drm_gem.h>
 
 struct drm_gpuvm;
+struct drm_gpuvm_bo;
 struct drm_gpuvm_ops;
 
 /**
@@ -73,6 +75,12 @@ struct drm_gpuva {
 	 */
 	struct drm_gpuvm *vm;
 
+	/**
+	 * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
+	 * &drm_gem_object
+	 */
+	struct drm_gpuvm_bo *vm_bo;
+
 	/**
 	 * @flags: the &drm_gpuva_flags for this mapping
 	 */
@@ -108,7 +116,7 @@ struct drm_gpuva {
 		struct drm_gem_object *obj;
 
 		/**
-		 * @entry: the &list_head to attach this object to a &drm_gem_object
+		 * @entry: the &list_head to attach this object to a &drm_gpuvm_bo
 		 */
 		struct list_head entry;
 	} gem;
@@ -141,7 +149,7 @@ struct drm_gpuva {
 int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
 void drm_gpuva_remove(struct drm_gpuva *va);
 
-void drm_gpuva_link(struct drm_gpuva *va);
+void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
 void drm_gpuva_unlink(struct drm_gpuva *va);
 
 struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
@@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
  * enum drm_gpuvm_flags - flags for struct drm_gpuvm
  */
 enum drm_gpuvm_flags {
+	/**
+	 * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally by the
+	 * GPUVM's &dma_resv lock
+	 */
+	DRM_GPUVM_RESV_PROTECTED = BIT(0),
+
 	/**
 	 * @DRM_GPUVM_USERBITS: user defined bits
 	 */
-	DRM_GPUVM_USERBITS = BIT(0),
+	DRM_GPUVM_USERBITS = BIT(1),
 };
 
 /**
@@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 struct drm_gem_object *
 drm_gpuvm_resv_object_alloc(struct drm_device *drm);
 
+/**
+ * drm_gpuvm_resv_protected() - indicates whether &DRM_GPUVM_RESV_PROTECTED is
+ * set
+ * @gpuvm: the &drm_gpuvm
+ *
+ * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false otherwise.
+ */
+static inline bool
+drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
+{
+	return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
+}
+
 /**
  * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
  * @gpuvm__: the &drm_gpuvm
@@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device *drm);
  */
 #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
 
+#define drm_gpuvm_resv_held(gpuvm__) \
+	dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
 #define drm_gpuvm_resv_held(gpuvm__) \
 	dma_resv_held(drm_gpuvm_resv(gpuvm__))
 
@@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
+ * &drm_gem_object combination
+ *
+ * This structure is an abstraction representing a &drm_gpuvm and
+ * &drm_gem_object combination. It serves as an indirection to accelerate
+ * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
+ * &drm_gem_object.
+ *
+ * Furthermore it is used cache evicted GEM objects for a certain GPU-VM to
+ * accelerate validation.
+ *
+ * Typically, drivers want to create an instance of a struct drm_gpuvm_bo once
+ * a GEM object is mapped first in a GPU-VM and release the instance once the
+ * last mapping of the GEM object in this GPU-VM is unmapped.
+ */
+struct drm_gpuvm_bo {
+	/**
+	 * @vm: The &drm_gpuvm the @obj is mapped in. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @obj: The &drm_gem_object being mapped in @vm. This is a reference
+	 * counted pointer.
+	 */
+	struct drm_gem_object *obj;
+
+	/**
+	 * @kref: The reference count for this &drm_gpuvm_bo.
+	 */
+	struct kref kref;
+
+	/**
+	 * @list: Structure containing all &list_heads.
+	 */
+	struct {
+		/**
+		 * @gpuva: The list of linked &drm_gpuvas.
+		 *
+		 * It is safe to access entries from this list as long as the
+		 * GEM's gpuva lock is held. See also struct drm_gem_object.
+		 */
+		struct list_head gpuva;
+
+		/**
+		 * @entry: Structure containing all &list_heads serving as
+		 * entry.
+		 */
+		struct {
+			/**
+			 * @gem: List entry to attach to the &drm_gem_objects
+			 * gpuva list.
+			 */
+			struct list_head gem;
+		} entry;
+	} list;
+};
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj);
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
+
+/**
+ * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
+ *
+ * This function acquires an additional reference to @vm_bo. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm_bo *
+drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+	kref_get(&vm_bo->kref);
+	return vm_bo;
+}
+
+void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+		  struct drm_gem_object *obj);
+
+/**
+ * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
+	list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
+
+/**
+ * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a list of
+ * &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @next__: &next &drm_gpuva to store the next step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(), hence
+ * it is save against removal of elements.
+ *
+ * The caller must hold the GEM's gpuva lock.
+ */
+#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
+	list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva, gem.entry)
+
 /**
  * enum drm_gpuva_op_type - GPU VA operation type
  *
@@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 				 u64 addr, u64 range);
 
 struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
-			       struct drm_gem_object *obj);
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
 
 void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
 			struct drm_gpuva_ops *ops);
@@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
 	 */
 	void (*op_free)(struct drm_gpuva_op *op);
 
+	/**
+	 * @vm_bo_alloc: called when the &drm_gpuvm allocates
+	 * a struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * allocate memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
+
+	/**
+	 * @vm_bo_free: called when the &drm_gpuvm frees a
+	 * struct drm_gpuvm_bo
+	 *
+	 * Some drivers may want to embed struct drm_gpuvm_bo into driver
+	 * specific structures. By implementing this callback drivers can
+	 * free the previously allocated memory accordingly.
+	 *
+	 * This callback is optional.
+	 */
+	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 11/12] drm/gpuvm: track/lock/validate external/evicted objects
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:31   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

Currently the DRM GPUVM offers common infrastructure to track GPU VA
allocations and mappings, generically connect GPU VA mappings to their
backing buffers and perform more complex mapping operations on the GPU VA
space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make the DRM GPUVM represent
a basis for GPU-VM implementations. In this context, this patch aims
at generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

Big thanks to Boris Brezillon for his help to figure out locking for
drivers updating the GPU VA space within the fence signalling path.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Suggested-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 633 ++++++++++++++++++++++++++++++++++++
 include/drm/drm_gpuvm.h     | 250 ++++++++++++++
 2 files changed, 883 insertions(+)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2c8fdefb19f0..d289282693ec 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -82,6 +82,21 @@
  * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
  * particular combination. If not existent a new instance is created and linked
  * to the &drm_gem_object.
+ *
+ * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
+ * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
+ * lists are maintained in order to accelerate locking of dma-resv locks and
+ * validation of evicted objects bound in a &drm_gpuvm. For instance, all
+ * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
+ * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
+ * order to validate all evicted &drm_gem_objects. It is also possible to lock
+ * additional &drm_gem_objects by providing the corresponding parameters to
+ * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
+ * use of helper functions such as drm_gpuvm_prepare_range() or
+ * drm_gpuvm_prepare_objects().
+ *
+ * Every bound &drm_gem_object is treated as external object when its &dma_resv
+ * structure is different than the &drm_gpuvm's common &dma_resv structure.
  */
 
 /**
@@ -429,6 +444,20 @@
  * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
  * &drm_gem_object must be able to observe previous creations and destructions
  * of &drm_gpuvm_bos in order to keep instances unique.
+ *
+ * The &drm_gpuvm's lists for keeping track of external and evicted objects are
+ * protected against concurrent insertion / removal and iteration internally.
+ *
+ * However, drivers still need ensure to protect concurrent calls to functions
+ * iterating those lists, namely drm_gpuvm_prepare_objects() and
+ * drm_gpuvm_validate().
+ *
+ * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
+ * that the corresponding &dma_resv locks are held in order to protect the
+ * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
+ * the corresponding lockdep checks are enabled. This is an optimization for
+ * drivers which are capable of taking the corresponding &dma_resv locks and
+ * hence do not require internal locking.
  */
 
 /**
@@ -641,6 +670,201 @@
  *	}
  */
 
+/**
+ * get_next_vm_bo_from_list() - get the next vm_bo element
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__prev_vm_bo: the previous element we got from get_next_vm_bo_from_list()
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from
+ * the list, so list insertion deletion can happen concurrently.
+ *
+ * Elements popped from the original list are kept in a local list, so removal
+ * and is_empty checks can still happen while we're iterating the list.
+ */
+#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)	\
+	({										\
+		struct drm_gpuvm_bo *__vm_bo = NULL;					\
+											\
+		drm_gpuvm_bo_put(__prev_vm_bo);						\
+											\
+		spin_lock(&(__gpuvm)->__list_name.lock);				\
+		if (!(__gpuvm)->__list_name.local_list)					\
+			(__gpuvm)->__list_name.local_list = __local_list;		\
+		else									\
+			drm_WARN_ON((__gpuvm)->drm,					\
+				    (__gpuvm)->__list_name.local_list != __local_list);	\
+											\
+		while (!list_empty(&(__gpuvm)->__list_name.list)) {			\
+			__vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,	\
+						   struct drm_gpuvm_bo,			\
+						   list.entry.__list_name);		\
+			if (kref_get_unless_zero(&__vm_bo->kref)) {			\
+				list_move_tail(&(__vm_bo)->list.entry.__list_name,	\
+					       __local_list);				\
+				break;							\
+			} else {							\
+				list_del_init(&(__vm_bo)->list.entry.__list_name);	\
+				__vm_bo = NULL;						\
+			}								\
+		}									\
+		spin_unlock(&(__gpuvm)->__list_name.lock);				\
+											\
+		__vm_bo;								\
+	})
+
+/**
+ * for_each_vm_bo_in_list() - internal vm_bo list iterator
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__vm_bo: the struct drm_gpuvm_bo to assign in each iteration step
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from the
+ * list, hence list insertion and deletion can happen concurrently.
+ *
+ * It is not allowed to re-assign the vm_bo pointer from inside this loop.
+ *
+ * Typical use:
+ *
+ *	struct drm_gpuvm_bo *vm_bo;
+ *	LIST_HEAD(my_local_list);
+ *
+ *	ret = 0;
+ *	for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
+ *		ret = do_something_with_vm_bo(..., vm_bo);
+ *		if (ret)
+ *			break;
+ *	}
+ *	// Drop ref in case we break out of the loop.
+ *	drm_gpuvm_bo_put(vm_bo);
+ *	restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
+ *
+ *
+ * Only used for internal list iterations, not meant to be exposed to the outside
+ * world.
+ */
+#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)	\
+	for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, NULL);		\
+	     __vm_bo;								\
+	     __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, __vm_bo))
+
+static void
+__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+		     struct list_head *list, struct list_head **local_list)
+{
+	/* Merge back the two lists, moving local list elements to the
+	 * head to preserve previous ordering, in case it matters.
+	 */
+	spin_lock(lock);
+	if (*local_list) {
+		list_splice(*local_list, list);
+		*local_list = NULL;
+	}
+	spin_unlock(lock);
+}
+
+/**
+ * restore_vm_bo_list() - move vm_bo elements back to their original list
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ *
+ * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
+ * to restore the original state and let new iterations take place.
+ */
+#define restore_vm_bo_list(__gpuvm, __list_name)			\
+	__restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,	\
+			     &(__gpuvm)->__list_name.list,		\
+			     &(__gpuvm)->__list_name.local_list)
+
+static void
+cond_spin_lock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_lock(lock);
+}
+
+static void
+cond_spin_unlock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_unlock(lock);
+}
+
+static void
+__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, struct list_head *list)
+{
+	cond_spin_lock(lock, !!lock);
+	if (list_empty(entry))
+		list_add_tail(entry, list);
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Inserts the given @__vm_bo into the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_add((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				&(__vm_bo)->vm->__list_name.list)
+
+static void
+__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, bool init)
+{
+	cond_spin_lock(lock, !!lock);
+	if (init) {
+		if (!list_empty(entry))
+			list_del_init(entry);
+	} else {
+		list_del(entry);
+	}
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)		\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				true)
+
+/**
+ * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				false)
+
 #define to_drm_gpuva(__node)	container_of((__node), struct drm_gpuva, rb.node)
 
 #define GPUVA_START(node) ((node)->va.addr)
@@ -781,6 +1005,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	INIT_LIST_HEAD(&gpuvm->extobj.list);
+	spin_lock_init(&gpuvm->extobj.lock);
+
+	INIT_LIST_HEAD(&gpuvm->evict.list);
+	spin_lock_init(&gpuvm->evict.lock);
+
 	kref_init(&gpuvm->kref);
 
 	gpuvm->name = name ? name : "unknown";
@@ -818,6 +1048,11 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
 
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->extobj.list),
+		 "Extobj list should be empty.\n");
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list),
+		 "Evict list should be empty.\n");
+
 	drm_gem_object_put(gpuvm->r_obj);
 }
 
@@ -848,6 +1083,343 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+static int
+__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(extobjs);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, extobj);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
+				 struct drm_exec *exec,
+				 unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+	list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+
+		if (vm_bo->evicted)
+			drm_gpuvm_bo_list_add(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Note: This function is safe against concurrent insertion and removal of
+ * external objects, however it is not safe against concurrent usage itself.
+ *
+ * Drivers need to make sure to protect this case with either an outer VM lock
+ * or by calling drm_gpuvm_prepare_vm() before this function within the
+ * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
+ * mutual exclusion.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			  struct drm_exec *exec,
+			  unsigned int num_fences)
+{
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
+							num_fences);
+	else
+		return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
+
+/**
+ * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
+ * and @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
+			u64 addr, u64 range, unsigned int num_fences)
+{
+	struct drm_gpuva *va;
+	u64 end = addr + range;
+	int ret;
+
+	drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
+		struct drm_gem_object *obj = va->gem.obj;
+
+		ret = drm_exec_prepare_obj(exec, obj, num_fences);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
+
+/**
+ * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Addionally, when calling this function with struct drm_gpuvm_exec::extra
+ * being set the driver receives the given @fn callback to lock additional
+ * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
+ * would call drm_exec_prepare_obj() from within this callback.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	unsigned int num_fences = vm_exec->num_fences;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		if (vm_exec->extra.fn) {
+			ret = vm_exec->extra.fn(vm_exec);
+			drm_exec_retry_on_contention(exec);
+			if (ret)
+				goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
+
+static int
+fn_lock_array(struct drm_gpuvm_exec *vm_exec)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} *args = vm_exec->extra.priv;
+
+	return drm_exec_prepare_array(&vm_exec->exec, args->objs,
+				      args->num_objs, vm_exec->num_fences);
+}
+
+/**
+ * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @objs: additional &drm_gem_objects to lock
+ * @num_objs: the number of additional &drm_gem_objects to lock
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
+ * contains mappings of, plus the ones given through @objs.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			  struct drm_gem_object **objs,
+			  unsigned int num_objs)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} args;
+
+	args.objs = objs;
+	args.num_objs = num_objs;
+
+	vm_exec->extra.fn = fn_lock_array;
+	vm_exec->extra.priv = &args;
+
+	return drm_gpuvm_exec_lock(vm_exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
+
+/**
+ * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
+ * @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			  u64 addr, u64 range)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
+					      vm_exec->num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+	}
+
+	return ret;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
+
+static int
+__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(evict);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, evict);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo, *next;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+
+	list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
+				 list.entry.evict) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+
+		dma_resv_assert_held(vm_bo->obj->resv);
+		if (!vm_bo->evicted)
+			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_validate() - validate all BOs marked as evicted
+ * @gpuvm: the &drm_gpuvm to validate evicted BOs
+ * @exec: the &drm_exec instance used for locking the GPUVM
+ *
+ * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
+ * objects being mapped in the given &drm_gpuvm.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+
+	if (unlikely(!ops || !ops->vm_bo_validate))
+		return -EOPNOTSUPP;
+
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_validate_locked(gpuvm, exec);
+	else
+		return __drm_gpuvm_validate(gpuvm, exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
+
+/**
+ * drm_gpuvm_resv_add_fence - add fence to private and all extobj
+ * dma-resv
+ * @gpuvm: the &drm_gpuvm to add a fence to
+ * @exec: the &drm_exec locking context
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ */
+void
+drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			 struct drm_exec *exec,
+			 struct dma_fence *fence,
+			 enum dma_resv_usage private_usage,
+			 enum dma_resv_usage extobj_usage)
+{
+	struct drm_gem_object *obj;
+	unsigned long index;
+
+	drm_exec_for_each_locked_object(exec, index, obj) {
+		dma_resv_assert_held(obj->resv);
+		dma_resv_add_fence(obj->resv, fence,
+				   drm_gpuvm_is_extobj(gpuvm, obj) ?
+				   extobj_usage : private_usage);
+	}
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
+
 /**
  * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
  * @gpuvm: The &drm_gpuvm the @obj is mapped in.
@@ -881,6 +1453,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
 	INIT_LIST_HEAD(&vm_bo->list.gpuva);
 	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
 
+	INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
+	INIT_LIST_HEAD(&vm_bo->list.entry.evict);
+
 	return vm_bo;
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
@@ -898,6 +1473,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
 	if (!lock)
 		drm_gpuvm_resv_assert_held(gpuvm);
 
+	drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
+	drm_gpuvm_bo_list_del(vm_bo, evict, lock);
+
 	drm_gem_gpuva_assert_lock_held(obj);
 	list_del(&vm_bo->list.entry.gem);
 
@@ -1037,6 +1615,61 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
 
+/**
+ * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
+ * extobj list
+ * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
+ *
+ * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
+ * already and if the corresponding &drm_gem_object is an external object,
+ * actually.
+ */
+void
+drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
+		drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
+
+/**
+ * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
+ * evicted list
+ * @vm_bo: the &drm_gpuvm_bo to add or remove
+ * @evict: indicates whether the object is evicted
+ *
+ * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
+ */
+void
+drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	dma_resv_assert_held(obj->resv);
+	vm_bo->evicted = evict;
+
+	/* Can't add external objects to the evicted list directly if not using
+	 * internal spinlocks, since in this case the evicted list is protected
+	 * with the VM's common dma-resv lock.
+	 */
+	if (drm_gpuvm_is_extobj(gpuvm, obj) && !lock)
+		return;
+
+	if (evict)
+		drm_gpuvm_bo_list_add(vm_bo, evict, lock);
+	else
+		drm_gpuvm_bo_list_del_init(vm_bo, evict, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_evict);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index b12fb22b0e22..8ca10461d8ac 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -32,6 +32,7 @@
 
 #include <drm/drm_device.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_exec.h>
 
 struct drm_gpuvm;
 struct drm_gpuvm_bo;
@@ -283,6 +284,50 @@ struct drm_gpuvm {
 	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
 	 */
 	struct drm_gem_object *r_obj;
+
+	/**
+	 * @extobj: structure holding the extobj list
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos serving as
+		 * external object
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the external object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the extobj list
+		 */
+		spinlock_t lock;
+	} extobj;
+
+	/**
+	 * @evict: structure holding the evict list and evict list lock
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos currently being
+		 * evicted
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the evicted object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the evict list
+		 */
+		spinlock_t lock;
+	} evict;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
@@ -359,6 +404,22 @@ drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
 #define drm_gpuvm_resv_assert_held(gpuvm__) \
 	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
 
+/**
+ * drm_gpuvm_is_extobj() - indicates whether the given &drm_gem_object is an
+ * external object
+ * @gpuvm: the &drm_gpuvm to check
+ * @obj: the &drm_gem_object to check
+ *
+ * Returns: true if the &drm_gem_object &dma_resv differs from the
+ * &drm_gpuvms &dma_resv, false otherwise
+ */
+static inline bool
+drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	return obj && obj->resv != drm_gpuvm_resv(gpuvm);
+}
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
@@ -437,6 +498,144 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_exec - &drm_gpuvm abstraction of &drm_exec
+ *
+ * This structure should be created on the stack as &drm_exec should be.
+ *
+ * Optionally, @extra can be set in order to lock additional &drm_gem_objects.
+ */
+struct drm_gpuvm_exec {
+	/**
+	 * @exec: the &drm_exec structure
+	 */
+	struct drm_exec exec;
+
+	/**
+	 * @flags: the flags for the struct drm_exec
+	 */
+	uint32_t flags;
+
+	/**
+	 * @vm: the &drm_gpuvm to lock its DMA reservations
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @num_fences: the number of fences to reserve for the &dma_resv of the
+	 * locked &drm_gem_objects
+	 */
+	unsigned int num_fences;
+
+	/**
+	 * @extra: Callback and corresponding private data for the driver to
+	 * lock arbitrary additional &drm_gem_objects.
+	 */
+	struct {
+		/**
+		 * @fn: The driver callback to lock additional &drm_gem_objects.
+		 */
+		int (*fn)(struct drm_gpuvm_exec *vm_exec);
+
+		/**
+		 * @priv: driver private data for the @fn callback
+		 */
+		void *priv;
+	} extra;
+};
+
+/**
+ * drm_gpuvm_prepare_vm() - prepare the GPUVMs common dma-resv
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for the GPUVMs dummy &drm_gem_object.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline int
+drm_gpuvm_prepare_vm(struct drm_gpuvm *gpuvm,
+		     struct drm_exec *exec,
+		     unsigned int num_fences)
+{
+	return drm_exec_prepare_obj(exec, gpuvm->r_obj, num_fences);
+}
+
+int drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      unsigned int num_fences);
+
+int drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    u64 addr, u64 range,
+			    unsigned int num_fences);
+
+int drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec);
+
+int drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			      struct drm_gem_object **objs,
+			      unsigned int num_objs);
+
+int drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			      u64 addr, u64 range);
+
+/**
+ * drm_gpuvm_exec_unlock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Releases all dma-resv locks of all &drm_gem_objects previously acquired
+ * through drm_gpuvm_exec_lock() or its variants.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline void
+drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
+{
+	drm_exec_fini(&vm_exec->exec);
+}
+
+int drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec);
+void drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage);
+
+/**
+ * drm_gpuvm_exec_resv_add_fence()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ *
+ * See drm_gpuvm_resv_add_fence().
+ */
+static inline void
+drm_gpuvm_exec_resv_add_fence(struct drm_gpuvm_exec *vm_exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage)
+{
+	drm_gpuvm_resv_add_fence(vm_exec->vm, &vm_exec->exec, fence,
+				 private_usage, extobj_usage);
+}
+
+/**
+ * drm_gpuvm_exec_validate()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * See drm_gpuvm_validate().
+ */
+static inline int
+drm_gpuvm_exec_validate(struct drm_gpuvm_exec *vm_exec)
+{
+	return drm_gpuvm_validate(vm_exec->vm, &vm_exec->exec);
+}
+
 /**
  * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
  * &drm_gem_object combination
@@ -466,6 +665,12 @@ struct drm_gpuvm_bo {
 	 */
 	struct drm_gem_object *obj;
 
+	/**
+	 * @evicted: Indicates whether the &drm_gem_object is evicted; field
+	 * protected by the &drm_gem_object's dma-resv lock.
+	 */
+	bool evicted;
+
 	/**
 	 * @kref: The reference count for this &drm_gpuvm_bo.
 	 */
@@ -493,6 +698,18 @@ struct drm_gpuvm_bo {
 			 * gpuva list.
 			 */
 			struct list_head gem;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms
+			 * extobj list.
+			 */
+			struct list_head extobj;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms evict
+			 * list.
+			 */
+			struct list_head evict;
 		} entry;
 	} list;
 };
@@ -527,6 +744,27 @@ struct drm_gpuvm_bo *
 drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
 		  struct drm_gem_object *obj);
 
+void drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict);
+
+/**
+ * drm_gpuvm_bo_gem_evict()
+ * @obj: the &drm_gem_object
+ * @evict: indicates whether @obj is evicted
+ *
+ * See drm_gpuvm_bo_evict().
+ */
+static inline void
+drm_gpuvm_bo_gem_evict(struct drm_gem_object *obj, bool evict)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		drm_gpuvm_bo_evict(vm_bo, evict);
+}
+
+void drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo);
+
 /**
  * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
  * @va__: &drm_gpuva structure to assign to in each iteration step
@@ -901,6 +1139,18 @@ struct drm_gpuvm_ops {
 	 */
 	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
 
+	/**
+	 * @vm_bo_validate: called from drm_gpuvm_validate()
+	 *
+	 * Drivers receive this callback for every evicted &drm_gem_object being
+	 * mapped in the corresponding &drm_gpuvm.
+	 *
+	 * Typically, drivers would call their driver specific variant of
+	 * ttm_bo_validate() from within this callback.
+	 */
+	int (*vm_bo_validate)(struct drm_gpuvm_bo *vm_bo,
+			      struct drm_exec *exec);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 11/12] drm/gpuvm: track/lock/validate external/evicted objects
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

Currently the DRM GPUVM offers common infrastructure to track GPU VA
allocations and mappings, generically connect GPU VA mappings to their
backing buffers and perform more complex mapping operations on the GPU VA
space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make the DRM GPUVM represent
a basis for GPU-VM implementations. In this context, this patch aims
at generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

Big thanks to Boris Brezillon for his help to figure out locking for
drivers updating the GPU VA space within the fence signalling path.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Suggested-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 633 ++++++++++++++++++++++++++++++++++++
 include/drm/drm_gpuvm.h     | 250 ++++++++++++++
 2 files changed, 883 insertions(+)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2c8fdefb19f0..d289282693ec 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -82,6 +82,21 @@
  * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
  * particular combination. If not existent a new instance is created and linked
  * to the &drm_gem_object.
+ *
+ * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
+ * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
+ * lists are maintained in order to accelerate locking of dma-resv locks and
+ * validation of evicted objects bound in a &drm_gpuvm. For instance, all
+ * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
+ * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
+ * order to validate all evicted &drm_gem_objects. It is also possible to lock
+ * additional &drm_gem_objects by providing the corresponding parameters to
+ * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
+ * use of helper functions such as drm_gpuvm_prepare_range() or
+ * drm_gpuvm_prepare_objects().
+ *
+ * Every bound &drm_gem_object is treated as external object when its &dma_resv
+ * structure is different than the &drm_gpuvm's common &dma_resv structure.
  */
 
 /**
@@ -429,6 +444,20 @@
  * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
  * &drm_gem_object must be able to observe previous creations and destructions
  * of &drm_gpuvm_bos in order to keep instances unique.
+ *
+ * The &drm_gpuvm's lists for keeping track of external and evicted objects are
+ * protected against concurrent insertion / removal and iteration internally.
+ *
+ * However, drivers still need ensure to protect concurrent calls to functions
+ * iterating those lists, namely drm_gpuvm_prepare_objects() and
+ * drm_gpuvm_validate().
+ *
+ * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
+ * that the corresponding &dma_resv locks are held in order to protect the
+ * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
+ * the corresponding lockdep checks are enabled. This is an optimization for
+ * drivers which are capable of taking the corresponding &dma_resv locks and
+ * hence do not require internal locking.
  */
 
 /**
@@ -641,6 +670,201 @@
  *	}
  */
 
+/**
+ * get_next_vm_bo_from_list() - get the next vm_bo element
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__prev_vm_bo: the previous element we got from get_next_vm_bo_from_list()
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from
+ * the list, so list insertion deletion can happen concurrently.
+ *
+ * Elements popped from the original list are kept in a local list, so removal
+ * and is_empty checks can still happen while we're iterating the list.
+ */
+#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)	\
+	({										\
+		struct drm_gpuvm_bo *__vm_bo = NULL;					\
+											\
+		drm_gpuvm_bo_put(__prev_vm_bo);						\
+											\
+		spin_lock(&(__gpuvm)->__list_name.lock);				\
+		if (!(__gpuvm)->__list_name.local_list)					\
+			(__gpuvm)->__list_name.local_list = __local_list;		\
+		else									\
+			drm_WARN_ON((__gpuvm)->drm,					\
+				    (__gpuvm)->__list_name.local_list != __local_list);	\
+											\
+		while (!list_empty(&(__gpuvm)->__list_name.list)) {			\
+			__vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,	\
+						   struct drm_gpuvm_bo,			\
+						   list.entry.__list_name);		\
+			if (kref_get_unless_zero(&__vm_bo->kref)) {			\
+				list_move_tail(&(__vm_bo)->list.entry.__list_name,	\
+					       __local_list);				\
+				break;							\
+			} else {							\
+				list_del_init(&(__vm_bo)->list.entry.__list_name);	\
+				__vm_bo = NULL;						\
+			}								\
+		}									\
+		spin_unlock(&(__gpuvm)->__list_name.lock);				\
+											\
+		__vm_bo;								\
+	})
+
+/**
+ * for_each_vm_bo_in_list() - internal vm_bo list iterator
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__vm_bo: the struct drm_gpuvm_bo to assign in each iteration step
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from the
+ * list, hence list insertion and deletion can happen concurrently.
+ *
+ * It is not allowed to re-assign the vm_bo pointer from inside this loop.
+ *
+ * Typical use:
+ *
+ *	struct drm_gpuvm_bo *vm_bo;
+ *	LIST_HEAD(my_local_list);
+ *
+ *	ret = 0;
+ *	for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
+ *		ret = do_something_with_vm_bo(..., vm_bo);
+ *		if (ret)
+ *			break;
+ *	}
+ *	// Drop ref in case we break out of the loop.
+ *	drm_gpuvm_bo_put(vm_bo);
+ *	restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
+ *
+ *
+ * Only used for internal list iterations, not meant to be exposed to the outside
+ * world.
+ */
+#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)	\
+	for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, NULL);		\
+	     __vm_bo;								\
+	     __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, __vm_bo))
+
+static void
+__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+		     struct list_head *list, struct list_head **local_list)
+{
+	/* Merge back the two lists, moving local list elements to the
+	 * head to preserve previous ordering, in case it matters.
+	 */
+	spin_lock(lock);
+	if (*local_list) {
+		list_splice(*local_list, list);
+		*local_list = NULL;
+	}
+	spin_unlock(lock);
+}
+
+/**
+ * restore_vm_bo_list() - move vm_bo elements back to their original list
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ *
+ * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
+ * to restore the original state and let new iterations take place.
+ */
+#define restore_vm_bo_list(__gpuvm, __list_name)			\
+	__restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,	\
+			     &(__gpuvm)->__list_name.list,		\
+			     &(__gpuvm)->__list_name.local_list)
+
+static void
+cond_spin_lock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_lock(lock);
+}
+
+static void
+cond_spin_unlock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_unlock(lock);
+}
+
+static void
+__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, struct list_head *list)
+{
+	cond_spin_lock(lock, !!lock);
+	if (list_empty(entry))
+		list_add_tail(entry, list);
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Inserts the given @__vm_bo into the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_add((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				&(__vm_bo)->vm->__list_name.list)
+
+static void
+__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, bool init)
+{
+	cond_spin_lock(lock, !!lock);
+	if (init) {
+		if (!list_empty(entry))
+			list_del_init(entry);
+	} else {
+		list_del(entry);
+	}
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)		\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				true)
+
+/**
+ * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				false)
+
 #define to_drm_gpuva(__node)	container_of((__node), struct drm_gpuva, rb.node)
 
 #define GPUVA_START(node) ((node)->va.addr)
@@ -781,6 +1005,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	INIT_LIST_HEAD(&gpuvm->extobj.list);
+	spin_lock_init(&gpuvm->extobj.lock);
+
+	INIT_LIST_HEAD(&gpuvm->evict.list);
+	spin_lock_init(&gpuvm->evict.lock);
+
 	kref_init(&gpuvm->kref);
 
 	gpuvm->name = name ? name : "unknown";
@@ -818,6 +1048,11 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
 
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->extobj.list),
+		 "Extobj list should be empty.\n");
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list),
+		 "Evict list should be empty.\n");
+
 	drm_gem_object_put(gpuvm->r_obj);
 }
 
@@ -848,6 +1083,343 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+static int
+__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(extobjs);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, extobj);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
+				 struct drm_exec *exec,
+				 unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+	list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+
+		if (vm_bo->evicted)
+			drm_gpuvm_bo_list_add(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Note: This function is safe against concurrent insertion and removal of
+ * external objects, however it is not safe against concurrent usage itself.
+ *
+ * Drivers need to make sure to protect this case with either an outer VM lock
+ * or by calling drm_gpuvm_prepare_vm() before this function within the
+ * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
+ * mutual exclusion.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			  struct drm_exec *exec,
+			  unsigned int num_fences)
+{
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
+							num_fences);
+	else
+		return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
+
+/**
+ * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
+ * and @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
+			u64 addr, u64 range, unsigned int num_fences)
+{
+	struct drm_gpuva *va;
+	u64 end = addr + range;
+	int ret;
+
+	drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
+		struct drm_gem_object *obj = va->gem.obj;
+
+		ret = drm_exec_prepare_obj(exec, obj, num_fences);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
+
+/**
+ * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Addionally, when calling this function with struct drm_gpuvm_exec::extra
+ * being set the driver receives the given @fn callback to lock additional
+ * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
+ * would call drm_exec_prepare_obj() from within this callback.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	unsigned int num_fences = vm_exec->num_fences;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		if (vm_exec->extra.fn) {
+			ret = vm_exec->extra.fn(vm_exec);
+			drm_exec_retry_on_contention(exec);
+			if (ret)
+				goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
+
+static int
+fn_lock_array(struct drm_gpuvm_exec *vm_exec)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} *args = vm_exec->extra.priv;
+
+	return drm_exec_prepare_array(&vm_exec->exec, args->objs,
+				      args->num_objs, vm_exec->num_fences);
+}
+
+/**
+ * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @objs: additional &drm_gem_objects to lock
+ * @num_objs: the number of additional &drm_gem_objects to lock
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
+ * contains mappings of, plus the ones given through @objs.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			  struct drm_gem_object **objs,
+			  unsigned int num_objs)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} args;
+
+	args.objs = objs;
+	args.num_objs = num_objs;
+
+	vm_exec->extra.fn = fn_lock_array;
+	vm_exec->extra.priv = &args;
+
+	return drm_gpuvm_exec_lock(vm_exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
+
+/**
+ * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
+ * @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			  u64 addr, u64 range)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
+					      vm_exec->num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+	}
+
+	return ret;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
+
+static int
+__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(evict);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, evict);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo, *next;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+
+	list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
+				 list.entry.evict) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+
+		dma_resv_assert_held(vm_bo->obj->resv);
+		if (!vm_bo->evicted)
+			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_validate() - validate all BOs marked as evicted
+ * @gpuvm: the &drm_gpuvm to validate evicted BOs
+ * @exec: the &drm_exec instance used for locking the GPUVM
+ *
+ * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
+ * objects being mapped in the given &drm_gpuvm.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+
+	if (unlikely(!ops || !ops->vm_bo_validate))
+		return -EOPNOTSUPP;
+
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_validate_locked(gpuvm, exec);
+	else
+		return __drm_gpuvm_validate(gpuvm, exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
+
+/**
+ * drm_gpuvm_resv_add_fence - add fence to private and all extobj
+ * dma-resv
+ * @gpuvm: the &drm_gpuvm to add a fence to
+ * @exec: the &drm_exec locking context
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ */
+void
+drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			 struct drm_exec *exec,
+			 struct dma_fence *fence,
+			 enum dma_resv_usage private_usage,
+			 enum dma_resv_usage extobj_usage)
+{
+	struct drm_gem_object *obj;
+	unsigned long index;
+
+	drm_exec_for_each_locked_object(exec, index, obj) {
+		dma_resv_assert_held(obj->resv);
+		dma_resv_add_fence(obj->resv, fence,
+				   drm_gpuvm_is_extobj(gpuvm, obj) ?
+				   extobj_usage : private_usage);
+	}
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
+
 /**
  * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
  * @gpuvm: The &drm_gpuvm the @obj is mapped in.
@@ -881,6 +1453,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
 	INIT_LIST_HEAD(&vm_bo->list.gpuva);
 	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
 
+	INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
+	INIT_LIST_HEAD(&vm_bo->list.entry.evict);
+
 	return vm_bo;
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
@@ -898,6 +1473,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
 	if (!lock)
 		drm_gpuvm_resv_assert_held(gpuvm);
 
+	drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
+	drm_gpuvm_bo_list_del(vm_bo, evict, lock);
+
 	drm_gem_gpuva_assert_lock_held(obj);
 	list_del(&vm_bo->list.entry.gem);
 
@@ -1037,6 +1615,61 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
 
+/**
+ * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
+ * extobj list
+ * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
+ *
+ * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
+ * already and if the corresponding &drm_gem_object is an external object,
+ * actually.
+ */
+void
+drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
+		drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
+
+/**
+ * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
+ * evicted list
+ * @vm_bo: the &drm_gpuvm_bo to add or remove
+ * @evict: indicates whether the object is evicted
+ *
+ * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
+ */
+void
+drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	dma_resv_assert_held(obj->resv);
+	vm_bo->evicted = evict;
+
+	/* Can't add external objects to the evicted list directly if not using
+	 * internal spinlocks, since in this case the evicted list is protected
+	 * with the VM's common dma-resv lock.
+	 */
+	if (drm_gpuvm_is_extobj(gpuvm, obj) && !lock)
+		return;
+
+	if (evict)
+		drm_gpuvm_bo_list_add(vm_bo, evict, lock);
+	else
+		drm_gpuvm_bo_list_del_init(vm_bo, evict, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_evict);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index b12fb22b0e22..8ca10461d8ac 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -32,6 +32,7 @@
 
 #include <drm/drm_device.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_exec.h>
 
 struct drm_gpuvm;
 struct drm_gpuvm_bo;
@@ -283,6 +284,50 @@ struct drm_gpuvm {
 	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
 	 */
 	struct drm_gem_object *r_obj;
+
+	/**
+	 * @extobj: structure holding the extobj list
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos serving as
+		 * external object
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the external object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the extobj list
+		 */
+		spinlock_t lock;
+	} extobj;
+
+	/**
+	 * @evict: structure holding the evict list and evict list lock
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos currently being
+		 * evicted
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the evicted object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the evict list
+		 */
+		spinlock_t lock;
+	} evict;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
@@ -359,6 +404,22 @@ drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
 #define drm_gpuvm_resv_assert_held(gpuvm__) \
 	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
 
+/**
+ * drm_gpuvm_is_extobj() - indicates whether the given &drm_gem_object is an
+ * external object
+ * @gpuvm: the &drm_gpuvm to check
+ * @obj: the &drm_gem_object to check
+ *
+ * Returns: true if the &drm_gem_object &dma_resv differs from the
+ * &drm_gpuvms &dma_resv, false otherwise
+ */
+static inline bool
+drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	return obj && obj->resv != drm_gpuvm_resv(gpuvm);
+}
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
@@ -437,6 +498,144 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_exec - &drm_gpuvm abstraction of &drm_exec
+ *
+ * This structure should be created on the stack as &drm_exec should be.
+ *
+ * Optionally, @extra can be set in order to lock additional &drm_gem_objects.
+ */
+struct drm_gpuvm_exec {
+	/**
+	 * @exec: the &drm_exec structure
+	 */
+	struct drm_exec exec;
+
+	/**
+	 * @flags: the flags for the struct drm_exec
+	 */
+	uint32_t flags;
+
+	/**
+	 * @vm: the &drm_gpuvm to lock its DMA reservations
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @num_fences: the number of fences to reserve for the &dma_resv of the
+	 * locked &drm_gem_objects
+	 */
+	unsigned int num_fences;
+
+	/**
+	 * @extra: Callback and corresponding private data for the driver to
+	 * lock arbitrary additional &drm_gem_objects.
+	 */
+	struct {
+		/**
+		 * @fn: The driver callback to lock additional &drm_gem_objects.
+		 */
+		int (*fn)(struct drm_gpuvm_exec *vm_exec);
+
+		/**
+		 * @priv: driver private data for the @fn callback
+		 */
+		void *priv;
+	} extra;
+};
+
+/**
+ * drm_gpuvm_prepare_vm() - prepare the GPUVMs common dma-resv
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for the GPUVMs dummy &drm_gem_object.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline int
+drm_gpuvm_prepare_vm(struct drm_gpuvm *gpuvm,
+		     struct drm_exec *exec,
+		     unsigned int num_fences)
+{
+	return drm_exec_prepare_obj(exec, gpuvm->r_obj, num_fences);
+}
+
+int drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      unsigned int num_fences);
+
+int drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    u64 addr, u64 range,
+			    unsigned int num_fences);
+
+int drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec);
+
+int drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			      struct drm_gem_object **objs,
+			      unsigned int num_objs);
+
+int drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			      u64 addr, u64 range);
+
+/**
+ * drm_gpuvm_exec_unlock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Releases all dma-resv locks of all &drm_gem_objects previously acquired
+ * through drm_gpuvm_exec_lock() or its variants.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline void
+drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
+{
+	drm_exec_fini(&vm_exec->exec);
+}
+
+int drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec);
+void drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage);
+
+/**
+ * drm_gpuvm_exec_resv_add_fence()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ *
+ * See drm_gpuvm_resv_add_fence().
+ */
+static inline void
+drm_gpuvm_exec_resv_add_fence(struct drm_gpuvm_exec *vm_exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage)
+{
+	drm_gpuvm_resv_add_fence(vm_exec->vm, &vm_exec->exec, fence,
+				 private_usage, extobj_usage);
+}
+
+/**
+ * drm_gpuvm_exec_validate()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * See drm_gpuvm_validate().
+ */
+static inline int
+drm_gpuvm_exec_validate(struct drm_gpuvm_exec *vm_exec)
+{
+	return drm_gpuvm_validate(vm_exec->vm, &vm_exec->exec);
+}
+
 /**
  * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
  * &drm_gem_object combination
@@ -466,6 +665,12 @@ struct drm_gpuvm_bo {
 	 */
 	struct drm_gem_object *obj;
 
+	/**
+	 * @evicted: Indicates whether the &drm_gem_object is evicted; field
+	 * protected by the &drm_gem_object's dma-resv lock.
+	 */
+	bool evicted;
+
 	/**
 	 * @kref: The reference count for this &drm_gpuvm_bo.
 	 */
@@ -493,6 +698,18 @@ struct drm_gpuvm_bo {
 			 * gpuva list.
 			 */
 			struct list_head gem;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms
+			 * extobj list.
+			 */
+			struct list_head extobj;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms evict
+			 * list.
+			 */
+			struct list_head evict;
 		} entry;
 	} list;
 };
@@ -527,6 +744,27 @@ struct drm_gpuvm_bo *
 drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
 		  struct drm_gem_object *obj);
 
+void drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict);
+
+/**
+ * drm_gpuvm_bo_gem_evict()
+ * @obj: the &drm_gem_object
+ * @evict: indicates whether @obj is evicted
+ *
+ * See drm_gpuvm_bo_evict().
+ */
+static inline void
+drm_gpuvm_bo_gem_evict(struct drm_gem_object *obj, bool evict)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		drm_gpuvm_bo_evict(vm_bo, evict);
+}
+
+void drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo);
+
 /**
  * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
  * @va__: &drm_gpuva structure to assign to in each iteration step
@@ -901,6 +1139,18 @@ struct drm_gpuvm_ops {
 	 */
 	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
 
+	/**
+	 * @vm_bo_validate: called from drm_gpuvm_validate()
+	 *
+	 * Drivers receive this callback for every evicted &drm_gem_object being
+	 * mapped in the corresponding &drm_gpuvm.
+	 *
+	 * Typically, drivers would call their driver specific variant of
+	 * ttm_bo_validate() from within this callback.
+	 */
+	int (*vm_bo_validate)(struct drm_gpuvm_bo *vm_bo,
+			      struct drm_exec *exec);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 11/12] drm/gpuvm: track/lock/validate external/evicted objects
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

Currently the DRM GPUVM offers common infrastructure to track GPU VA
allocations and mappings, generically connect GPU VA mappings to their
backing buffers and perform more complex mapping operations on the GPU VA
space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make the DRM GPUVM represent
a basis for GPU-VM implementations. In this context, this patch aims
at generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
   this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
   shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
   GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
   of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

Big thanks to Boris Brezillon for his help to figure out locking for
drivers updating the GPU VA space within the fence signalling path.

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Suggested-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/drm_gpuvm.c | 633 ++++++++++++++++++++++++++++++++++++
 include/drm/drm_gpuvm.h     | 250 ++++++++++++++
 2 files changed, 883 insertions(+)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 2c8fdefb19f0..d289282693ec 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -82,6 +82,21 @@
  * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
  * particular combination. If not existent a new instance is created and linked
  * to the &drm_gem_object.
+ *
+ * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
+ * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
+ * lists are maintained in order to accelerate locking of dma-resv locks and
+ * validation of evicted objects bound in a &drm_gpuvm. For instance, all
+ * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
+ * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
+ * order to validate all evicted &drm_gem_objects. It is also possible to lock
+ * additional &drm_gem_objects by providing the corresponding parameters to
+ * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
+ * use of helper functions such as drm_gpuvm_prepare_range() or
+ * drm_gpuvm_prepare_objects().
+ *
+ * Every bound &drm_gem_object is treated as external object when its &dma_resv
+ * structure is different than the &drm_gpuvm's common &dma_resv structure.
  */
 
 /**
@@ -429,6 +444,20 @@
  * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
  * &drm_gem_object must be able to observe previous creations and destructions
  * of &drm_gpuvm_bos in order to keep instances unique.
+ *
+ * The &drm_gpuvm's lists for keeping track of external and evicted objects are
+ * protected against concurrent insertion / removal and iteration internally.
+ *
+ * However, drivers still need ensure to protect concurrent calls to functions
+ * iterating those lists, namely drm_gpuvm_prepare_objects() and
+ * drm_gpuvm_validate().
+ *
+ * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
+ * that the corresponding &dma_resv locks are held in order to protect the
+ * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
+ * the corresponding lockdep checks are enabled. This is an optimization for
+ * drivers which are capable of taking the corresponding &dma_resv locks and
+ * hence do not require internal locking.
  */
 
 /**
@@ -641,6 +670,201 @@
  *	}
  */
 
+/**
+ * get_next_vm_bo_from_list() - get the next vm_bo element
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__prev_vm_bo: the previous element we got from get_next_vm_bo_from_list()
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from
+ * the list, so list insertion deletion can happen concurrently.
+ *
+ * Elements popped from the original list are kept in a local list, so removal
+ * and is_empty checks can still happen while we're iterating the list.
+ */
+#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)	\
+	({										\
+		struct drm_gpuvm_bo *__vm_bo = NULL;					\
+											\
+		drm_gpuvm_bo_put(__prev_vm_bo);						\
+											\
+		spin_lock(&(__gpuvm)->__list_name.lock);				\
+		if (!(__gpuvm)->__list_name.local_list)					\
+			(__gpuvm)->__list_name.local_list = __local_list;		\
+		else									\
+			drm_WARN_ON((__gpuvm)->drm,					\
+				    (__gpuvm)->__list_name.local_list != __local_list);	\
+											\
+		while (!list_empty(&(__gpuvm)->__list_name.list)) {			\
+			__vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,	\
+						   struct drm_gpuvm_bo,			\
+						   list.entry.__list_name);		\
+			if (kref_get_unless_zero(&__vm_bo->kref)) {			\
+				list_move_tail(&(__vm_bo)->list.entry.__list_name,	\
+					       __local_list);				\
+				break;							\
+			} else {							\
+				list_del_init(&(__vm_bo)->list.entry.__list_name);	\
+				__vm_bo = NULL;						\
+			}								\
+		}									\
+		spin_unlock(&(__gpuvm)->__list_name.lock);				\
+											\
+		__vm_bo;								\
+	})
+
+/**
+ * for_each_vm_bo_in_list() - internal vm_bo list iterator
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ * @__local_list: a pointer to the local list used to store already iterated items
+ * @__vm_bo: the struct drm_gpuvm_bo to assign in each iteration step
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from the
+ * list, hence list insertion and deletion can happen concurrently.
+ *
+ * It is not allowed to re-assign the vm_bo pointer from inside this loop.
+ *
+ * Typical use:
+ *
+ *	struct drm_gpuvm_bo *vm_bo;
+ *	LIST_HEAD(my_local_list);
+ *
+ *	ret = 0;
+ *	for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
+ *		ret = do_something_with_vm_bo(..., vm_bo);
+ *		if (ret)
+ *			break;
+ *	}
+ *	// Drop ref in case we break out of the loop.
+ *	drm_gpuvm_bo_put(vm_bo);
+ *	restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
+ *
+ *
+ * Only used for internal list iterations, not meant to be exposed to the outside
+ * world.
+ */
+#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)	\
+	for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, NULL);		\
+	     __vm_bo;								\
+	     __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,		\
+						__local_list, __vm_bo))
+
+static void
+__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+		     struct list_head *list, struct list_head **local_list)
+{
+	/* Merge back the two lists, moving local list elements to the
+	 * head to preserve previous ordering, in case it matters.
+	 */
+	spin_lock(lock);
+	if (*local_list) {
+		list_splice(*local_list, list);
+		*local_list = NULL;
+	}
+	spin_unlock(lock);
+}
+
+/**
+ * restore_vm_bo_list() - move vm_bo elements back to their original list
+ * @__gpuvm: the &drm_gpuvm
+ * @__list_name: the name of the list we're iterating on
+ *
+ * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
+ * to restore the original state and let new iterations take place.
+ */
+#define restore_vm_bo_list(__gpuvm, __list_name)			\
+	__restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,	\
+			     &(__gpuvm)->__list_name.list,		\
+			     &(__gpuvm)->__list_name.local_list)
+
+static void
+cond_spin_lock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_lock(lock);
+}
+
+static void
+cond_spin_unlock(spinlock_t *lock, bool cond)
+{
+	if (cond)
+		spin_unlock(lock);
+}
+
+static void
+__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, struct list_head *list)
+{
+	cond_spin_lock(lock, !!lock);
+	if (list_empty(entry))
+		list_add_tail(entry, list);
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Inserts the given @__vm_bo into the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_add((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				&(__vm_bo)->vm->__list_name.list)
+
+static void
+__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+			struct list_head *entry, bool init)
+{
+	cond_spin_lock(lock, !!lock);
+	if (init) {
+		if (!list_empty(entry))
+			list_del_init(entry);
+	} else {
+		list_del(entry);
+	}
+	cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)		\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				true)
+
+/**
+ * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)			\
+	__drm_gpuvm_bo_list_del((__vm_bo)->vm,					\
+				__lock ? &(__vm_bo)->vm->__list_name.lock :	\
+					 NULL,					\
+				&(__vm_bo)->list.entry.__list_name,		\
+				false)
+
 #define to_drm_gpuva(__node)	container_of((__node), struct drm_gpuva, rb.node)
 
 #define GPUVA_START(node) ((node)->va.addr)
@@ -781,6 +1005,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 	gpuvm->rb.tree = RB_ROOT_CACHED;
 	INIT_LIST_HEAD(&gpuvm->rb.list);
 
+	INIT_LIST_HEAD(&gpuvm->extobj.list);
+	spin_lock_init(&gpuvm->extobj.lock);
+
+	INIT_LIST_HEAD(&gpuvm->evict.list);
+	spin_lock_init(&gpuvm->evict.lock);
+
 	kref_init(&gpuvm->kref);
 
 	gpuvm->name = name ? name : "unknown";
@@ -818,6 +1048,11 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 	drm_WARN(gpuvm->drm, !RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
 		 "GPUVA tree is not empty, potentially leaking memory.\n");
 
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->extobj.list),
+		 "Extobj list should be empty.\n");
+	drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list),
+		 "Evict list should be empty.\n");
+
 	drm_gem_object_put(gpuvm->r_obj);
 }
 
@@ -848,6 +1083,343 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
+static int
+__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(extobjs);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, extobj);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
+				 struct drm_exec *exec,
+				 unsigned int num_fences)
+{
+	struct drm_gpuvm_bo *vm_bo;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+	list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
+		ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+		if (ret)
+			break;
+
+		if (vm_bo->evicted)
+			drm_gpuvm_bo_list_add(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Note: This function is safe against concurrent insertion and removal of
+ * external objects, however it is not safe against concurrent usage itself.
+ *
+ * Drivers need to make sure to protect this case with either an outer VM lock
+ * or by calling drm_gpuvm_prepare_vm() before this function within the
+ * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
+ * mutual exclusion.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			  struct drm_exec *exec,
+			  unsigned int num_fences)
+{
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
+							num_fences);
+	else
+		return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
+
+/**
+ * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
+ * and @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
+			u64 addr, u64 range, unsigned int num_fences)
+{
+	struct drm_gpuva *va;
+	u64 end = addr + range;
+	int ret;
+
+	drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
+		struct drm_gem_object *obj = va->gem.obj;
+
+		ret = drm_exec_prepare_obj(exec, obj, num_fences);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
+
+/**
+ * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Addionally, when calling this function with struct drm_gpuvm_exec::extra
+ * being set the driver receives the given @fn callback to lock additional
+ * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
+ * would call drm_exec_prepare_obj() from within this callback.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	unsigned int num_fences = vm_exec->num_fences;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+
+		if (vm_exec->extra.fn) {
+			ret = vm_exec->extra.fn(vm_exec);
+			drm_exec_retry_on_contention(exec);
+			if (ret)
+				goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
+
+static int
+fn_lock_array(struct drm_gpuvm_exec *vm_exec)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} *args = vm_exec->extra.priv;
+
+	return drm_exec_prepare_array(&vm_exec->exec, args->objs,
+				      args->num_objs, vm_exec->num_fences);
+}
+
+/**
+ * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @objs: additional &drm_gem_objects to lock
+ * @num_objs: the number of additional &drm_gem_objects to lock
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
+ * contains mappings of, plus the ones given through @objs.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			  struct drm_gem_object **objs,
+			  unsigned int num_objs)
+{
+	struct {
+		struct drm_gem_object **objs;
+		unsigned int num_objs;
+	} args;
+
+	args.objs = objs;
+	args.num_objs = num_objs;
+
+	vm_exec->extra.fn = fn_lock_array;
+	vm_exec->extra.priv = &args;
+
+	return drm_gpuvm_exec_lock(vm_exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
+
+/**
+ * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
+ * @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			  u64 addr, u64 range)
+{
+	struct drm_gpuvm *gpuvm = vm_exec->vm;
+	struct drm_exec *exec = &vm_exec->exec;
+	int ret;
+
+	drm_exec_init(exec, vm_exec->flags);
+
+	drm_exec_until_all_locked(exec) {
+		ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
+					      vm_exec->num_fences);
+		drm_exec_retry_on_contention(exec);
+		if (ret)
+			goto err;
+	}
+
+	return ret;
+
+err:
+	drm_exec_fini(exec);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
+
+static int
+__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo;
+	LIST_HEAD(evict);
+	int ret = 0;
+
+	for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+	}
+	/* Drop ref in case we break out of the loop. */
+	drm_gpuvm_bo_put(vm_bo);
+	restore_vm_bo_list(gpuvm, evict);
+
+	return ret;
+}
+
+static int
+drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+	struct drm_gpuvm_bo *vm_bo, *next;
+	int ret = 0;
+
+	drm_gpuvm_resv_assert_held(gpuvm);
+
+	list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
+				 list.entry.evict) {
+		ret = ops->vm_bo_validate(vm_bo, exec);
+		if (ret)
+			break;
+
+		dma_resv_assert_held(vm_bo->obj->resv);
+		if (!vm_bo->evicted)
+			drm_gpuvm_bo_list_del_init(vm_bo, evict, false);
+	}
+
+	return ret;
+}
+
+/**
+ * drm_gpuvm_validate() - validate all BOs marked as evicted
+ * @gpuvm: the &drm_gpuvm to validate evicted BOs
+ * @exec: the &drm_exec instance used for locking the GPUVM
+ *
+ * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
+ * objects being mapped in the given &drm_gpuvm.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+	const struct drm_gpuvm_ops *ops = gpuvm->ops;
+
+	if (unlikely(!ops || !ops->vm_bo_validate))
+		return -EOPNOTSUPP;
+
+	if (drm_gpuvm_resv_protected(gpuvm))
+		return drm_gpuvm_validate_locked(gpuvm, exec);
+	else
+		return __drm_gpuvm_validate(gpuvm, exec);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
+
+/**
+ * drm_gpuvm_resv_add_fence - add fence to private and all extobj
+ * dma-resv
+ * @gpuvm: the &drm_gpuvm to add a fence to
+ * @exec: the &drm_exec locking context
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ */
+void
+drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			 struct drm_exec *exec,
+			 struct dma_fence *fence,
+			 enum dma_resv_usage private_usage,
+			 enum dma_resv_usage extobj_usage)
+{
+	struct drm_gem_object *obj;
+	unsigned long index;
+
+	drm_exec_for_each_locked_object(exec, index, obj) {
+		dma_resv_assert_held(obj->resv);
+		dma_resv_add_fence(obj->resv, fence,
+				   drm_gpuvm_is_extobj(gpuvm, obj) ?
+				   extobj_usage : private_usage);
+	}
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
+
 /**
  * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
  * @gpuvm: The &drm_gpuvm the @obj is mapped in.
@@ -881,6 +1453,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
 	INIT_LIST_HEAD(&vm_bo->list.gpuva);
 	INIT_LIST_HEAD(&vm_bo->list.entry.gem);
 
+	INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
+	INIT_LIST_HEAD(&vm_bo->list.entry.evict);
+
 	return vm_bo;
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
@@ -898,6 +1473,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
 	if (!lock)
 		drm_gpuvm_resv_assert_held(gpuvm);
 
+	drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
+	drm_gpuvm_bo_list_del(vm_bo, evict, lock);
+
 	drm_gem_gpuva_assert_lock_held(obj);
 	list_del(&vm_bo->list.entry.gem);
 
@@ -1037,6 +1615,61 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
 
+/**
+ * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
+ * extobj list
+ * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
+ *
+ * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
+ * already and if the corresponding &drm_gem_object is an external object,
+ * actually.
+ */
+void
+drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	if (!lock)
+		drm_gpuvm_resv_assert_held(gpuvm);
+
+	if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
+		drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
+
+/**
+ * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
+ * evicted list
+ * @vm_bo: the &drm_gpuvm_bo to add or remove
+ * @evict: indicates whether the object is evicted
+ *
+ * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
+ */
+void
+drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
+{
+	struct drm_gpuvm *gpuvm = vm_bo->vm;
+	struct drm_gem_object *obj = vm_bo->obj;
+	bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+	dma_resv_assert_held(obj->resv);
+	vm_bo->evicted = evict;
+
+	/* Can't add external objects to the evicted list directly if not using
+	 * internal spinlocks, since in this case the evicted list is protected
+	 * with the VM's common dma-resv lock.
+	 */
+	if (drm_gpuvm_is_extobj(gpuvm, obj) && !lock)
+		return;
+
+	if (evict)
+		drm_gpuvm_bo_list_add(vm_bo, evict, lock);
+	else
+		drm_gpuvm_bo_list_del_init(vm_bo, evict, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_evict);
+
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va)
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index b12fb22b0e22..8ca10461d8ac 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -32,6 +32,7 @@
 
 #include <drm/drm_device.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_exec.h>
 
 struct drm_gpuvm;
 struct drm_gpuvm_bo;
@@ -283,6 +284,50 @@ struct drm_gpuvm {
 	 * @r_obj: Resv GEM object; representing the GPUVM's common &dma_resv.
 	 */
 	struct drm_gem_object *r_obj;
+
+	/**
+	 * @extobj: structure holding the extobj list
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos serving as
+		 * external object
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the external object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the extobj list
+		 */
+		spinlock_t lock;
+	} extobj;
+
+	/**
+	 * @evict: structure holding the evict list and evict list lock
+	 */
+	struct {
+		/**
+		 * @list: &list_head storing &drm_gpuvm_bos currently being
+		 * evicted
+		 */
+		struct list_head list;
+
+		/**
+		 * @local_list: pointer to the local list temporarily storing
+		 * entries from the evicted object list
+		 */
+		struct list_head *local_list;
+
+		/**
+		 * @lock: spinlock to protect the evict list
+		 */
+		spinlock_t lock;
+	} evict;
 };
 
 void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
@@ -359,6 +404,22 @@ drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
 #define drm_gpuvm_resv_assert_held(gpuvm__) \
 	dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
 
+/**
+ * drm_gpuvm_is_extobj() - indicates whether the given &drm_gem_object is an
+ * external object
+ * @gpuvm: the &drm_gpuvm to check
+ * @obj: the &drm_gem_object to check
+ *
+ * Returns: true if the &drm_gem_object &dma_resv differs from the
+ * &drm_gpuvms &dma_resv, false otherwise
+ */
+static inline bool
+drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm,
+		    struct drm_gem_object *obj)
+{
+	return obj && obj->resv != drm_gpuvm_resv(gpuvm);
+}
+
 static inline struct drm_gpuva *
 __drm_gpuva_next(struct drm_gpuva *va)
 {
@@ -437,6 +498,144 @@ __drm_gpuva_next(struct drm_gpuva *va)
 #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
 	list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)
 
+/**
+ * struct drm_gpuvm_exec - &drm_gpuvm abstraction of &drm_exec
+ *
+ * This structure should be created on the stack as &drm_exec should be.
+ *
+ * Optionally, @extra can be set in order to lock additional &drm_gem_objects.
+ */
+struct drm_gpuvm_exec {
+	/**
+	 * @exec: the &drm_exec structure
+	 */
+	struct drm_exec exec;
+
+	/**
+	 * @flags: the flags for the struct drm_exec
+	 */
+	uint32_t flags;
+
+	/**
+	 * @vm: the &drm_gpuvm to lock its DMA reservations
+	 */
+	struct drm_gpuvm *vm;
+
+	/**
+	 * @num_fences: the number of fences to reserve for the &dma_resv of the
+	 * locked &drm_gem_objects
+	 */
+	unsigned int num_fences;
+
+	/**
+	 * @extra: Callback and corresponding private data for the driver to
+	 * lock arbitrary additional &drm_gem_objects.
+	 */
+	struct {
+		/**
+		 * @fn: The driver callback to lock additional &drm_gem_objects.
+		 */
+		int (*fn)(struct drm_gpuvm_exec *vm_exec);
+
+		/**
+		 * @priv: driver private data for the @fn callback
+		 */
+		void *priv;
+	} extra;
+};
+
+/**
+ * drm_gpuvm_prepare_vm() - prepare the GPUVMs common dma-resv
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for the GPUVMs dummy &drm_gem_object.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline int
+drm_gpuvm_prepare_vm(struct drm_gpuvm *gpuvm,
+		     struct drm_exec *exec,
+		     unsigned int num_fences)
+{
+	return drm_exec_prepare_obj(exec, gpuvm->r_obj, num_fences);
+}
+
+int drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      unsigned int num_fences);
+
+int drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm,
+			    struct drm_exec *exec,
+			    u64 addr, u64 range,
+			    unsigned int num_fences);
+
+int drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec);
+
+int drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+			      struct drm_gem_object **objs,
+			      unsigned int num_objs);
+
+int drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+			      u64 addr, u64 range);
+
+/**
+ * drm_gpuvm_exec_unlock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Releases all dma-resv locks of all &drm_gem_objects previously acquired
+ * through drm_gpuvm_exec_lock() or its variants.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline void
+drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
+{
+	drm_exec_fini(&vm_exec->exec);
+}
+
+int drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec);
+void drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+			      struct drm_exec *exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage);
+
+/**
+ * drm_gpuvm_exec_resv_add_fence()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ *
+ * See drm_gpuvm_resv_add_fence().
+ */
+static inline void
+drm_gpuvm_exec_resv_add_fence(struct drm_gpuvm_exec *vm_exec,
+			      struct dma_fence *fence,
+			      enum dma_resv_usage private_usage,
+			      enum dma_resv_usage extobj_usage)
+{
+	drm_gpuvm_resv_add_fence(vm_exec->vm, &vm_exec->exec, fence,
+				 private_usage, extobj_usage);
+}
+
+/**
+ * drm_gpuvm_exec_validate()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * See drm_gpuvm_validate().
+ */
+static inline int
+drm_gpuvm_exec_validate(struct drm_gpuvm_exec *vm_exec)
+{
+	return drm_gpuvm_validate(vm_exec->vm, &vm_exec->exec);
+}
+
 /**
  * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
  * &drm_gem_object combination
@@ -466,6 +665,12 @@ struct drm_gpuvm_bo {
 	 */
 	struct drm_gem_object *obj;
 
+	/**
+	 * @evicted: Indicates whether the &drm_gem_object is evicted; field
+	 * protected by the &drm_gem_object's dma-resv lock.
+	 */
+	bool evicted;
+
 	/**
 	 * @kref: The reference count for this &drm_gpuvm_bo.
 	 */
@@ -493,6 +698,18 @@ struct drm_gpuvm_bo {
 			 * gpuva list.
 			 */
 			struct list_head gem;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms
+			 * extobj list.
+			 */
+			struct list_head extobj;
+
+			/**
+			 * @evict: List entry to attach to the &drm_gpuvms evict
+			 * list.
+			 */
+			struct list_head evict;
 		} entry;
 	} list;
 };
@@ -527,6 +744,27 @@ struct drm_gpuvm_bo *
 drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
 		  struct drm_gem_object *obj);
 
+void drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict);
+
+/**
+ * drm_gpuvm_bo_gem_evict()
+ * @obj: the &drm_gem_object
+ * @evict: indicates whether @obj is evicted
+ *
+ * See drm_gpuvm_bo_evict().
+ */
+static inline void
+drm_gpuvm_bo_gem_evict(struct drm_gem_object *obj, bool evict)
+{
+	struct drm_gpuvm_bo *vm_bo;
+
+	drm_gem_gpuva_assert_lock_held(obj);
+	drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+		drm_gpuvm_bo_evict(vm_bo, evict);
+}
+
+void drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo);
+
 /**
  * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
  * @va__: &drm_gpuva structure to assign to in each iteration step
@@ -901,6 +1139,18 @@ struct drm_gpuvm_ops {
 	 */
 	void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
 
+	/**
+	 * @vm_bo_validate: called from drm_gpuvm_validate()
+	 *
+	 * Drivers receive this callback for every evicted &drm_gem_object being
+	 * mapped in the corresponding &drm_gpuvm.
+	 *
+	 * Typically, drivers would call their driver specific variant of
+	 * ttm_bo_validate() from within this callback.
+	 */
+	int (*vm_bo_validate)(struct drm_gpuvm_bo *vm_bo,
+			      struct drm_exec *exec);
+
 	/**
 	 * @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
 	 * mapping once all previous steps were completed
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [Nouveau] [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
  2023-11-01 23:30 ` Danilo Krummrich
  (?)
@ 2023-11-01 23:31   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

GPUVM provides common infrastructure to track external and evicted GEM
objects as well as locking and validation helpers.

Especially external and evicted object tracking is a huge improvement
compared to the current brute force approach of iterating all mappings
in order to lock and validate the GPUVM's GEM objects. Hence, make us of
it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
 drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
 drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
 drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++---------
 6 files changed, 90 insertions(+), 90 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 7afad86da64b..b7dda486a7ea 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 {
 	struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
 	struct nouveau_bo *nvbo = nouveau_bo(bo);
+	struct drm_gem_object *obj = &bo->base;
 	struct ttm_resource *old_reg = bo->resource;
 	struct nouveau_drm_tile *new_tile = NULL;
 	int ret = 0;
 
-
 	if (new_reg->mem_type == TTM_PL_TT) {
 		ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm, new_reg);
 		if (ret)
 			return ret;
 	}
 
+	drm_gpuvm_bo_gem_evict(obj, evict);
 	nouveau_bo_move_ntfy(bo, new_reg);
 	ret = ttm_bo_wait_ctx(bo, ctx);
 	if (ret)
@@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 out_ntfy:
 	if (ret) {
 		nouveau_bo_move_ntfy(bo, bo->resource);
+		drm_gpuvm_bo_gem_evict(obj, !evict);
 	}
 	return ret;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index bf6c12f4342a..9d9835fb5970 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -1,7 +1,5 @@
 // SPDX-License-Identifier: MIT
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_gem.h"
 #include "nouveau_mem.h"
@@ -86,14 +84,12 @@
  */
 
 static int
-nouveau_exec_job_submit(struct nouveau_job *job)
+nouveau_exec_job_submit(struct nouveau_job *job,
+			struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
 	struct nouveau_cli *cli = job->cli;
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
 	int ret;
 
 	/* Create a new fence, but do not emit yet. */
@@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
 		return ret;
 
 	nouveau_uvmm_lock(uvmm);
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		struct drm_gpuva *va;
-
-		drm_gpuvm_for_each_va(va, &uvmm->base) {
-			if (unlikely(va == &uvmm->base.kernel_alloc_node))
-				continue;
-
-			ret = drm_exec_prepare_obj(exec, va->gem.obj, 1);
-			drm_exec_retry_on_contention(exec);
-			if (ret)
-				goto err_uvmm_unlock;
-		}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret) {
+		nouveau_uvmm_unlock(uvmm);
+		return ret;
 	}
 	nouveau_uvmm_unlock(uvmm);
 
-	drm_exec_for_each_locked_object(exec, index, obj) {
-		struct nouveau_bo *nvbo = nouveau_gem_object(obj);
-
-		ret = nouveau_bo_validate(nvbo, true, false);
-		if (ret)
-			goto err_exec_fini;
+	ret = drm_gpuvm_exec_validate(vme);
+	if (ret) {
+		drm_gpuvm_exec_unlock(vme);
+		return ret;
 	}
 
 	return 0;
-
-err_uvmm_unlock:
-	nouveau_uvmm_unlock(uvmm);
-err_exec_fini:
-	drm_exec_fini(exec);
-	return ret;
-
 }
 
 static void
-nouveau_exec_job_armed_submit(struct nouveau_job *job)
+nouveau_exec_job_armed_submit(struct nouveau_job *job,
+			      struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h b/drivers/gpu/drm/nouveau/nouveau_exec.h
index 778cacd90f65..b815de2428f3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.h
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
@@ -3,16 +3,12 @@
 #ifndef __NOUVEAU_EXEC_H__
 #define __NOUVEAU_EXEC_H__
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_sched.h"
 
 struct nouveau_exec_job_args {
 	struct drm_file *file_priv;
 	struct nouveau_sched_entity *sched_entity;
-
-	struct drm_exec exec;
 	struct nouveau_channel *chan;
 
 	struct {
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c
index 7e64b5ef90fb..0416fd6b6a40 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
@@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
 {
 	struct nouveau_sched_entity *entity = to_nouveau_sched_entity(job->base.entity);
 	struct dma_fence *done_fence = NULL;
+	struct drm_gpuvm_exec vm_exec = {
+		.vm = &nouveau_cli_uvmm(job->cli)->base,
+		.flags = DRM_EXEC_IGNORE_DUPLICATES,
+		.num_fences = 1,
+	};
 	int ret;
 
 	ret = nouveau_job_add_deps(job);
@@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	 * successfully.
 	 */
 	if (job->ops->submit) {
-		ret = job->ops->submit(job);
+		ret = job->ops->submit(job, &vm_exec);
 		if (ret)
 			goto err_cleanup;
 	}
@@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence->flags);
 
 	if (job->ops->armed_submit)
-		job->ops->armed_submit(job);
+		job->ops->armed_submit(job, &vm_exec);
 
 	nouveau_job_fence_attach(job);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 27ac19792597..0f87697dbc9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -5,7 +5,7 @@
 
 #include <linux/types.h>
 
-#include <drm/drm_exec.h>
+#include <drm/drm_gpuvm.h>
 #include <drm/gpu_scheduler.h>
 
 #include "nouveau_drv.h"
@@ -54,7 +54,6 @@ struct nouveau_job {
 	struct drm_file *file_priv;
 	struct nouveau_cli *cli;
 
-	struct drm_exec exec;
 	enum dma_resv_usage resv_usage;
 	struct dma_fence *done_fence;
 
@@ -76,8 +75,8 @@ struct nouveau_job {
 		/* If .submit() returns without any error, it is guaranteed that
 		 * armed_submit() is called.
 		 */
-		int (*submit)(struct nouveau_job *);
-		void (*armed_submit)(struct nouveau_job *);
+		int (*submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
+		void (*armed_submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
 		struct dma_fence *(*run)(struct nouveau_job *);
 		void (*free)(struct nouveau_job *);
 		enum drm_gpu_sched_stat (*timeout)(struct nouveau_job *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index eda7bb8624f1..2bb72fff06e0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct nouveau_uvma_region *reg)
 static void
 op_map_prepare_unwind(struct nouveau_uvma *uvma)
 {
+	struct drm_gpuva *va = &uvma->va;
 	nouveau_uvma_gem_put(uvma);
-	drm_gpuva_remove(&uvma->va);
+	drm_gpuva_remove(va);
 	nouveau_uvma_free(uvma);
 }
 
@@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			break;
 		case DRM_GPUVA_OP_REMAP: {
 			struct drm_gpuva_op_remap *r = &op->remap;
+			struct drm_gpuva *va = r->unmap->va;
 
 			if (r->next)
 				op_map_prepare_unwind(new->next);
@@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			if (r->prev)
 				op_map_prepare_unwind(new->prev);
 
-			op_unmap_prepare_unwind(r->unmap->va);
+			op_unmap_prepare_unwind(va);
 			break;
 		}
 		case DRM_GPUVA_OP_UNMAP:
@@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
 					goto unwind;
 				}
 			}
+
 			break;
 		}
 		case DRM_GPUVA_OP_REMAP: {
@@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
 }
 
 static int
-nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
+bind_lock_extra(struct drm_gpuvm_exec *vme)
+{
+	struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
+	struct drm_exec *exec = &vme->exec;
+	struct bind_job_op *op;
+	int ret;
+
+	list_for_each_op(op, &bind_job->ops) {
+		struct drm_gpuva_op *va_op;
+
+		if (IS_ERR_OR_NULL(op->ops))
+			continue;
+
+		drm_gpuva_for_each_op(va_op, op->ops) {
+			struct drm_gem_object *obj = op_gem_obj(va_op);
+
+			if (unlikely(!obj))
+				continue;
+
+			if (va_op->op != DRM_GPUVA_OP_UNMAP)
+				continue;
+
+			ret = drm_exec_prepare_obj(exec, obj, vme->num_fences);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int
+nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
+			     struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
 	struct nouveau_uvmm_bind_job *bind_job = to_uvmm_bind_job(job);
 	struct nouveau_sched_entity *entity = job->entity;
-	struct drm_exec *exec = &job->exec;
 	struct bind_job_op *op;
 	int ret;
 
@@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 			dma_resv_unlock(obj->resv);
 			if (IS_ERR(op->vm_bo))
 				return PTR_ERR(op->vm_bo);
+
+			drm_gpuvm_bo_extobj_add(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	 * unwind all GPU VA space changes on failure.
 	 */
 	nouveau_uvmm_lock(uvmm);
+
 	list_for_each_op(op, &bind_job->ops) {
 		switch (op->op) {
 		case OP_MAP_SPARSE:
@@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		}
 	}
 
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		list_for_each_op(op, &bind_job->ops) {
-			struct drm_gpuva_op *va_op;
+	vme->extra.fn = bind_lock_extra;
+	vme->extra.priv = bind_job;
 
-			if (IS_ERR_OR_NULL(op->ops))
-				continue;
-
-			drm_gpuva_for_each_op(va_op, op->ops) {
-				struct drm_gem_object *obj = op_gem_obj(va_op);
-
-				if (unlikely(!obj))
-					continue;
-
-				ret = drm_exec_prepare_obj(exec, obj, 1);
-				drm_exec_retry_on_contention(exec);
-				if (ret) {
-					op = list_last_op(&bind_job->ops);
-					goto unwind;
-				}
-			}
-		}
-	}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret)
+		goto unwind_continue;
 
 	list_for_each_op(op, &bind_job->ops) {
 		struct drm_gpuva_op *va_op;
@@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	}
 
 	nouveau_uvmm_unlock(uvmm);
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_unlock(vme);
 	return ret;
 }
 
 static void
-nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
+nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
+				   struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
@@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
 	kfree(uvmm);
 }
 
+static int
+nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
+{
+	struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
+
+	return nouveau_bo_validate(nvbo, true, false);
+}
+
 static const struct drm_gpuvm_ops gpuvm_ops = {
 	.vm_free = nouveau_uvmm_free,
+	.vm_bo_validate = nouveau_uvmm_bo_validate,
 };
 
 int
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel

GPUVM provides common infrastructure to track external and evicted GEM
objects as well as locking and validation helpers.

Especially external and evicted object tracking is a huge improvement
compared to the current brute force approach of iterating all mappings
in order to lock and validate the GPUVM's GEM objects. Hence, make us of
it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
 drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
 drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
 drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++---------
 6 files changed, 90 insertions(+), 90 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 7afad86da64b..b7dda486a7ea 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 {
 	struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
 	struct nouveau_bo *nvbo = nouveau_bo(bo);
+	struct drm_gem_object *obj = &bo->base;
 	struct ttm_resource *old_reg = bo->resource;
 	struct nouveau_drm_tile *new_tile = NULL;
 	int ret = 0;
 
-
 	if (new_reg->mem_type == TTM_PL_TT) {
 		ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm, new_reg);
 		if (ret)
 			return ret;
 	}
 
+	drm_gpuvm_bo_gem_evict(obj, evict);
 	nouveau_bo_move_ntfy(bo, new_reg);
 	ret = ttm_bo_wait_ctx(bo, ctx);
 	if (ret)
@@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 out_ntfy:
 	if (ret) {
 		nouveau_bo_move_ntfy(bo, bo->resource);
+		drm_gpuvm_bo_gem_evict(obj, !evict);
 	}
 	return ret;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index bf6c12f4342a..9d9835fb5970 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -1,7 +1,5 @@
 // SPDX-License-Identifier: MIT
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_gem.h"
 #include "nouveau_mem.h"
@@ -86,14 +84,12 @@
  */
 
 static int
-nouveau_exec_job_submit(struct nouveau_job *job)
+nouveau_exec_job_submit(struct nouveau_job *job,
+			struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
 	struct nouveau_cli *cli = job->cli;
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
 	int ret;
 
 	/* Create a new fence, but do not emit yet. */
@@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
 		return ret;
 
 	nouveau_uvmm_lock(uvmm);
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		struct drm_gpuva *va;
-
-		drm_gpuvm_for_each_va(va, &uvmm->base) {
-			if (unlikely(va == &uvmm->base.kernel_alloc_node))
-				continue;
-
-			ret = drm_exec_prepare_obj(exec, va->gem.obj, 1);
-			drm_exec_retry_on_contention(exec);
-			if (ret)
-				goto err_uvmm_unlock;
-		}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret) {
+		nouveau_uvmm_unlock(uvmm);
+		return ret;
 	}
 	nouveau_uvmm_unlock(uvmm);
 
-	drm_exec_for_each_locked_object(exec, index, obj) {
-		struct nouveau_bo *nvbo = nouveau_gem_object(obj);
-
-		ret = nouveau_bo_validate(nvbo, true, false);
-		if (ret)
-			goto err_exec_fini;
+	ret = drm_gpuvm_exec_validate(vme);
+	if (ret) {
+		drm_gpuvm_exec_unlock(vme);
+		return ret;
 	}
 
 	return 0;
-
-err_uvmm_unlock:
-	nouveau_uvmm_unlock(uvmm);
-err_exec_fini:
-	drm_exec_fini(exec);
-	return ret;
-
 }
 
 static void
-nouveau_exec_job_armed_submit(struct nouveau_job *job)
+nouveau_exec_job_armed_submit(struct nouveau_job *job,
+			      struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h b/drivers/gpu/drm/nouveau/nouveau_exec.h
index 778cacd90f65..b815de2428f3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.h
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
@@ -3,16 +3,12 @@
 #ifndef __NOUVEAU_EXEC_H__
 #define __NOUVEAU_EXEC_H__
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_sched.h"
 
 struct nouveau_exec_job_args {
 	struct drm_file *file_priv;
 	struct nouveau_sched_entity *sched_entity;
-
-	struct drm_exec exec;
 	struct nouveau_channel *chan;
 
 	struct {
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c
index 7e64b5ef90fb..0416fd6b6a40 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
@@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
 {
 	struct nouveau_sched_entity *entity = to_nouveau_sched_entity(job->base.entity);
 	struct dma_fence *done_fence = NULL;
+	struct drm_gpuvm_exec vm_exec = {
+		.vm = &nouveau_cli_uvmm(job->cli)->base,
+		.flags = DRM_EXEC_IGNORE_DUPLICATES,
+		.num_fences = 1,
+	};
 	int ret;
 
 	ret = nouveau_job_add_deps(job);
@@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	 * successfully.
 	 */
 	if (job->ops->submit) {
-		ret = job->ops->submit(job);
+		ret = job->ops->submit(job, &vm_exec);
 		if (ret)
 			goto err_cleanup;
 	}
@@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence->flags);
 
 	if (job->ops->armed_submit)
-		job->ops->armed_submit(job);
+		job->ops->armed_submit(job, &vm_exec);
 
 	nouveau_job_fence_attach(job);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 27ac19792597..0f87697dbc9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -5,7 +5,7 @@
 
 #include <linux/types.h>
 
-#include <drm/drm_exec.h>
+#include <drm/drm_gpuvm.h>
 #include <drm/gpu_scheduler.h>
 
 #include "nouveau_drv.h"
@@ -54,7 +54,6 @@ struct nouveau_job {
 	struct drm_file *file_priv;
 	struct nouveau_cli *cli;
 
-	struct drm_exec exec;
 	enum dma_resv_usage resv_usage;
 	struct dma_fence *done_fence;
 
@@ -76,8 +75,8 @@ struct nouveau_job {
 		/* If .submit() returns without any error, it is guaranteed that
 		 * armed_submit() is called.
 		 */
-		int (*submit)(struct nouveau_job *);
-		void (*armed_submit)(struct nouveau_job *);
+		int (*submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
+		void (*armed_submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
 		struct dma_fence *(*run)(struct nouveau_job *);
 		void (*free)(struct nouveau_job *);
 		enum drm_gpu_sched_stat (*timeout)(struct nouveau_job *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index eda7bb8624f1..2bb72fff06e0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct nouveau_uvma_region *reg)
 static void
 op_map_prepare_unwind(struct nouveau_uvma *uvma)
 {
+	struct drm_gpuva *va = &uvma->va;
 	nouveau_uvma_gem_put(uvma);
-	drm_gpuva_remove(&uvma->va);
+	drm_gpuva_remove(va);
 	nouveau_uvma_free(uvma);
 }
 
@@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			break;
 		case DRM_GPUVA_OP_REMAP: {
 			struct drm_gpuva_op_remap *r = &op->remap;
+			struct drm_gpuva *va = r->unmap->va;
 
 			if (r->next)
 				op_map_prepare_unwind(new->next);
@@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			if (r->prev)
 				op_map_prepare_unwind(new->prev);
 
-			op_unmap_prepare_unwind(r->unmap->va);
+			op_unmap_prepare_unwind(va);
 			break;
 		}
 		case DRM_GPUVA_OP_UNMAP:
@@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
 					goto unwind;
 				}
 			}
+
 			break;
 		}
 		case DRM_GPUVA_OP_REMAP: {
@@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
 }
 
 static int
-nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
+bind_lock_extra(struct drm_gpuvm_exec *vme)
+{
+	struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
+	struct drm_exec *exec = &vme->exec;
+	struct bind_job_op *op;
+	int ret;
+
+	list_for_each_op(op, &bind_job->ops) {
+		struct drm_gpuva_op *va_op;
+
+		if (IS_ERR_OR_NULL(op->ops))
+			continue;
+
+		drm_gpuva_for_each_op(va_op, op->ops) {
+			struct drm_gem_object *obj = op_gem_obj(va_op);
+
+			if (unlikely(!obj))
+				continue;
+
+			if (va_op->op != DRM_GPUVA_OP_UNMAP)
+				continue;
+
+			ret = drm_exec_prepare_obj(exec, obj, vme->num_fences);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int
+nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
+			     struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
 	struct nouveau_uvmm_bind_job *bind_job = to_uvmm_bind_job(job);
 	struct nouveau_sched_entity *entity = job->entity;
-	struct drm_exec *exec = &job->exec;
 	struct bind_job_op *op;
 	int ret;
 
@@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 			dma_resv_unlock(obj->resv);
 			if (IS_ERR(op->vm_bo))
 				return PTR_ERR(op->vm_bo);
+
+			drm_gpuvm_bo_extobj_add(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	 * unwind all GPU VA space changes on failure.
 	 */
 	nouveau_uvmm_lock(uvmm);
+
 	list_for_each_op(op, &bind_job->ops) {
 		switch (op->op) {
 		case OP_MAP_SPARSE:
@@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		}
 	}
 
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		list_for_each_op(op, &bind_job->ops) {
-			struct drm_gpuva_op *va_op;
+	vme->extra.fn = bind_lock_extra;
+	vme->extra.priv = bind_job;
 
-			if (IS_ERR_OR_NULL(op->ops))
-				continue;
-
-			drm_gpuva_for_each_op(va_op, op->ops) {
-				struct drm_gem_object *obj = op_gem_obj(va_op);
-
-				if (unlikely(!obj))
-					continue;
-
-				ret = drm_exec_prepare_obj(exec, obj, 1);
-				drm_exec_retry_on_contention(exec);
-				if (ret) {
-					op = list_last_op(&bind_job->ops);
-					goto unwind;
-				}
-			}
-		}
-	}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret)
+		goto unwind_continue;
 
 	list_for_each_op(op, &bind_job->ops) {
 		struct drm_gpuva_op *va_op;
@@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	}
 
 	nouveau_uvmm_unlock(uvmm);
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_unlock(vme);
 	return ret;
 }
 
 static void
-nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
+nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
+				   struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
@@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
 	kfree(uvmm);
 }
 
+static int
+nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
+{
+	struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
+
+	return nouveau_bo_validate(nvbo, true, false);
+}
+
 static const struct drm_gpuvm_ops gpuvm_ops = {
 	.vm_free = nouveau_uvmm_free,
+	.vm_bo_validate = nouveau_uvmm_bo_validate,
 };
 
 int
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-01 23:31   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-01 23:31 UTC (permalink / raw)
  To: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel, Danilo Krummrich

GPUVM provides common infrastructure to track external and evicted GEM
objects as well as locking and validation helpers.

Especially external and evicted object tracking is a huge improvement
compared to the current brute force approach of iterating all mappings
in order to lock and validate the GPUVM's GEM objects. Hence, make us of
it.

Signed-off-by: Danilo Krummrich <dakr@redhat.com>
---
 drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
 drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
 drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
 drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
 drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
 drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++---------
 6 files changed, 90 insertions(+), 90 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 7afad86da64b..b7dda486a7ea 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 {
 	struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
 	struct nouveau_bo *nvbo = nouveau_bo(bo);
+	struct drm_gem_object *obj = &bo->base;
 	struct ttm_resource *old_reg = bo->resource;
 	struct nouveau_drm_tile *new_tile = NULL;
 	int ret = 0;
 
-
 	if (new_reg->mem_type == TTM_PL_TT) {
 		ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm, new_reg);
 		if (ret)
 			return ret;
 	}
 
+	drm_gpuvm_bo_gem_evict(obj, evict);
 	nouveau_bo_move_ntfy(bo, new_reg);
 	ret = ttm_bo_wait_ctx(bo, ctx);
 	if (ret)
@@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
 out_ntfy:
 	if (ret) {
 		nouveau_bo_move_ntfy(bo, bo->resource);
+		drm_gpuvm_bo_gem_evict(obj, !evict);
 	}
 	return ret;
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index bf6c12f4342a..9d9835fb5970 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -1,7 +1,5 @@
 // SPDX-License-Identifier: MIT
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_gem.h"
 #include "nouveau_mem.h"
@@ -86,14 +84,12 @@
  */
 
 static int
-nouveau_exec_job_submit(struct nouveau_job *job)
+nouveau_exec_job_submit(struct nouveau_job *job,
+			struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
 	struct nouveau_cli *cli = job->cli;
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
 	int ret;
 
 	/* Create a new fence, but do not emit yet. */
@@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
 		return ret;
 
 	nouveau_uvmm_lock(uvmm);
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		struct drm_gpuva *va;
-
-		drm_gpuvm_for_each_va(va, &uvmm->base) {
-			if (unlikely(va == &uvmm->base.kernel_alloc_node))
-				continue;
-
-			ret = drm_exec_prepare_obj(exec, va->gem.obj, 1);
-			drm_exec_retry_on_contention(exec);
-			if (ret)
-				goto err_uvmm_unlock;
-		}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret) {
+		nouveau_uvmm_unlock(uvmm);
+		return ret;
 	}
 	nouveau_uvmm_unlock(uvmm);
 
-	drm_exec_for_each_locked_object(exec, index, obj) {
-		struct nouveau_bo *nvbo = nouveau_gem_object(obj);
-
-		ret = nouveau_bo_validate(nvbo, true, false);
-		if (ret)
-			goto err_exec_fini;
+	ret = drm_gpuvm_exec_validate(vme);
+	if (ret) {
+		drm_gpuvm_exec_unlock(vme);
+		return ret;
 	}
 
 	return 0;
-
-err_uvmm_unlock:
-	nouveau_uvmm_unlock(uvmm);
-err_exec_fini:
-	drm_exec_fini(exec);
-	return ret;
-
 }
 
 static void
-nouveau_exec_job_armed_submit(struct nouveau_job *job)
+nouveau_exec_job_armed_submit(struct nouveau_job *job,
+			      struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h b/drivers/gpu/drm/nouveau/nouveau_exec.h
index 778cacd90f65..b815de2428f3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.h
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
@@ -3,16 +3,12 @@
 #ifndef __NOUVEAU_EXEC_H__
 #define __NOUVEAU_EXEC_H__
 
-#include <drm/drm_exec.h>
-
 #include "nouveau_drv.h"
 #include "nouveau_sched.h"
 
 struct nouveau_exec_job_args {
 	struct drm_file *file_priv;
 	struct nouveau_sched_entity *sched_entity;
-
-	struct drm_exec exec;
 	struct nouveau_channel *chan;
 
 	struct {
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c
index 7e64b5ef90fb..0416fd6b6a40 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.c
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
@@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
 {
 	struct nouveau_sched_entity *entity = to_nouveau_sched_entity(job->base.entity);
 	struct dma_fence *done_fence = NULL;
+	struct drm_gpuvm_exec vm_exec = {
+		.vm = &nouveau_cli_uvmm(job->cli)->base,
+		.flags = DRM_EXEC_IGNORE_DUPLICATES,
+		.num_fences = 1,
+	};
 	int ret;
 
 	ret = nouveau_job_add_deps(job);
@@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	 * successfully.
 	 */
 	if (job->ops->submit) {
-		ret = job->ops->submit(job);
+		ret = job->ops->submit(job, &vm_exec);
 		if (ret)
 			goto err_cleanup;
 	}
@@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
 	set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence->flags);
 
 	if (job->ops->armed_submit)
-		job->ops->armed_submit(job);
+		job->ops->armed_submit(job, &vm_exec);
 
 	nouveau_job_fence_attach(job);
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 27ac19792597..0f87697dbc9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -5,7 +5,7 @@
 
 #include <linux/types.h>
 
-#include <drm/drm_exec.h>
+#include <drm/drm_gpuvm.h>
 #include <drm/gpu_scheduler.h>
 
 #include "nouveau_drv.h"
@@ -54,7 +54,6 @@ struct nouveau_job {
 	struct drm_file *file_priv;
 	struct nouveau_cli *cli;
 
-	struct drm_exec exec;
 	enum dma_resv_usage resv_usage;
 	struct dma_fence *done_fence;
 
@@ -76,8 +75,8 @@ struct nouveau_job {
 		/* If .submit() returns without any error, it is guaranteed that
 		 * armed_submit() is called.
 		 */
-		int (*submit)(struct nouveau_job *);
-		void (*armed_submit)(struct nouveau_job *);
+		int (*submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
+		void (*armed_submit)(struct nouveau_job *, struct drm_gpuvm_exec *);
 		struct dma_fence *(*run)(struct nouveau_job *);
 		void (*free)(struct nouveau_job *);
 		enum drm_gpu_sched_stat (*timeout)(struct nouveau_job *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index eda7bb8624f1..2bb72fff06e0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct nouveau_uvma_region *reg)
 static void
 op_map_prepare_unwind(struct nouveau_uvma *uvma)
 {
+	struct drm_gpuva *va = &uvma->va;
 	nouveau_uvma_gem_put(uvma);
-	drm_gpuva_remove(&uvma->va);
+	drm_gpuva_remove(va);
 	nouveau_uvma_free(uvma);
 }
 
@@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			break;
 		case DRM_GPUVA_OP_REMAP: {
 			struct drm_gpuva_op_remap *r = &op->remap;
+			struct drm_gpuva *va = r->unmap->va;
 
 			if (r->next)
 				op_map_prepare_unwind(new->next);
@@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
 			if (r->prev)
 				op_map_prepare_unwind(new->prev);
 
-			op_unmap_prepare_unwind(r->unmap->va);
+			op_unmap_prepare_unwind(va);
 			break;
 		}
 		case DRM_GPUVA_OP_UNMAP:
@@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
 					goto unwind;
 				}
 			}
+
 			break;
 		}
 		case DRM_GPUVA_OP_REMAP: {
@@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
 }
 
 static int
-nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
+bind_lock_extra(struct drm_gpuvm_exec *vme)
+{
+	struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
+	struct drm_exec *exec = &vme->exec;
+	struct bind_job_op *op;
+	int ret;
+
+	list_for_each_op(op, &bind_job->ops) {
+		struct drm_gpuva_op *va_op;
+
+		if (IS_ERR_OR_NULL(op->ops))
+			continue;
+
+		drm_gpuva_for_each_op(va_op, op->ops) {
+			struct drm_gem_object *obj = op_gem_obj(va_op);
+
+			if (unlikely(!obj))
+				continue;
+
+			if (va_op->op != DRM_GPUVA_OP_UNMAP)
+				continue;
+
+			ret = drm_exec_prepare_obj(exec, obj, vme->num_fences);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int
+nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
+			     struct drm_gpuvm_exec *vme)
 {
 	struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
 	struct nouveau_uvmm_bind_job *bind_job = to_uvmm_bind_job(job);
 	struct nouveau_sched_entity *entity = job->entity;
-	struct drm_exec *exec = &job->exec;
 	struct bind_job_op *op;
 	int ret;
 
@@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 			dma_resv_unlock(obj->resv);
 			if (IS_ERR(op->vm_bo))
 				return PTR_ERR(op->vm_bo);
+
+			drm_gpuvm_bo_extobj_add(op->vm_bo);
 		}
 
 		ret = bind_validate_op(job, op);
@@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	 * unwind all GPU VA space changes on failure.
 	 */
 	nouveau_uvmm_lock(uvmm);
+
 	list_for_each_op(op, &bind_job->ops) {
 		switch (op->op) {
 		case OP_MAP_SPARSE:
@@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 		}
 	}
 
-	drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
-			    DRM_EXEC_IGNORE_DUPLICATES);
-	drm_exec_until_all_locked(exec) {
-		list_for_each_op(op, &bind_job->ops) {
-			struct drm_gpuva_op *va_op;
+	vme->extra.fn = bind_lock_extra;
+	vme->extra.priv = bind_job;
 
-			if (IS_ERR_OR_NULL(op->ops))
-				continue;
-
-			drm_gpuva_for_each_op(va_op, op->ops) {
-				struct drm_gem_object *obj = op_gem_obj(va_op);
-
-				if (unlikely(!obj))
-					continue;
-
-				ret = drm_exec_prepare_obj(exec, obj, 1);
-				drm_exec_retry_on_contention(exec);
-				if (ret) {
-					op = list_last_op(&bind_job->ops);
-					goto unwind;
-				}
-			}
-		}
-	}
+	ret = drm_gpuvm_exec_lock(vme);
+	if (ret)
+		goto unwind_continue;
 
 	list_for_each_op(op, &bind_job->ops) {
 		struct drm_gpuva_op *va_op;
@@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
 	}
 
 	nouveau_uvmm_unlock(uvmm);
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_unlock(vme);
 	return ret;
 }
 
 static void
-nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
+nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
+				   struct drm_gpuvm_exec *vme)
 {
-	struct drm_exec *exec = &job->exec;
-	struct drm_gem_object *obj;
-	unsigned long index;
-
-	drm_exec_for_each_locked_object(exec, index, obj)
-		dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
-	drm_exec_fini(exec);
+	drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
+				      job->resv_usage, job->resv_usage);
+	drm_gpuvm_exec_unlock(vme);
 }
 
 static struct dma_fence *
@@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
 	kfree(uvmm);
 }
 
+static int
+nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
+{
+	struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
+
+	return nouveau_bo_validate(nvbo, true, false);
+}
+
 static const struct drm_gpuvm_ops gpuvm_ops = {
 	.vm_free = nouveau_uvmm_free,
+	.vm_bo_validate = nouveau_uvmm_bo_validate,
 };
 
 int
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02  4:44     ` Dave Airlie
  -1 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:44 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith,
	dri-devel, nouveau, linux-kernel

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Allocate struct nouveau_uvmm separately in preparation for subsequent
> commits introducing reference counting for struct drm_gpuvm.
>
> While at it, get rid of nouveau_uvmm_init() as indirection of
> nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
@ 2023-11-02  4:44     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:44 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel, christian.koenig

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Allocate struct nouveau_uvmm separately in preparation for subsequent
> commits introducing reference counting for struct drm_gpuvm.
>
> While at it, get rid of nouveau_uvmm_init() as indirection of
> nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm
@ 2023-11-02  4:44     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:44 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	christian.koenig

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Allocate struct nouveau_uvmm separately in preparation for subsequent
> commits introducing reference counting for struct drm_gpuvm.
>
> While at it, get rid of nouveau_uvmm_init() as indirection of
> nouveau_uvmm_ioctl_vm_init() and perform some minor cleanups.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
  2023-11-01 23:30   ` Danilo Krummrich
  (?)
@ 2023-11-02  4:46     ` Dave Airlie
  -1 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:46 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith,
	dri-devel, nouveau, linux-kernel

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Use drm_gpuvm_range_valid() in order to validate userspace requests.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
@ 2023-11-02  4:46     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:46 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel, christian.koenig

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Use drm_gpuvm_range_valid() in order to validate userspace requests.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid()
@ 2023-11-02  4:46     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:46 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	christian.koenig

On Thu, 2 Nov 2023 at 09:31, Danilo Krummrich <dakr@redhat.com> wrote:
>
> Use drm_gpuvm_range_valid() in order to validate userspace requests.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02  4:47     ` Dave Airlie
  -1 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:47 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith,
	dri-devel, nouveau, linux-kernel

On Thu, 2 Nov 2023 at 09:32, Danilo Krummrich <dakr@redhat.com> wrote:
>
> GPUVM provides common infrastructure to track external and evicted GEM
> objects as well as locking and validation helpers.
>
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us of
> it.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

Dave.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-02  4:47     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:47 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel, christian.koenig

On Thu, 2 Nov 2023 at 09:32, Danilo Krummrich <dakr@redhat.com> wrote:
>
> GPUVM provides common infrastructure to track external and evicted GEM
> objects as well as locking and validation helpers.
>
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us of
> it.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

Dave.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-02  4:47     ` Dave Airlie
  0 siblings, 0 replies; 141+ messages in thread
From: Dave Airlie @ 2023-11-02  4:47 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	christian.koenig

On Thu, 2 Nov 2023 at 09:32, Danilo Krummrich <dakr@redhat.com> wrote:
>
> GPUVM provides common infrastructure to track external and evicted GEM
> objects as well as locking and validation helpers.
>
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us of
> it.
>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Reviewed-by: Dave Airlie <airlied@redhat.com>

Dave.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 10:46     ` kernel test robot
  -1 siblings, 0 replies; 141+ messages in thread
From: kernel test robot @ 2023-11-02 10:46 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	christian.koenig, faith
  Cc: oe-kbuild-all, nouveau, Danilo Krummrich, linux-kernel, dri-devel

Hi Danilo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883]

url:    https://github.com/intel-lab-lkp/linux/commits/Danilo-Krummrich/drm-gpuvm-convert-WARN-to-drm_WARN-variants/20231102-073332
base:   3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
patch link:    https://lore.kernel.org/r/20231101233113.8059-10-dakr%40redhat.com
patch subject: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
config: arc-allmodconfig (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/config)
compiler: arceb-elf-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311021833.q8aYDJnr-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/drm_gpuvm.c:810: warning: expecting prototype for drm_gpuvm_bo_put(). Prototype was for drm_gpuvm_put() instead


vim +810 drivers/gpu/drm/drm_gpuvm.c

   801	
   802	/**
   803	 * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
   804	 * @gpuvm: the &drm_gpuvm to release the reference of
   805	 *
   806	 * This releases a reference to @gpuvm.
   807	 */
   808	void
   809	drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 > 810	{
   811		if (gpuvm)
   812			kref_put(&gpuvm->kref, drm_gpuvm_free);
   813	}
   814	EXPORT_SYMBOL_GPL(drm_gpuvm_put);
   815	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 10:46     ` kernel test robot
  0 siblings, 0 replies; 141+ messages in thread
From: kernel test robot @ 2023-11-02 10:46 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel, oe-kbuild-all

Hi Danilo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883]

url:    https://github.com/intel-lab-lkp/linux/commits/Danilo-Krummrich/drm-gpuvm-convert-WARN-to-drm_WARN-variants/20231102-073332
base:   3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
patch link:    https://lore.kernel.org/r/20231101233113.8059-10-dakr%40redhat.com
patch subject: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
config: arc-allmodconfig (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/config)
compiler: arceb-elf-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311021833.q8aYDJnr-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/drm_gpuvm.c:810: warning: expecting prototype for drm_gpuvm_bo_put(). Prototype was for drm_gpuvm_put() instead


vim +810 drivers/gpu/drm/drm_gpuvm.c

   801	
   802	/**
   803	 * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
   804	 * @gpuvm: the &drm_gpuvm to release the reference of
   805	 *
   806	 * This releases a reference to @gpuvm.
   807	 */
   808	void
   809	drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 > 810	{
   811		if (gpuvm)
   812			kref_put(&gpuvm->kref, drm_gpuvm_free);
   813	}
   814	EXPORT_SYMBOL_GPL(drm_gpuvm_put);
   815	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 10:46     ` kernel test robot
  0 siblings, 0 replies; 141+ messages in thread
From: kernel test robot @ 2023-11-02 10:46 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	christian.koenig, faith
  Cc: nouveau, Danilo Krummrich, linux-kernel, dri-devel, oe-kbuild-all

Hi Danilo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 3c6c7ca4508b6cb1a033ac954c50a1b2c97af883]

url:    https://github.com/intel-lab-lkp/linux/commits/Danilo-Krummrich/drm-gpuvm-convert-WARN-to-drm_WARN-variants/20231102-073332
base:   3c6c7ca4508b6cb1a033ac954c50a1b2c97af883
patch link:    https://lore.kernel.org/r/20231101233113.8059-10-dakr%40redhat.com
patch subject: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
config: arc-allmodconfig (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/config)
compiler: arceb-elf-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231102/202311021833.q8aYDJnr-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311021833.q8aYDJnr-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/drm_gpuvm.c:810: warning: expecting prototype for drm_gpuvm_bo_put(). Prototype was for drm_gpuvm_put() instead


vim +810 drivers/gpu/drm/drm_gpuvm.c

   801	
   802	/**
   803	 * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
   804	 * @gpuvm: the &drm_gpuvm to release the reference of
   805	 *
   806	 * This releases a reference to @gpuvm.
   807	 */
   808	void
   809	drm_gpuvm_put(struct drm_gpuvm *gpuvm)
 > 810	{
   811		if (gpuvm)
   812			kref_put(&gpuvm->kref, drm_gpuvm_free);
   813	}
   814	EXPORT_SYMBOL_GPL(drm_gpuvm_put);
   815	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 13:18     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> GPUVM provides common infrastructure to track external and evicted
> GEM
> objects as well as locking and validation helpers.
> 
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all
> mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us
> of
> it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
NIT: Multiple checkpatch warnings in this one.

> ---
>  drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
>  drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
>  drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
>  drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
>  drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++-------
> --
>  6 files changed, 90 insertions(+), 90 deletions(-)
> 
> diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c
> b/drivers/gpu/drm/nouveau/nouveau_bo.c
> index 7afad86da64b..b7dda486a7ea 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_bo.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
> @@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  {
>         struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
>         struct nouveau_bo *nvbo = nouveau_bo(bo);
> +       struct drm_gem_object *obj = &bo->base;
>         struct ttm_resource *old_reg = bo->resource;
>         struct nouveau_drm_tile *new_tile = NULL;
>         int ret = 0;
>  
> -
>         if (new_reg->mem_type == TTM_PL_TT) {
>                 ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm,
> new_reg);
>                 if (ret)
>                         return ret;
>         }
>  
> +       drm_gpuvm_bo_gem_evict(obj, evict);
>         nouveau_bo_move_ntfy(bo, new_reg);
>         ret = ttm_bo_wait_ctx(bo, ctx);
>         if (ret)
> @@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  out_ntfy:
>         if (ret) {
>                 nouveau_bo_move_ntfy(bo, bo->resource);
> +               drm_gpuvm_bo_gem_evict(obj, !evict);
>         }
>         return ret;
>  }
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c
> b/drivers/gpu/drm/nouveau/nouveau_exec.c
> index bf6c12f4342a..9d9835fb5970 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
> @@ -1,7 +1,5 @@
>  // SPDX-License-Identifier: MIT
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_gem.h"
>  #include "nouveau_mem.h"
> @@ -86,14 +84,12 @@
>   */
>  
>  static int
> -nouveau_exec_job_submit(struct nouveau_job *job)
> +nouveau_exec_job_submit(struct nouveau_job *job,
> +                       struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
>         struct nouveau_cli *cli = job->cli;
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
>         int ret;
>  
>         /* Create a new fence, but do not emit yet. */
> @@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
>                 return ret;
>  
>         nouveau_uvmm_lock(uvmm);
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               struct drm_gpuva *va;
> -
> -               drm_gpuvm_for_each_va(va, &uvmm->base) {
> -                       if (unlikely(va == &uvmm-
> >base.kernel_alloc_node))
> -                               continue;
> -
> -                       ret = drm_exec_prepare_obj(exec, va->gem.obj,
> 1);
> -                       drm_exec_retry_on_contention(exec);
> -                       if (ret)
> -                               goto err_uvmm_unlock;
> -               }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret) {
> +               nouveau_uvmm_unlock(uvmm);
> +               return ret;
>         }
>         nouveau_uvmm_unlock(uvmm);
>  
> -       drm_exec_for_each_locked_object(exec, index, obj) {
> -               struct nouveau_bo *nvbo = nouveau_gem_object(obj);
> -
> -               ret = nouveau_bo_validate(nvbo, true, false);
> -               if (ret)
> -                       goto err_exec_fini;
> +       ret = drm_gpuvm_exec_validate(vme);
> +       if (ret) {
> +               drm_gpuvm_exec_unlock(vme);
> +               return ret;
>         }
>  
>         return 0;
> -
> -err_uvmm_unlock:
> -       nouveau_uvmm_unlock(uvmm);
> -err_exec_fini:
> -       drm_exec_fini(exec);
> -       return ret;
> -
>  }
>  
>  static void
> -nouveau_exec_job_armed_submit(struct nouveau_job *job)
> +nouveau_exec_job_armed_submit(struct nouveau_job *job,
> +                             struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h
> b/drivers/gpu/drm/nouveau/nouveau_exec.h
> index 778cacd90f65..b815de2428f3 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
> @@ -3,16 +3,12 @@
>  #ifndef __NOUVEAU_EXEC_H__
>  #define __NOUVEAU_EXEC_H__
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_sched.h"
>  
>  struct nouveau_exec_job_args {
>         struct drm_file *file_priv;
>         struct nouveau_sched_entity *sched_entity;
> -
> -       struct drm_exec exec;
>         struct nouveau_channel *chan;
>  
>         struct {
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c
> b/drivers/gpu/drm/nouveau/nouveau_sched.c
> index 7e64b5ef90fb..0416fd6b6a40 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
> @@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
>  {
>         struct nouveau_sched_entity *entity =
> to_nouveau_sched_entity(job->base.entity);
>         struct dma_fence *done_fence = NULL;
> +       struct drm_gpuvm_exec vm_exec = {
> +               .vm = &nouveau_cli_uvmm(job->cli)->base,
> +               .flags = DRM_EXEC_IGNORE_DUPLICATES,
> +               .num_fences = 1,
> +       };
>         int ret;
>  
>         ret = nouveau_job_add_deps(job);
> @@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
>          * successfully.
>          */
>         if (job->ops->submit) {
> -               ret = job->ops->submit(job);
> +               ret = job->ops->submit(job, &vm_exec);
>                 if (ret)
>                         goto err_cleanup;
>         }
> @@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
>         set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence-
> >flags);
>  
>         if (job->ops->armed_submit)
> -               job->ops->armed_submit(job);
> +               job->ops->armed_submit(job, &vm_exec);
>  
>         nouveau_job_fence_attach(job);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h
> b/drivers/gpu/drm/nouveau/nouveau_sched.h
> index 27ac19792597..0f87697dbc9e 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
> @@ -5,7 +5,7 @@
>  
>  #include <linux/types.h>
>  
> -#include <drm/drm_exec.h>
> +#include <drm/drm_gpuvm.h>
>  #include <drm/gpu_scheduler.h>
>  
>  #include "nouveau_drv.h"
> @@ -54,7 +54,6 @@ struct nouveau_job {
>         struct drm_file *file_priv;
>         struct nouveau_cli *cli;
>  
> -       struct drm_exec exec;
>         enum dma_resv_usage resv_usage;
>         struct dma_fence *done_fence;
>  
> @@ -76,8 +75,8 @@ struct nouveau_job {
>                 /* If .submit() returns without any error, it is
> guaranteed that
>                  * armed_submit() is called.
>                  */
> -               int (*submit)(struct nouveau_job *);
> -               void (*armed_submit)(struct nouveau_job *);
> +               int (*submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
> +               void (*armed_submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
>                 struct dma_fence *(*run)(struct nouveau_job *);
>                 void (*free)(struct nouveau_job *);
>                 enum drm_gpu_sched_stat (*timeout)(struct nouveau_job
> *);
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index eda7bb8624f1..2bb72fff06e0 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct
> nouveau_uvma_region *reg)
>  static void
>  op_map_prepare_unwind(struct nouveau_uvma *uvma)
>  {
> +       struct drm_gpuva *va = &uvma->va;
>         nouveau_uvma_gem_put(uvma);
> -       drm_gpuva_remove(&uvma->va);
> +       drm_gpuva_remove(va);
>         nouveau_uvma_free(uvma);
>  }
>  
> @@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         break;
>                 case DRM_GPUVA_OP_REMAP: {
>                         struct drm_gpuva_op_remap *r = &op->remap;
> +                       struct drm_gpuva *va = r->unmap->va;
>  
>                         if (r->next)
>                                 op_map_prepare_unwind(new->next);
> @@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         if (r->prev)
>                                 op_map_prepare_unwind(new->prev);
>  
> -                       op_unmap_prepare_unwind(r->unmap->va);
> +                       op_unmap_prepare_unwind(va);
>                         break;
>                 }
>                 case DRM_GPUVA_OP_UNMAP:
> @@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm
> *uvmm,
>                                         goto unwind;
>                                 }
>                         }
> +
>                         break;
>                 }
>                 case DRM_GPUVA_OP_REMAP: {
> @@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
>  }
>  
>  static int
> -nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
> +bind_lock_extra(struct drm_gpuvm_exec *vme)
> +{
> +       struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
> +       struct drm_exec *exec = &vme->exec;
> +       struct bind_job_op *op;
> +       int ret;
> +
> +       list_for_each_op(op, &bind_job->ops) {
> +               struct drm_gpuva_op *va_op;
> +
> +               if (IS_ERR_OR_NULL(op->ops))
> +                       continue;
> +
> +               drm_gpuva_for_each_op(va_op, op->ops) {
> +                       struct drm_gem_object *obj =
> op_gem_obj(va_op);
> +
> +                       if (unlikely(!obj))
> +                               continue;
> +
> +                       if (va_op->op != DRM_GPUVA_OP_UNMAP)
> +                               continue;
> +
> +                       ret = drm_exec_prepare_obj(exec, obj, vme-
> >num_fences);
> +                       if (ret)
> +                               return ret;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
> +                            struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
>         struct nouveau_uvmm_bind_job *bind_job =
> to_uvmm_bind_job(job);
>         struct nouveau_sched_entity *entity = job->entity;
> -       struct drm_exec *exec = &job->exec;
>         struct bind_job_op *op;
>         int ret;
>  
> @@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                         dma_resv_unlock(obj->resv);
>                         if (IS_ERR(op->vm_bo))
>                                 return PTR_ERR(op->vm_bo);
> +
> +                       drm_gpuvm_bo_extobj_add(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>          * unwind all GPU VA space changes on failure.
>          */
>         nouveau_uvmm_lock(uvmm);
> +
>         list_for_each_op(op, &bind_job->ops) {
>                 switch (op->op) {
>                 case OP_MAP_SPARSE:
> @@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>                 }
>         }
>  
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               list_for_each_op(op, &bind_job->ops) {
> -                       struct drm_gpuva_op *va_op;
> +       vme->extra.fn = bind_lock_extra;
> +       vme->extra.priv = bind_job;
>  
> -                       if (IS_ERR_OR_NULL(op->ops))
> -                               continue;
> -
> -                       drm_gpuva_for_each_op(va_op, op->ops) {
> -                               struct drm_gem_object *obj =
> op_gem_obj(va_op);
> -
> -                               if (unlikely(!obj))
> -                                       continue;
> -
> -                               ret = drm_exec_prepare_obj(exec, obj,
> 1);
> -                               drm_exec_retry_on_contention(exec);
> -                               if (ret) {
> -                                       op = list_last_op(&bind_job-
> >ops);
> -                                       goto unwind;
> -                               }
> -                       }
> -               }
> -       }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret)
> +               goto unwind_continue;
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 struct drm_gpuva_op *va_op;
> @@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>         }
>  
>         nouveau_uvmm_unlock(uvmm);
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_unlock(vme);
>         return ret;
>  }
>  
>  static void
> -nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
> +nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
> +                                  struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> @@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>         kfree(uvmm);
>  }
>  
> +static int
> +nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec
> *exec)
> +{
> +       struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
> +
> +       return nouveau_bo_validate(nvbo, true, false);
> +}
> +
>  static const struct drm_gpuvm_ops gpuvm_ops = {
>         .vm_free = nouveau_uvmm_free,
> +       .vm_bo_validate = nouveau_uvmm_bo_validate,
>  };
>  
>  int


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-02 13:18     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> GPUVM provides common infrastructure to track external and evicted
> GEM
> objects as well as locking and validation helpers.
> 
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all
> mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us
> of
> it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
NIT: Multiple checkpatch warnings in this one.

> ---
>  drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
>  drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
>  drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
>  drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
>  drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++-------
> --
>  6 files changed, 90 insertions(+), 90 deletions(-)
> 
> diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c
> b/drivers/gpu/drm/nouveau/nouveau_bo.c
> index 7afad86da64b..b7dda486a7ea 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_bo.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
> @@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  {
>         struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
>         struct nouveau_bo *nvbo = nouveau_bo(bo);
> +       struct drm_gem_object *obj = &bo->base;
>         struct ttm_resource *old_reg = bo->resource;
>         struct nouveau_drm_tile *new_tile = NULL;
>         int ret = 0;
>  
> -
>         if (new_reg->mem_type == TTM_PL_TT) {
>                 ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm,
> new_reg);
>                 if (ret)
>                         return ret;
>         }
>  
> +       drm_gpuvm_bo_gem_evict(obj, evict);
>         nouveau_bo_move_ntfy(bo, new_reg);
>         ret = ttm_bo_wait_ctx(bo, ctx);
>         if (ret)
> @@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  out_ntfy:
>         if (ret) {
>                 nouveau_bo_move_ntfy(bo, bo->resource);
> +               drm_gpuvm_bo_gem_evict(obj, !evict);
>         }
>         return ret;
>  }
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c
> b/drivers/gpu/drm/nouveau/nouveau_exec.c
> index bf6c12f4342a..9d9835fb5970 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
> @@ -1,7 +1,5 @@
>  // SPDX-License-Identifier: MIT
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_gem.h"
>  #include "nouveau_mem.h"
> @@ -86,14 +84,12 @@
>   */
>  
>  static int
> -nouveau_exec_job_submit(struct nouveau_job *job)
> +nouveau_exec_job_submit(struct nouveau_job *job,
> +                       struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
>         struct nouveau_cli *cli = job->cli;
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
>         int ret;
>  
>         /* Create a new fence, but do not emit yet. */
> @@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
>                 return ret;
>  
>         nouveau_uvmm_lock(uvmm);
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               struct drm_gpuva *va;
> -
> -               drm_gpuvm_for_each_va(va, &uvmm->base) {
> -                       if (unlikely(va == &uvmm-
> >base.kernel_alloc_node))
> -                               continue;
> -
> -                       ret = drm_exec_prepare_obj(exec, va->gem.obj,
> 1);
> -                       drm_exec_retry_on_contention(exec);
> -                       if (ret)
> -                               goto err_uvmm_unlock;
> -               }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret) {
> +               nouveau_uvmm_unlock(uvmm);
> +               return ret;
>         }
>         nouveau_uvmm_unlock(uvmm);
>  
> -       drm_exec_for_each_locked_object(exec, index, obj) {
> -               struct nouveau_bo *nvbo = nouveau_gem_object(obj);
> -
> -               ret = nouveau_bo_validate(nvbo, true, false);
> -               if (ret)
> -                       goto err_exec_fini;
> +       ret = drm_gpuvm_exec_validate(vme);
> +       if (ret) {
> +               drm_gpuvm_exec_unlock(vme);
> +               return ret;
>         }
>  
>         return 0;
> -
> -err_uvmm_unlock:
> -       nouveau_uvmm_unlock(uvmm);
> -err_exec_fini:
> -       drm_exec_fini(exec);
> -       return ret;
> -
>  }
>  
>  static void
> -nouveau_exec_job_armed_submit(struct nouveau_job *job)
> +nouveau_exec_job_armed_submit(struct nouveau_job *job,
> +                             struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h
> b/drivers/gpu/drm/nouveau/nouveau_exec.h
> index 778cacd90f65..b815de2428f3 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
> @@ -3,16 +3,12 @@
>  #ifndef __NOUVEAU_EXEC_H__
>  #define __NOUVEAU_EXEC_H__
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_sched.h"
>  
>  struct nouveau_exec_job_args {
>         struct drm_file *file_priv;
>         struct nouveau_sched_entity *sched_entity;
> -
> -       struct drm_exec exec;
>         struct nouveau_channel *chan;
>  
>         struct {
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c
> b/drivers/gpu/drm/nouveau/nouveau_sched.c
> index 7e64b5ef90fb..0416fd6b6a40 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
> @@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
>  {
>         struct nouveau_sched_entity *entity =
> to_nouveau_sched_entity(job->base.entity);
>         struct dma_fence *done_fence = NULL;
> +       struct drm_gpuvm_exec vm_exec = {
> +               .vm = &nouveau_cli_uvmm(job->cli)->base,
> +               .flags = DRM_EXEC_IGNORE_DUPLICATES,
> +               .num_fences = 1,
> +       };
>         int ret;
>  
>         ret = nouveau_job_add_deps(job);
> @@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
>          * successfully.
>          */
>         if (job->ops->submit) {
> -               ret = job->ops->submit(job);
> +               ret = job->ops->submit(job, &vm_exec);
>                 if (ret)
>                         goto err_cleanup;
>         }
> @@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
>         set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence-
> >flags);
>  
>         if (job->ops->armed_submit)
> -               job->ops->armed_submit(job);
> +               job->ops->armed_submit(job, &vm_exec);
>  
>         nouveau_job_fence_attach(job);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h
> b/drivers/gpu/drm/nouveau/nouveau_sched.h
> index 27ac19792597..0f87697dbc9e 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
> @@ -5,7 +5,7 @@
>  
>  #include <linux/types.h>
>  
> -#include <drm/drm_exec.h>
> +#include <drm/drm_gpuvm.h>
>  #include <drm/gpu_scheduler.h>
>  
>  #include "nouveau_drv.h"
> @@ -54,7 +54,6 @@ struct nouveau_job {
>         struct drm_file *file_priv;
>         struct nouveau_cli *cli;
>  
> -       struct drm_exec exec;
>         enum dma_resv_usage resv_usage;
>         struct dma_fence *done_fence;
>  
> @@ -76,8 +75,8 @@ struct nouveau_job {
>                 /* If .submit() returns without any error, it is
> guaranteed that
>                  * armed_submit() is called.
>                  */
> -               int (*submit)(struct nouveau_job *);
> -               void (*armed_submit)(struct nouveau_job *);
> +               int (*submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
> +               void (*armed_submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
>                 struct dma_fence *(*run)(struct nouveau_job *);
>                 void (*free)(struct nouveau_job *);
>                 enum drm_gpu_sched_stat (*timeout)(struct nouveau_job
> *);
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index eda7bb8624f1..2bb72fff06e0 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct
> nouveau_uvma_region *reg)
>  static void
>  op_map_prepare_unwind(struct nouveau_uvma *uvma)
>  {
> +       struct drm_gpuva *va = &uvma->va;
>         nouveau_uvma_gem_put(uvma);
> -       drm_gpuva_remove(&uvma->va);
> +       drm_gpuva_remove(va);
>         nouveau_uvma_free(uvma);
>  }
>  
> @@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         break;
>                 case DRM_GPUVA_OP_REMAP: {
>                         struct drm_gpuva_op_remap *r = &op->remap;
> +                       struct drm_gpuva *va = r->unmap->va;
>  
>                         if (r->next)
>                                 op_map_prepare_unwind(new->next);
> @@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         if (r->prev)
>                                 op_map_prepare_unwind(new->prev);
>  
> -                       op_unmap_prepare_unwind(r->unmap->va);
> +                       op_unmap_prepare_unwind(va);
>                         break;
>                 }
>                 case DRM_GPUVA_OP_UNMAP:
> @@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm
> *uvmm,
>                                         goto unwind;
>                                 }
>                         }
> +
>                         break;
>                 }
>                 case DRM_GPUVA_OP_REMAP: {
> @@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
>  }
>  
>  static int
> -nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
> +bind_lock_extra(struct drm_gpuvm_exec *vme)
> +{
> +       struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
> +       struct drm_exec *exec = &vme->exec;
> +       struct bind_job_op *op;
> +       int ret;
> +
> +       list_for_each_op(op, &bind_job->ops) {
> +               struct drm_gpuva_op *va_op;
> +
> +               if (IS_ERR_OR_NULL(op->ops))
> +                       continue;
> +
> +               drm_gpuva_for_each_op(va_op, op->ops) {
> +                       struct drm_gem_object *obj =
> op_gem_obj(va_op);
> +
> +                       if (unlikely(!obj))
> +                               continue;
> +
> +                       if (va_op->op != DRM_GPUVA_OP_UNMAP)
> +                               continue;
> +
> +                       ret = drm_exec_prepare_obj(exec, obj, vme-
> >num_fences);
> +                       if (ret)
> +                               return ret;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
> +                            struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
>         struct nouveau_uvmm_bind_job *bind_job =
> to_uvmm_bind_job(job);
>         struct nouveau_sched_entity *entity = job->entity;
> -       struct drm_exec *exec = &job->exec;
>         struct bind_job_op *op;
>         int ret;
>  
> @@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                         dma_resv_unlock(obj->resv);
>                         if (IS_ERR(op->vm_bo))
>                                 return PTR_ERR(op->vm_bo);
> +
> +                       drm_gpuvm_bo_extobj_add(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>          * unwind all GPU VA space changes on failure.
>          */
>         nouveau_uvmm_lock(uvmm);
> +
>         list_for_each_op(op, &bind_job->ops) {
>                 switch (op->op) {
>                 case OP_MAP_SPARSE:
> @@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>                 }
>         }
>  
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               list_for_each_op(op, &bind_job->ops) {
> -                       struct drm_gpuva_op *va_op;
> +       vme->extra.fn = bind_lock_extra;
> +       vme->extra.priv = bind_job;
>  
> -                       if (IS_ERR_OR_NULL(op->ops))
> -                               continue;
> -
> -                       drm_gpuva_for_each_op(va_op, op->ops) {
> -                               struct drm_gem_object *obj =
> op_gem_obj(va_op);
> -
> -                               if (unlikely(!obj))
> -                                       continue;
> -
> -                               ret = drm_exec_prepare_obj(exec, obj,
> 1);
> -                               drm_exec_retry_on_contention(exec);
> -                               if (ret) {
> -                                       op = list_last_op(&bind_job-
> >ops);
> -                                       goto unwind;
> -                               }
> -                       }
> -               }
> -       }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret)
> +               goto unwind_continue;
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 struct drm_gpuva_op *va_op;
> @@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>         }
>  
>         nouveau_uvmm_unlock(uvmm);
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_unlock(vme);
>         return ret;
>  }
>  
>  static void
> -nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
> +nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
> +                                  struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> @@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>         kfree(uvmm);
>  }
>  
> +static int
> +nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec
> *exec)
> +{
> +       struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
> +
> +       return nouveau_bo_validate(nvbo, true, false);
> +}
> +
>  static const struct drm_gpuvm_ops gpuvm_ops = {
>         .vm_free = nouveau_uvmm_free,
> +       .vm_bo_validate = nouveau_uvmm_bo_validate,
>  };
>  
>  int


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure
@ 2023-11-02 13:18     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> GPUVM provides common infrastructure to track external and evicted
> GEM
> objects as well as locking and validation helpers.
> 
> Especially external and evicted object tracking is a huge improvement
> compared to the current brute force approach of iterating all
> mappings
> in order to lock and validate the GPUVM's GEM objects. Hence, make us
> of
> it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
NIT: Multiple checkpatch warnings in this one.

> ---
>  drivers/gpu/drm/nouveau/nouveau_bo.c    |  4 +-
>  drivers/gpu/drm/nouveau/nouveau_exec.c  | 57 ++++----------
>  drivers/gpu/drm/nouveau/nouveau_exec.h  |  4 -
>  drivers/gpu/drm/nouveau/nouveau_sched.c |  9 ++-
>  drivers/gpu/drm/nouveau/nouveau_sched.h |  7 +-
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c  | 99 ++++++++++++++++-------
> --
>  6 files changed, 90 insertions(+), 90 deletions(-)
> 
> diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c
> b/drivers/gpu/drm/nouveau/nouveau_bo.c
> index 7afad86da64b..b7dda486a7ea 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_bo.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
> @@ -1061,17 +1061,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  {
>         struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
>         struct nouveau_bo *nvbo = nouveau_bo(bo);
> +       struct drm_gem_object *obj = &bo->base;
>         struct ttm_resource *old_reg = bo->resource;
>         struct nouveau_drm_tile *new_tile = NULL;
>         int ret = 0;
>  
> -
>         if (new_reg->mem_type == TTM_PL_TT) {
>                 ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm,
> new_reg);
>                 if (ret)
>                         return ret;
>         }
>  
> +       drm_gpuvm_bo_gem_evict(obj, evict);
>         nouveau_bo_move_ntfy(bo, new_reg);
>         ret = ttm_bo_wait_ctx(bo, ctx);
>         if (ret)
> @@ -1136,6 +1137,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo,
> bool evict,
>  out_ntfy:
>         if (ret) {
>                 nouveau_bo_move_ntfy(bo, bo->resource);
> +               drm_gpuvm_bo_gem_evict(obj, !evict);
>         }
>         return ret;
>  }
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c
> b/drivers/gpu/drm/nouveau/nouveau_exec.c
> index bf6c12f4342a..9d9835fb5970 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
> @@ -1,7 +1,5 @@
>  // SPDX-License-Identifier: MIT
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_gem.h"
>  #include "nouveau_mem.h"
> @@ -86,14 +84,12 @@
>   */
>  
>  static int
> -nouveau_exec_job_submit(struct nouveau_job *job)
> +nouveau_exec_job_submit(struct nouveau_job *job,
> +                       struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
>         struct nouveau_cli *cli = job->cli;
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
>         int ret;
>  
>         /* Create a new fence, but do not emit yet. */
> @@ -102,52 +98,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
>                 return ret;
>  
>         nouveau_uvmm_lock(uvmm);
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               struct drm_gpuva *va;
> -
> -               drm_gpuvm_for_each_va(va, &uvmm->base) {
> -                       if (unlikely(va == &uvmm-
> >base.kernel_alloc_node))
> -                               continue;
> -
> -                       ret = drm_exec_prepare_obj(exec, va->gem.obj,
> 1);
> -                       drm_exec_retry_on_contention(exec);
> -                       if (ret)
> -                               goto err_uvmm_unlock;
> -               }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret) {
> +               nouveau_uvmm_unlock(uvmm);
> +               return ret;
>         }
>         nouveau_uvmm_unlock(uvmm);
>  
> -       drm_exec_for_each_locked_object(exec, index, obj) {
> -               struct nouveau_bo *nvbo = nouveau_gem_object(obj);
> -
> -               ret = nouveau_bo_validate(nvbo, true, false);
> -               if (ret)
> -                       goto err_exec_fini;
> +       ret = drm_gpuvm_exec_validate(vme);
> +       if (ret) {
> +               drm_gpuvm_exec_unlock(vme);
> +               return ret;
>         }
>  
>         return 0;
> -
> -err_uvmm_unlock:
> -       nouveau_uvmm_unlock(uvmm);
> -err_exec_fini:
> -       drm_exec_fini(exec);
> -       return ret;
> -
>  }
>  
>  static void
> -nouveau_exec_job_armed_submit(struct nouveau_job *job)
> +nouveau_exec_job_armed_submit(struct nouveau_job *job,
> +                             struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h
> b/drivers/gpu/drm/nouveau/nouveau_exec.h
> index 778cacd90f65..b815de2428f3 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_exec.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
> @@ -3,16 +3,12 @@
>  #ifndef __NOUVEAU_EXEC_H__
>  #define __NOUVEAU_EXEC_H__
>  
> -#include <drm/drm_exec.h>
> -
>  #include "nouveau_drv.h"
>  #include "nouveau_sched.h"
>  
>  struct nouveau_exec_job_args {
>         struct drm_file *file_priv;
>         struct nouveau_sched_entity *sched_entity;
> -
> -       struct drm_exec exec;
>         struct nouveau_channel *chan;
>  
>         struct {
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c
> b/drivers/gpu/drm/nouveau/nouveau_sched.c
> index 7e64b5ef90fb..0416fd6b6a40 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c
> @@ -263,6 +263,11 @@ nouveau_job_submit(struct nouveau_job *job)
>  {
>         struct nouveau_sched_entity *entity =
> to_nouveau_sched_entity(job->base.entity);
>         struct dma_fence *done_fence = NULL;
> +       struct drm_gpuvm_exec vm_exec = {
> +               .vm = &nouveau_cli_uvmm(job->cli)->base,
> +               .flags = DRM_EXEC_IGNORE_DUPLICATES,
> +               .num_fences = 1,
> +       };
>         int ret;
>  
>         ret = nouveau_job_add_deps(job);
> @@ -282,7 +287,7 @@ nouveau_job_submit(struct nouveau_job *job)
>          * successfully.
>          */
>         if (job->ops->submit) {
> -               ret = job->ops->submit(job);
> +               ret = job->ops->submit(job, &vm_exec);
>                 if (ret)
>                         goto err_cleanup;
>         }
> @@ -315,7 +320,7 @@ nouveau_job_submit(struct nouveau_job *job)
>         set_bit(DRM_SCHED_FENCE_DONT_PIPELINE, &job->done_fence-
> >flags);
>  
>         if (job->ops->armed_submit)
> -               job->ops->armed_submit(job);
> +               job->ops->armed_submit(job, &vm_exec);
>  
>         nouveau_job_fence_attach(job);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h
> b/drivers/gpu/drm/nouveau/nouveau_sched.h
> index 27ac19792597..0f87697dbc9e 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_sched.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
> @@ -5,7 +5,7 @@
>  
>  #include <linux/types.h>
>  
> -#include <drm/drm_exec.h>
> +#include <drm/drm_gpuvm.h>
>  #include <drm/gpu_scheduler.h>
>  
>  #include "nouveau_drv.h"
> @@ -54,7 +54,6 @@ struct nouveau_job {
>         struct drm_file *file_priv;
>         struct nouveau_cli *cli;
>  
> -       struct drm_exec exec;
>         enum dma_resv_usage resv_usage;
>         struct dma_fence *done_fence;
>  
> @@ -76,8 +75,8 @@ struct nouveau_job {
>                 /* If .submit() returns without any error, it is
> guaranteed that
>                  * armed_submit() is called.
>                  */
> -               int (*submit)(struct nouveau_job *);
> -               void (*armed_submit)(struct nouveau_job *);
> +               int (*submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
> +               void (*armed_submit)(struct nouveau_job *, struct
> drm_gpuvm_exec *);
>                 struct dma_fence *(*run)(struct nouveau_job *);
>                 void (*free)(struct nouveau_job *);
>                 enum drm_gpu_sched_stat (*timeout)(struct nouveau_job
> *);
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index eda7bb8624f1..2bb72fff06e0 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct
> nouveau_uvma_region *reg)
>  static void
>  op_map_prepare_unwind(struct nouveau_uvma *uvma)
>  {
> +       struct drm_gpuva *va = &uvma->va;
>         nouveau_uvma_gem_put(uvma);
> -       drm_gpuva_remove(&uvma->va);
> +       drm_gpuva_remove(va);
>         nouveau_uvma_free(uvma);
>  }
>  
> @@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         break;
>                 case DRM_GPUVA_OP_REMAP: {
>                         struct drm_gpuva_op_remap *r = &op->remap;
> +                       struct drm_gpuva *va = r->unmap->va;
>  
>                         if (r->next)
>                                 op_map_prepare_unwind(new->next);
> @@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct
> nouveau_uvmm *uvmm,
>                         if (r->prev)
>                                 op_map_prepare_unwind(new->prev);
>  
> -                       op_unmap_prepare_unwind(r->unmap->va);
> +                       op_unmap_prepare_unwind(va);
>                         break;
>                 }
>                 case DRM_GPUVA_OP_UNMAP:
> @@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm
> *uvmm,
>                                         goto unwind;
>                                 }
>                         }
> +
>                         break;
>                 }
>                 case DRM_GPUVA_OP_REMAP: {
> @@ -1135,12 +1138,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
>  }
>  
>  static int
> -nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
> +bind_lock_extra(struct drm_gpuvm_exec *vme)
> +{
> +       struct nouveau_uvmm_bind_job *bind_job = vme->extra.priv;
> +       struct drm_exec *exec = &vme->exec;
> +       struct bind_job_op *op;
> +       int ret;
> +
> +       list_for_each_op(op, &bind_job->ops) {
> +               struct drm_gpuva_op *va_op;
> +
> +               if (IS_ERR_OR_NULL(op->ops))
> +                       continue;
> +
> +               drm_gpuva_for_each_op(va_op, op->ops) {
> +                       struct drm_gem_object *obj =
> op_gem_obj(va_op);
> +
> +                       if (unlikely(!obj))
> +                               continue;
> +
> +                       if (va_op->op != DRM_GPUVA_OP_UNMAP)
> +                               continue;
> +
> +                       ret = drm_exec_prepare_obj(exec, obj, vme-
> >num_fences);
> +                       if (ret)
> +                               return ret;
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
> +                            struct drm_gpuvm_exec *vme)
>  {
>         struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
>         struct nouveau_uvmm_bind_job *bind_job =
> to_uvmm_bind_job(job);
>         struct nouveau_sched_entity *entity = job->entity;
> -       struct drm_exec *exec = &job->exec;
>         struct bind_job_op *op;
>         int ret;
>  
> @@ -1157,6 +1192,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                         dma_resv_unlock(obj->resv);
>                         if (IS_ERR(op->vm_bo))
>                                 return PTR_ERR(op->vm_bo);
> +
> +                       drm_gpuvm_bo_extobj_add(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1179,6 +1216,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>          * unwind all GPU VA space changes on failure.
>          */
>         nouveau_uvmm_lock(uvmm);
> +
>         list_for_each_op(op, &bind_job->ops) {
>                 switch (op->op) {
>                 case OP_MAP_SPARSE:
> @@ -1290,30 +1328,12 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>                 }
>         }
>  
> -       drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
> -                           DRM_EXEC_IGNORE_DUPLICATES);
> -       drm_exec_until_all_locked(exec) {
> -               list_for_each_op(op, &bind_job->ops) {
> -                       struct drm_gpuva_op *va_op;
> +       vme->extra.fn = bind_lock_extra;
> +       vme->extra.priv = bind_job;
>  
> -                       if (IS_ERR_OR_NULL(op->ops))
> -                               continue;
> -
> -                       drm_gpuva_for_each_op(va_op, op->ops) {
> -                               struct drm_gem_object *obj =
> op_gem_obj(va_op);
> -
> -                               if (unlikely(!obj))
> -                                       continue;
> -
> -                               ret = drm_exec_prepare_obj(exec, obj,
> 1);
> -                               drm_exec_retry_on_contention(exec);
> -                               if (ret) {
> -                                       op = list_last_op(&bind_job-
> >ops);
> -                                       goto unwind;
> -                               }
> -                       }
> -               }
> -       }
> +       ret = drm_gpuvm_exec_lock(vme);
> +       if (ret)
> +               goto unwind_continue;
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 struct drm_gpuva_op *va_op;
> @@ -1413,21 +1433,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>         }
>  
>         nouveau_uvmm_unlock(uvmm);
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_unlock(vme);
>         return ret;
>  }
>  
>  static void
> -nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
> +nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job,
> +                                  struct drm_gpuvm_exec *vme)
>  {
> -       struct drm_exec *exec = &job->exec;
> -       struct drm_gem_object *obj;
> -       unsigned long index;
> -
> -       drm_exec_for_each_locked_object(exec, index, obj)
> -               dma_resv_add_fence(obj->resv, job->done_fence, job-
> >resv_usage);
> -
> -       drm_exec_fini(exec);
> +       drm_gpuvm_exec_resv_add_fence(vme, job->done_fence,
> +                                     job->resv_usage, job-
> >resv_usage);
> +       drm_gpuvm_exec_unlock(vme);
>  }
>  
>  static struct dma_fence *
> @@ -1815,8 +1831,17 @@ nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>         kfree(uvmm);
>  }
>  
> +static int
> +nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec
> *exec)
> +{
> +       struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
> +
> +       return nouveau_bo_validate(nvbo, true, false);
> +}
> +
>  static const struct drm_gpuvm_ops gpuvm_ops = {
>         .vm_free = nouveau_uvmm_free,
> +       .vm_bo_validate = nouveau_uvmm_bo_validate,
>  };
>  
>  int


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
  2023-11-01 23:30   ` Danilo Krummrich
  (?)
@ 2023-11-02 13:19     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:19 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Don't always WARN in drm_gpuvm_check_overflow() and separate it into
> a
> drm_gpuvm_check_overflow() and a dedicated
> drm_gpuvm_warn_check_overflow() variant.
> 
> This avoids printing warnings due to invalid userspace requests.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
>  1 file changed, 13 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index d7367a202fee..445767f8fbc4 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm
> *gpuvm,
>  static void __drm_gpuva_remove(struct drm_gpuva *va);
>  
>  static bool
> -drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +drm_gpuvm_check_overflow(u64 addr, u64 range)
>  {
>         u64 end;
>  
> -       return drm_WARN(gpuvm->drm, check_add_overflow(addr, range,
> &end),
> -                       "GPUVA address limited to %zu bytes.\n",
> sizeof(end));
> +       return check_add_overflow(addr, range, &end);
> +}
> +
> +static bool
> +drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +{
> +       return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr,
> range),
> +                       "GPUVA address limited to %zu bytes.\n",
> sizeof(addr));
>  }
>  
>  static bool
> @@ -647,7 +653,7 @@ static bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> -       return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
> +       return !drm_gpuvm_check_overflow(addr, range) &&
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> @@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->ops = ops;
>         gpuvm->drm = drm;
>  
> -       drm_gpuvm_check_overflow(gpuvm, start_offset, range);
> +       drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
>         gpuvm->mm_start = start_offset;
>         gpuvm->mm_range = range;
>  
> @@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>                 gpuvm->kernel_alloc_node.va.addr = reserve_offset;
>                 gpuvm->kernel_alloc_node.va.range = reserve_range;
>  
> -               if (likely(!drm_gpuvm_check_overflow(gpuvm,
> reserve_offset,
> -                                                    reserve_range)))
> +               if (likely(!drm_gpuvm_warn_check_overflow(gpuvm,
> reserve_offset,
> +                                                        
> reserve_range)))
>                         __drm_gpuva_insert(gpuvm, &gpuvm-
> >kernel_alloc_node);
>         }
>  }


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
@ 2023-11-02 13:19     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:19 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Don't always WARN in drm_gpuvm_check_overflow() and separate it into
> a
> drm_gpuvm_check_overflow() and a dedicated
> drm_gpuvm_warn_check_overflow() variant.
> 
> This avoids printing warnings due to invalid userspace requests.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
>  1 file changed, 13 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index d7367a202fee..445767f8fbc4 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm
> *gpuvm,
>  static void __drm_gpuva_remove(struct drm_gpuva *va);
>  
>  static bool
> -drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +drm_gpuvm_check_overflow(u64 addr, u64 range)
>  {
>         u64 end;
>  
> -       return drm_WARN(gpuvm->drm, check_add_overflow(addr, range,
> &end),
> -                       "GPUVA address limited to %zu bytes.\n",
> sizeof(end));
> +       return check_add_overflow(addr, range, &end);
> +}
> +
> +static bool
> +drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +{
> +       return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr,
> range),
> +                       "GPUVA address limited to %zu bytes.\n",
> sizeof(addr));
>  }
>  
>  static bool
> @@ -647,7 +653,7 @@ static bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> -       return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
> +       return !drm_gpuvm_check_overflow(addr, range) &&
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> @@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->ops = ops;
>         gpuvm->drm = drm;
>  
> -       drm_gpuvm_check_overflow(gpuvm, start_offset, range);
> +       drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
>         gpuvm->mm_start = start_offset;
>         gpuvm->mm_range = range;
>  
> @@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>                 gpuvm->kernel_alloc_node.va.addr = reserve_offset;
>                 gpuvm->kernel_alloc_node.va.range = reserve_range;
>  
> -               if (likely(!drm_gpuvm_check_overflow(gpuvm,
> reserve_offset,
> -                                                    reserve_range)))
> +               if (likely(!drm_gpuvm_warn_check_overflow(gpuvm,
> reserve_offset,
> +                                                        
> reserve_range)))
>                         __drm_gpuva_insert(gpuvm, &gpuvm-
> >kernel_alloc_node);
>         }
>  }


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow()
@ 2023-11-02 13:19     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:19 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Don't always WARN in drm_gpuvm_check_overflow() and separate it into
> a
> drm_gpuvm_check_overflow() and a dedicated
> drm_gpuvm_warn_check_overflow() variant.
> 
> This avoids printing warnings due to invalid userspace requests.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 20 +++++++++++++-------
>  1 file changed, 13 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index d7367a202fee..445767f8fbc4 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -614,12 +614,18 @@ static int __drm_gpuva_insert(struct drm_gpuvm
> *gpuvm,
>  static void __drm_gpuva_remove(struct drm_gpuva *va);
>  
>  static bool
> -drm_gpuvm_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +drm_gpuvm_check_overflow(u64 addr, u64 range)
>  {
>         u64 end;
>  
> -       return drm_WARN(gpuvm->drm, check_add_overflow(addr, range,
> &end),
> -                       "GPUVA address limited to %zu bytes.\n",
> sizeof(end));
> +       return check_add_overflow(addr, range, &end);
> +}
> +
> +static bool
> +drm_gpuvm_warn_check_overflow(struct drm_gpuvm *gpuvm, u64 addr, u64
> range)
> +{
> +       return drm_WARN(gpuvm->drm, drm_gpuvm_check_overflow(addr,
> range),
> +                       "GPUVA address limited to %zu bytes.\n",
> sizeof(addr));
>  }
>  
>  static bool
> @@ -647,7 +653,7 @@ static bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> -       return !drm_gpuvm_check_overflow(gpuvm, addr, range) &&
> +       return !drm_gpuvm_check_overflow(addr, range) &&
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> @@ -682,7 +688,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->ops = ops;
>         gpuvm->drm = drm;
>  
> -       drm_gpuvm_check_overflow(gpuvm, start_offset, range);
> +       drm_gpuvm_warn_check_overflow(gpuvm, start_offset, range);
>         gpuvm->mm_start = start_offset;
>         gpuvm->mm_range = range;
>  
> @@ -691,8 +697,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>                 gpuvm->kernel_alloc_node.va.addr = reserve_offset;
>                 gpuvm->kernel_alloc_node.va.range = reserve_range;
>  
> -               if (likely(!drm_gpuvm_check_overflow(gpuvm,
> reserve_offset,
> -                                                    reserve_range)))
> +               if (likely(!drm_gpuvm_warn_check_overflow(gpuvm,
> reserve_offset,
> +                                                        
> reserve_range)))
>                         __drm_gpuva_insert(gpuvm, &gpuvm-
> >kernel_alloc_node);
>         }
>  }


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
  2023-11-01 23:30   ` Danilo Krummrich
  (?)
@ 2023-11-02 13:20     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:20 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Drivers may use this function to validate userspace requests in
> advance,
> hence export it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
>  include/drm/drm_gpuvm.h     |  1 +
>  2 files changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 445767f8fbc4..2669f9bbc377 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range)
>         return krange && addr < kend && kstart < end;
>  }
>  
> -static bool
> +/**
> + * drm_gpuvm_range_valid() - checks whether the given range is valid
> for the
> + * given &drm_gpuvm
> + * @gpuvm: the GPUVM to check the range for
> + * @addr: the base address
> + * @range: the range starting from the base address
> + *
> + * Checks whether the range is within the GPUVM's managed
> boundaries.
> + *
> + * Returns: true for a valid range, false otherwise
> + */
> +bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> @@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> +EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
>  
>  /**
>   * drm_gpuvm_init() - initialize a &drm_gpuvm
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 687fd5893624..13eac6f70061 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     const struct drm_gpuvm_ops *ops);
>  void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>  
> +bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  
>  static inline struct drm_gpuva *


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
@ 2023-11-02 13:20     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:20 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Drivers may use this function to validate userspace requests in
> advance,
> hence export it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
>  include/drm/drm_gpuvm.h     |  1 +
>  2 files changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 445767f8fbc4..2669f9bbc377 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range)
>         return krange && addr < kend && kstart < end;
>  }
>  
> -static bool
> +/**
> + * drm_gpuvm_range_valid() - checks whether the given range is valid
> for the
> + * given &drm_gpuvm
> + * @gpuvm: the GPUVM to check the range for
> + * @addr: the base address
> + * @range: the range starting from the base address
> + *
> + * Checks whether the range is within the GPUVM's managed
> boundaries.
> + *
> + * Returns: true for a valid range, false otherwise
> + */
> +bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> @@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> +EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
>  
>  /**
>   * drm_gpuvm_init() - initialize a &drm_gpuvm
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 687fd5893624..13eac6f70061 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     const struct drm_gpuvm_ops *ops);
>  void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>  
> +bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  
>  static inline struct drm_gpuva *


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid()
@ 2023-11-02 13:20     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:20 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:30 +0100, Danilo Krummrich wrote:
> Drivers may use this function to validate userspace requests in
> advance,
> hence export it.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c | 14 +++++++++++++-
>  include/drm/drm_gpuvm.h     |  1 +
>  2 files changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 445767f8fbc4..2669f9bbc377 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -649,7 +649,18 @@ drm_gpuvm_in_kernel_node(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range)
>         return krange && addr < kend && kstart < end;
>  }
>  
> -static bool
> +/**
> + * drm_gpuvm_range_valid() - checks whether the given range is valid
> for the
> + * given &drm_gpuvm
> + * @gpuvm: the GPUVM to check the range for
> + * @addr: the base address
> + * @range: the range starting from the base address
> + *
> + * Checks whether the range is within the GPUVM's managed
> boundaries.
> + *
> + * Returns: true for a valid range, false otherwise
> + */
> +bool
>  drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                       u64 addr, u64 range)
>  {
> @@ -657,6 +668,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                drm_gpuvm_in_mm_range(gpuvm, addr, range) &&
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
> +EXPORT_SYMBOL_GPL(drm_gpuvm_range_valid);
>  
>  /**
>   * drm_gpuvm_init() - initialize a &drm_gpuvm
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 687fd5893624..13eac6f70061 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -253,6 +253,7 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     const struct drm_gpuvm_ops *ops);
>  void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>  
> +bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  
>  static inline struct drm_gpuva *


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 13:21     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:21 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Will port the Xe series over to check that it works properly and get
back with review on this one.


> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>  
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>         }
>  
>         __drm_gpuva_remove(va);
> +       drm_gpuvm_put(va->vm);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo
> *nvbo)
>         }
>  }
>  
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +       struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +       kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +       .vm_free = nouveau_uvmm_free,
> +};
> +
>  int
>  nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>                            void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>                        NOUVEAU_VA_SPACE_END,
>                        init->kernel_managed_addr,
>                        init->kernel_managed_size,
> -                      NULL);
> +                      &gpuvm_ops);
>         /* GPUVM takes care from here on. */
>         drm_gem_object_put(r_obj);
>  
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>         return 0;
>  
>  out_gpuvm_fini:
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>  out_unlock:
>         mutex_unlock(&cli->mutex);
>         return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>  
>         mutex_lock(&cli->mutex);
>         nouveau_vmm_fini(&uvmm->vmm);
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>         mutex_unlock(&cli->mutex);
>  }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>                 struct list_head list;
>         } rb;
>  
> +       /**
> +        * @kref: reference count of this object
> +        */
> +       struct kref kref;
> +
>         /**
>          * @kernel_alloc_node:
>          *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     u64 start_offset, u64 range,
>                     u64 reserve_offset, u64 reserve_range,
>                     const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +       kref_get(&gpuvm->kref);
> +
> +       return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>  
>  bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct
> drm_gpuva *va,
>   * operations to drivers.
>   */
>  struct drm_gpuvm_ops {
> +       /**
> +        * @vm_free: called when the last reference of a struct
> drm_gpuvm is
> +        * dropped
> +        *
> +        * This callback is mandatory.
> +        */
> +       void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>         /**
>          * @op_alloc: called when the &drm_gpuvm allocates
>          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 13:21     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:21 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Will port the Xe series over to check that it works properly and get
back with review on this one.


> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>  
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>         }
>  
>         __drm_gpuva_remove(va);
> +       drm_gpuvm_put(va->vm);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo
> *nvbo)
>         }
>  }
>  
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +       struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +       kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +       .vm_free = nouveau_uvmm_free,
> +};
> +
>  int
>  nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>                            void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>                        NOUVEAU_VA_SPACE_END,
>                        init->kernel_managed_addr,
>                        init->kernel_managed_size,
> -                      NULL);
> +                      &gpuvm_ops);
>         /* GPUVM takes care from here on. */
>         drm_gem_object_put(r_obj);
>  
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>         return 0;
>  
>  out_gpuvm_fini:
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>  out_unlock:
>         mutex_unlock(&cli->mutex);
>         return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>  
>         mutex_lock(&cli->mutex);
>         nouveau_vmm_fini(&uvmm->vmm);
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>         mutex_unlock(&cli->mutex);
>  }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>                 struct list_head list;
>         } rb;
>  
> +       /**
> +        * @kref: reference count of this object
> +        */
> +       struct kref kref;
> +
>         /**
>          * @kernel_alloc_node:
>          *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     u64 start_offset, u64 range,
>                     u64 reserve_offset, u64 reserve_range,
>                     const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +       kref_get(&gpuvm->kref);
> +
> +       return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>  
>  bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct
> drm_gpuva *va,
>   * operations to drivers.
>   */
>  struct drm_gpuvm_ops {
> +       /**
> +        * @vm_free: called when the last reference of a struct
> drm_gpuvm is
> +        * dropped
> +        *
> +        * This callback is mandatory.
> +        */
> +       void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>         /**
>          * @op_alloc: called when the &drm_gpuvm allocates
>          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 13:21     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:21 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>

Will port the Xe series over to check that it works properly and get
back with review on this one.


> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>  
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>         }
>  
>         __drm_gpuva_remove(va);
> +       drm_gpuvm_put(va->vm);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo
> *nvbo)
>         }
>  }
>  
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +       struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +       kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +       .vm_free = nouveau_uvmm_free,
> +};
> +
>  int
>  nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>                            void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>                        NOUVEAU_VA_SPACE_END,
>                        init->kernel_managed_addr,
>                        init->kernel_managed_size,
> -                      NULL);
> +                      &gpuvm_ops);
>         /* GPUVM takes care from here on. */
>         drm_gem_object_put(r_obj);
>  
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device
> *dev,
>         return 0;
>  
>  out_gpuvm_fini:
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>  out_unlock:
>         mutex_unlock(&cli->mutex);
>         return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>  
>         mutex_lock(&cli->mutex);
>         nouveau_vmm_fini(&uvmm->vmm);
> -       drm_gpuvm_destroy(&uvmm->base);
> -       kfree(uvmm);
> +       drm_gpuvm_put(&uvmm->base);
>         mutex_unlock(&cli->mutex);
>  }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>                 struct list_head list;
>         } rb;
>  
> +       /**
> +        * @kref: reference count of this object
> +        */
> +       struct kref kref;
> +
>         /**
>          * @kernel_alloc_node:
>          *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> const char *name,
>                     u64 start_offset, u64 range,
>                     u64 reserve_offset, u64 reserve_range,
>                     const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +       kref_get(&gpuvm->kref);
> +
> +       return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>  
>  bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct
> drm_gpuva *va,
>   * operations to drivers.
>   */
>  struct drm_gpuvm_ops {
> +       /**
> +        * @vm_free: called when the last reference of a struct
> drm_gpuvm is
> +        * dropped
> +        *
> +        * This callback is mandatory.
> +        */
> +       void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>         /**
>          * @op_alloc: called when the &drm_gpuvm allocates
>          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 13:25     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:25 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.
> + */
> +void
> +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
> +{
> +       if (vm_bo)
> +               kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
> +
> +static struct drm_gpuvm_bo *
> +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> +               if (vm_bo->vm == gpuvm)
> +                       return vm_bo;
> +
> +       return NULL;
> +}
> +
> +/**
> + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
> + * &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
> +
> +       return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
> +
> +/**
> + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
> for the
> + * given &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
> new
> + * &drm_gpuvm_bo.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo)
> +               return vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> +       if (!vm_bo)
> +               return ERR_PTR(-ENOMEM);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
> +
> +/**
> + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
> &drm_gpuvm_bo
> + * for the given &drm_gpuvm and &drm_gem_object
> + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
> reference
> + * count is decreased. If not found @__vm_bo is returned without
> further
> + * increase of the reference count.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
> existing
> + * &drm_gpuvm_bo was found
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> +{
> +       struct drm_gpuvm *gpuvm = __vm_bo->vm;
> +       struct drm_gem_object *obj = __vm_bo->obj;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo) {
> +               drm_gpuvm_bo_put(__vm_bo);
> +               return vm_bo;
> +       }
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return __vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> +
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>                    struct drm_gpuva *va)
> @@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  /**
>   * drm_gpuva_link() - link a &drm_gpuva
>   * @va: the &drm_gpuva to link
> + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
>   *
> - * This adds the given &va to the GPU VA list of the &drm_gem_object
> it is
> - * associated with.
> + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
> and the
> + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
> + *
> + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
> additional
> + * reference of the latter is taken.
>   *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
> -drm_gpuva_link(struct drm_gpuva *va)
> +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
>         if (unlikely(!obj))
>                 return;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
>  
> -       list_add_tail(&va->gem.entry, &obj->gpuva.list);
> +       va->vm_bo = drm_gpuvm_bo_get(vm_bo);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_link);
>  
> @@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
>   * This removes the given &va from the GPU VA list of the
> &drm_gem_object it is
>   * associated with.
>   *
> + * This removes the given &va from the GPU VA list of the
> &drm_gpuvm_bo and
> + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
> in case
> + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
> + *
> + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
> reference of
> + * the latter is dropped.
> + *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
>  drm_gpuva_unlink(struct drm_gpuva *va)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm_bo *vm_bo = va->vm_bo;
>  
>         if (unlikely(!obj))
>                 return;
>  
>         drm_gem_gpuva_assert_lock_held(obj);
> -
>         list_del_init(&va->gem.entry);
> +
> +       va->vm_bo = NULL;
> +       drm_gpuvm_bo_put(vm_bo);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
>  
> @@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
>                 struct drm_gpuva *next,
>                 struct drm_gpuva_op_remap *op)
>  {
> -       struct drm_gpuva *curr = op->unmap->va;
> -       struct drm_gpuvm *gpuvm = curr->vm;
> +       struct drm_gpuva *va = op->unmap->va;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
> -       drm_gpuva_remove(curr);
> +       drm_gpuva_remove(va);
>  
>         if (op->prev) {
>                 drm_gpuva_init_from_op(prev, op->prev);
> @@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>  EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>  
>  /**
> - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> - * @gpuvm: the &drm_gpuvm representing the GPU VA space
> - * @obj: the &drm_gem_object to unmap
> + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> + * @vm_bo: the &drm_gpuvm_bo abstraction
>   *
>   * This function creates a list of operations to perform unmapping
> for every
>   * GPUVA attached to a GEM.
> @@ -1754,15 +1997,14 @@
> EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>   * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
> on failure
>   */
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj)
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gpuva_ops *ops;
>         struct drm_gpuva_op *op;
>         struct drm_gpuva *va;
>         int ret;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_gpuva_assert_lock_held(vm_bo->obj);
>  
>         ops = kzalloc(sizeof(*ops), GFP_KERNEL);
>         if (!ops)
> @@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
> *gpuvm,
>  
>         INIT_LIST_HEAD(&ops->list);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               op = gpuva_op_alloc(gpuvm);
> +       drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +               op = gpuva_op_alloc(vm_bo->vm);
>                 if (!op) {
>                         ret = -ENOMEM;
>                         goto err_free_ops;
> @@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
> drm_gpuvm *gpuvm,
>         return ops;
>  
>  err_free_ops:
> -       drm_gpuva_ops_free(gpuvm, ops);
> +       drm_gpuva_ops_free(vm_bo->vm, ops);
>         return ERR_PTR(ret);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
>  
>  /**
>   * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index cb2f06565c46..eda7bb8624f1 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -62,6 +62,8 @@ struct bind_job_op {
>         enum vm_bind_op op;
>         u32 flags;
>  
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         struct {
>                 u64 addr;
>                 u64 range;
> @@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
>  }
>  
>  static void
> -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
> nouveau_uvma_prealloc *new)
> +bind_link_gpuvas(struct bind_job_op *bop)
>  {
> +       struct nouveau_uvma_prealloc *new = &bop->new;
> +       struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
> +       struct drm_gpuva_ops *ops = bop->ops;
>         struct drm_gpuva_op *op;
>  
>         drm_gpuva_for_each_op(op, ops) {
>                 switch (op->op) {
>                 case DRM_GPUVA_OP_MAP:
> -                       drm_gpuva_link(&new->map->va);
> +                       drm_gpuva_link(&new->map->va, vm_bo);
>                         break;
> -               case DRM_GPUVA_OP_REMAP:
> +               case DRM_GPUVA_OP_REMAP: {
> +                       struct drm_gpuva *va = op->remap.unmap->va;
> +
>                         if (op->remap.prev)
> -                               drm_gpuva_link(&new->prev->va);
> +                               drm_gpuva_link(&new->prev->va, va-
> >vm_bo);
>                         if (op->remap.next)
> -                               drm_gpuva_link(&new->next->va);
> -                       drm_gpuva_unlink(op->remap.unmap->va);
> +                               drm_gpuva_link(&new->next->va, va-
> >vm_bo);
> +                       drm_gpuva_unlink(va);
>                         break;
> +               }
>                 case DRM_GPUVA_OP_UNMAP:
>                         drm_gpuva_unlink(op->unmap.va);
>                         break;
> @@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 if (op->op == OP_MAP) {
> -                       op->gem.obj = drm_gem_object_lookup(job-
> >file_priv,
> -                                                           op-
> >gem.handle);
> -                       if (!op->gem.obj)
> +                       struct drm_gem_object *obj = op->gem.obj =
> +                               drm_gem_object_lookup(job->file_priv,
> +                                                     op-
> >gem.handle);
> +                       if (!obj)
>                                 return -ENOENT;
> +
> +                       dma_resv_lock(obj->resv, NULL);
> +                       op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
> obj);
> +                       dma_resv_unlock(obj->resv);
> +                       if (IS_ERR(op->vm_bo))
> +                               return PTR_ERR(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                 case OP_UNMAP_SPARSE:
>                 case OP_MAP:
>                 case OP_UNMAP:
> -                       bind_link_gpuvas(op->ops, &op->new);
> +                       bind_link_gpuvas(op);
>                         break;
>                 default:
>                         break;
> @@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
> work_struct *work)
>                 if (!IS_ERR_OR_NULL(op->ops))
>                         drm_gpuva_ops_free(&uvmm->base, op->ops);
>  
> +               if (!IS_ERR_OR_NULL(op->vm_bo)) {
> +                       dma_resv_lock(obj->resv, NULL);
> +                       drm_gpuvm_bo_put(op->vm_bo);
> +                       dma_resv_unlock(obj->resv);
> +               }
> +
>                 if (obj)
>                         drm_gem_object_put(obj);
>         }
> @@ -1752,15 +1773,18 @@ void
>  nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
> *mem)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_map(uvma, mem);
> -               drm_gpuva_invalidate(va, false);
> +                       nouveau_uvma_map(uvma, mem);
> +                       drm_gpuva_invalidate(va, false);
> +               }
>         }
>  }
>  
> @@ -1768,15 +1792,18 @@ void
>  nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_unmap(uvma);
> -               drm_gpuva_invalidate(va, true);
> +                       nouveau_uvma_unmap(uvma);
> +                       drm_gpuva_invalidate(va, true);
> +               }
>         }
>  }
>  
> diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
> index 16364487fde9..369505447acd 100644
> --- a/include/drm/drm_gem.h
> +++ b/include/drm/drm_gem.h
> @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
>   * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
>   * @obj: the &drm_gem_object
>   *
> - * This initializes the &drm_gem_object's &drm_gpuva list.
> + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
>   *
>   * Calling this function is only necessary for drivers intending to
> support the
>   * &drm_driver_feature DRIVER_GEM_GPUVA.
> @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
> drm_gem_object *obj)
>  }
>  
>  /**
> - * drm_gem_for_each_gpuva() - iternator to walk over a list of
> gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
> &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
> step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> - * &drm_gpuva_manager.
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
> + * &drm_gem_object.
>   */
> -#define drm_gem_for_each_gpuva(entry__, obj__) \
> -       list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
> +       list_for_each_entry(entry__, &(obj__)->gpuva.list,
> list.entry.gem)
>  
>  /**
> - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
> list of
> - * gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @next__: &next &drm_gpuva to store the next step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
> step
> + * @next__: &next &drm_gpuvm_bo to store the next step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
>   * &drm_gem_object. It is implemented with
> list_for_each_entry_safe(), hence
>   * it is save against removal of elements.
>   */
> -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
> -       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
> +       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, list.entry.gem)
>  
>  #endif /* __DRM_GEM_H__ */
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 4e6e1fd3485a..b12fb22b0e22 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -25,6 +25,7 @@
>   * OTHER DEALINGS IN THE SOFTWARE.
>   */
>  
> +#include <linux/dma-resv.h>
>  #include <linux/list.h>
>  #include <linux/rbtree.h>
>  #include <linux/types.h>
> @@ -33,6 +34,7 @@
>  #include <drm/drm_gem.h>
>  
>  struct drm_gpuvm;
> +struct drm_gpuvm_bo;
>  struct drm_gpuvm_ops;
>  
>  /**
> @@ -73,6 +75,12 @@ struct drm_gpuva {
>          */
>         struct drm_gpuvm *vm;
>  
> +       /**
> +        * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
> +        * &drm_gem_object
> +        */
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         /**
>          * @flags: the &drm_gpuva_flags for this mapping
>          */
> @@ -108,7 +116,7 @@ struct drm_gpuva {
>                 struct drm_gem_object *obj;
>  
>                 /**
> -                * @entry: the &list_head to attach this object to a
> &drm_gem_object
> +                * @entry: the &list_head to attach this object to a
> &drm_gpuvm_bo
>                  */
>                 struct list_head entry;
>         } gem;
> @@ -141,7 +149,7 @@ struct drm_gpuva {
>  int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
>  void drm_gpuva_remove(struct drm_gpuva *va);
>  
> -void drm_gpuva_link(struct drm_gpuva *va);
> +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
> *vm_bo);
>  void drm_gpuva_unlink(struct drm_gpuva *va);
>  
>  struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
> @@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct
> drm_gpuva *va)
>   * enum drm_gpuvm_flags - flags for struct drm_gpuvm
>   */
>  enum drm_gpuvm_flags {
> +       /**
> +        * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
> by the
> +        * GPUVM's &dma_resv lock
> +        */
> +       DRM_GPUVM_RESV_PROTECTED = BIT(0),
> +
>         /**
>          * @DRM_GPUVM_USERBITS: user defined bits
>          */
> -       DRM_GPUVM_USERBITS = BIT(0),
> +       DRM_GPUVM_USERBITS = BIT(1),
>  };
>  
>  /**
> @@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range);
>  struct drm_gem_object *
>  drm_gpuvm_resv_object_alloc(struct drm_device *drm);
>  
> +/**
> + * drm_gpuvm_resv_protected() - indicates whether
> &DRM_GPUVM_RESV_PROTECTED is
> + * set
> + * @gpuvm: the &drm_gpuvm
> + *
> + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
> otherwise.
> + */
> +static inline bool
> +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
> +{
> +       return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
> +}
> +
>  /**
>   * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>   * @gpuvm__: the &drm_gpuvm
> @@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device
> *drm);
>   */
>  #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>  
> +#define drm_gpuvm_resv_held(gpuvm__) \
> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> +
> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> +
>  #define drm_gpuvm_resv_held(gpuvm__) \
>         dma_resv_held(drm_gpuvm_resv(gpuvm__))
>  
> @@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> rb.entry)
>  
> +/**
> + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
> + * &drm_gem_object combination
> + *
> + * This structure is an abstraction representing a &drm_gpuvm and
> + * &drm_gem_object combination. It serves as an indirection to
> accelerate
> + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
> + * &drm_gem_object.
> + *
> + * Furthermore it is used cache evicted GEM objects for a certain
> GPU-VM to
> + * accelerate validation.
> + *
> + * Typically, drivers want to create an instance of a struct
> drm_gpuvm_bo once
> + * a GEM object is mapped first in a GPU-VM and release the instance
> once the
> + * last mapping of the GEM object in this GPU-VM is unmapped.
> + */
> +struct drm_gpuvm_bo {
> +       /**
> +        * @vm: The &drm_gpuvm the @obj is mapped in. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gpuvm *vm;
> +
> +       /**
> +        * @obj: The &drm_gem_object being mapped in @vm. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gem_object *obj;
> +
> +       /**
> +        * @kref: The reference count for this &drm_gpuvm_bo.
> +        */
> +       struct kref kref;
> +
> +       /**
> +        * @list: Structure containing all &list_heads.
> +        */
> +       struct {
> +               /**
> +                * @gpuva: The list of linked &drm_gpuvas.
> +                *
> +                * It is safe to access entries from this list as
> long as the
> +                * GEM's gpuva lock is held. See also struct
> drm_gem_object.
> +                */
> +               struct list_head gpuva;
> +
> +               /**
> +                * @entry: Structure containing all &list_heads
> serving as
> +                * entry.
> +                */
> +               struct {
> +                       /**
> +                        * @gem: List entry to attach to the
> &drm_gem_objects
> +                        * gpuva list.
> +                        */
> +                       struct list_head gem;
> +               } entry;
> +       } list;
> +};
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
> +
> +/**
> + * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
> + *
> + * This function acquires an additional reference to @vm_bo. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm_bo *
> +drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
> +{
> +       kref_get(&vm_bo->kref);
> +       return vm_bo;
> +}
> +
> +void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj);
> +
> +/**
> + * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
> +       list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
> +
> +/**
> + * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @next__: &next &drm_gpuva to store the next step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(),
> hence
> + * it is save against removal of elements.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
> +       list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva,
> gem.entry)
> +
>  /**
>   * enum drm_gpuva_op_type - GPU VA operation type
>   *
> @@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>                                  u64 addr, u64 range);
>  
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj);
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
>  
>  void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
>                         struct drm_gpuva_ops *ops);
> @@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
>          */
>         void (*op_free)(struct drm_gpuva_op *op);
>  
> +       /**
> +        * @vm_bo_alloc: called when the &drm_gpuvm allocates
> +        * a struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * allocate memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
> +
> +       /**
> +        * @vm_bo_free: called when the &drm_gpuvm frees a
> +        * struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * free the previously allocated memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
> +
>         /**
>          * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> insert the
>          * mapping once all previous steps were completed


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-02 13:25     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:25 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.
> + */
> +void
> +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
> +{
> +       if (vm_bo)
> +               kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
> +
> +static struct drm_gpuvm_bo *
> +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> +               if (vm_bo->vm == gpuvm)
> +                       return vm_bo;
> +
> +       return NULL;
> +}
> +
> +/**
> + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
> + * &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
> +
> +       return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
> +
> +/**
> + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
> for the
> + * given &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
> new
> + * &drm_gpuvm_bo.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo)
> +               return vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> +       if (!vm_bo)
> +               return ERR_PTR(-ENOMEM);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
> +
> +/**
> + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
> &drm_gpuvm_bo
> + * for the given &drm_gpuvm and &drm_gem_object
> + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
> reference
> + * count is decreased. If not found @__vm_bo is returned without
> further
> + * increase of the reference count.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
> existing
> + * &drm_gpuvm_bo was found
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> +{
> +       struct drm_gpuvm *gpuvm = __vm_bo->vm;
> +       struct drm_gem_object *obj = __vm_bo->obj;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo) {
> +               drm_gpuvm_bo_put(__vm_bo);
> +               return vm_bo;
> +       }
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return __vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> +
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>                    struct drm_gpuva *va)
> @@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  /**
>   * drm_gpuva_link() - link a &drm_gpuva
>   * @va: the &drm_gpuva to link
> + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
>   *
> - * This adds the given &va to the GPU VA list of the &drm_gem_object
> it is
> - * associated with.
> + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
> and the
> + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
> + *
> + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
> additional
> + * reference of the latter is taken.
>   *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
> -drm_gpuva_link(struct drm_gpuva *va)
> +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
>         if (unlikely(!obj))
>                 return;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
>  
> -       list_add_tail(&va->gem.entry, &obj->gpuva.list);
> +       va->vm_bo = drm_gpuvm_bo_get(vm_bo);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_link);
>  
> @@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
>   * This removes the given &va from the GPU VA list of the
> &drm_gem_object it is
>   * associated with.
>   *
> + * This removes the given &va from the GPU VA list of the
> &drm_gpuvm_bo and
> + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
> in case
> + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
> + *
> + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
> reference of
> + * the latter is dropped.
> + *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
>  drm_gpuva_unlink(struct drm_gpuva *va)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm_bo *vm_bo = va->vm_bo;
>  
>         if (unlikely(!obj))
>                 return;
>  
>         drm_gem_gpuva_assert_lock_held(obj);
> -
>         list_del_init(&va->gem.entry);
> +
> +       va->vm_bo = NULL;
> +       drm_gpuvm_bo_put(vm_bo);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
>  
> @@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
>                 struct drm_gpuva *next,
>                 struct drm_gpuva_op_remap *op)
>  {
> -       struct drm_gpuva *curr = op->unmap->va;
> -       struct drm_gpuvm *gpuvm = curr->vm;
> +       struct drm_gpuva *va = op->unmap->va;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
> -       drm_gpuva_remove(curr);
> +       drm_gpuva_remove(va);
>  
>         if (op->prev) {
>                 drm_gpuva_init_from_op(prev, op->prev);
> @@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>  EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>  
>  /**
> - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> - * @gpuvm: the &drm_gpuvm representing the GPU VA space
> - * @obj: the &drm_gem_object to unmap
> + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> + * @vm_bo: the &drm_gpuvm_bo abstraction
>   *
>   * This function creates a list of operations to perform unmapping
> for every
>   * GPUVA attached to a GEM.
> @@ -1754,15 +1997,14 @@
> EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>   * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
> on failure
>   */
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj)
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gpuva_ops *ops;
>         struct drm_gpuva_op *op;
>         struct drm_gpuva *va;
>         int ret;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_gpuva_assert_lock_held(vm_bo->obj);
>  
>         ops = kzalloc(sizeof(*ops), GFP_KERNEL);
>         if (!ops)
> @@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
> *gpuvm,
>  
>         INIT_LIST_HEAD(&ops->list);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               op = gpuva_op_alloc(gpuvm);
> +       drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +               op = gpuva_op_alloc(vm_bo->vm);
>                 if (!op) {
>                         ret = -ENOMEM;
>                         goto err_free_ops;
> @@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
> drm_gpuvm *gpuvm,
>         return ops;
>  
>  err_free_ops:
> -       drm_gpuva_ops_free(gpuvm, ops);
> +       drm_gpuva_ops_free(vm_bo->vm, ops);
>         return ERR_PTR(ret);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
>  
>  /**
>   * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index cb2f06565c46..eda7bb8624f1 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -62,6 +62,8 @@ struct bind_job_op {
>         enum vm_bind_op op;
>         u32 flags;
>  
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         struct {
>                 u64 addr;
>                 u64 range;
> @@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
>  }
>  
>  static void
> -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
> nouveau_uvma_prealloc *new)
> +bind_link_gpuvas(struct bind_job_op *bop)
>  {
> +       struct nouveau_uvma_prealloc *new = &bop->new;
> +       struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
> +       struct drm_gpuva_ops *ops = bop->ops;
>         struct drm_gpuva_op *op;
>  
>         drm_gpuva_for_each_op(op, ops) {
>                 switch (op->op) {
>                 case DRM_GPUVA_OP_MAP:
> -                       drm_gpuva_link(&new->map->va);
> +                       drm_gpuva_link(&new->map->va, vm_bo);
>                         break;
> -               case DRM_GPUVA_OP_REMAP:
> +               case DRM_GPUVA_OP_REMAP: {
> +                       struct drm_gpuva *va = op->remap.unmap->va;
> +
>                         if (op->remap.prev)
> -                               drm_gpuva_link(&new->prev->va);
> +                               drm_gpuva_link(&new->prev->va, va-
> >vm_bo);
>                         if (op->remap.next)
> -                               drm_gpuva_link(&new->next->va);
> -                       drm_gpuva_unlink(op->remap.unmap->va);
> +                               drm_gpuva_link(&new->next->va, va-
> >vm_bo);
> +                       drm_gpuva_unlink(va);
>                         break;
> +               }
>                 case DRM_GPUVA_OP_UNMAP:
>                         drm_gpuva_unlink(op->unmap.va);
>                         break;
> @@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 if (op->op == OP_MAP) {
> -                       op->gem.obj = drm_gem_object_lookup(job-
> >file_priv,
> -                                                           op-
> >gem.handle);
> -                       if (!op->gem.obj)
> +                       struct drm_gem_object *obj = op->gem.obj =
> +                               drm_gem_object_lookup(job->file_priv,
> +                                                     op-
> >gem.handle);
> +                       if (!obj)
>                                 return -ENOENT;
> +
> +                       dma_resv_lock(obj->resv, NULL);
> +                       op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
> obj);
> +                       dma_resv_unlock(obj->resv);
> +                       if (IS_ERR(op->vm_bo))
> +                               return PTR_ERR(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                 case OP_UNMAP_SPARSE:
>                 case OP_MAP:
>                 case OP_UNMAP:
> -                       bind_link_gpuvas(op->ops, &op->new);
> +                       bind_link_gpuvas(op);
>                         break;
>                 default:
>                         break;
> @@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
> work_struct *work)
>                 if (!IS_ERR_OR_NULL(op->ops))
>                         drm_gpuva_ops_free(&uvmm->base, op->ops);
>  
> +               if (!IS_ERR_OR_NULL(op->vm_bo)) {
> +                       dma_resv_lock(obj->resv, NULL);
> +                       drm_gpuvm_bo_put(op->vm_bo);
> +                       dma_resv_unlock(obj->resv);
> +               }
> +
>                 if (obj)
>                         drm_gem_object_put(obj);
>         }
> @@ -1752,15 +1773,18 @@ void
>  nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
> *mem)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_map(uvma, mem);
> -               drm_gpuva_invalidate(va, false);
> +                       nouveau_uvma_map(uvma, mem);
> +                       drm_gpuva_invalidate(va, false);
> +               }
>         }
>  }
>  
> @@ -1768,15 +1792,18 @@ void
>  nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_unmap(uvma);
> -               drm_gpuva_invalidate(va, true);
> +                       nouveau_uvma_unmap(uvma);
> +                       drm_gpuva_invalidate(va, true);
> +               }
>         }
>  }
>  
> diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
> index 16364487fde9..369505447acd 100644
> --- a/include/drm/drm_gem.h
> +++ b/include/drm/drm_gem.h
> @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
>   * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
>   * @obj: the &drm_gem_object
>   *
> - * This initializes the &drm_gem_object's &drm_gpuva list.
> + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
>   *
>   * Calling this function is only necessary for drivers intending to
> support the
>   * &drm_driver_feature DRIVER_GEM_GPUVA.
> @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
> drm_gem_object *obj)
>  }
>  
>  /**
> - * drm_gem_for_each_gpuva() - iternator to walk over a list of
> gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
> &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
> step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> - * &drm_gpuva_manager.
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
> + * &drm_gem_object.
>   */
> -#define drm_gem_for_each_gpuva(entry__, obj__) \
> -       list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
> +       list_for_each_entry(entry__, &(obj__)->gpuva.list,
> list.entry.gem)
>  
>  /**
> - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
> list of
> - * gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @next__: &next &drm_gpuva to store the next step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
> step
> + * @next__: &next &drm_gpuvm_bo to store the next step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
>   * &drm_gem_object. It is implemented with
> list_for_each_entry_safe(), hence
>   * it is save against removal of elements.
>   */
> -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
> -       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
> +       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, list.entry.gem)
>  
>  #endif /* __DRM_GEM_H__ */
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 4e6e1fd3485a..b12fb22b0e22 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -25,6 +25,7 @@
>   * OTHER DEALINGS IN THE SOFTWARE.
>   */
>  
> +#include <linux/dma-resv.h>
>  #include <linux/list.h>
>  #include <linux/rbtree.h>
>  #include <linux/types.h>
> @@ -33,6 +34,7 @@
>  #include <drm/drm_gem.h>
>  
>  struct drm_gpuvm;
> +struct drm_gpuvm_bo;
>  struct drm_gpuvm_ops;
>  
>  /**
> @@ -73,6 +75,12 @@ struct drm_gpuva {
>          */
>         struct drm_gpuvm *vm;
>  
> +       /**
> +        * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
> +        * &drm_gem_object
> +        */
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         /**
>          * @flags: the &drm_gpuva_flags for this mapping
>          */
> @@ -108,7 +116,7 @@ struct drm_gpuva {
>                 struct drm_gem_object *obj;
>  
>                 /**
> -                * @entry: the &list_head to attach this object to a
> &drm_gem_object
> +                * @entry: the &list_head to attach this object to a
> &drm_gpuvm_bo
>                  */
>                 struct list_head entry;
>         } gem;
> @@ -141,7 +149,7 @@ struct drm_gpuva {
>  int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
>  void drm_gpuva_remove(struct drm_gpuva *va);
>  
> -void drm_gpuva_link(struct drm_gpuva *va);
> +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
> *vm_bo);
>  void drm_gpuva_unlink(struct drm_gpuva *va);
>  
>  struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
> @@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct
> drm_gpuva *va)
>   * enum drm_gpuvm_flags - flags for struct drm_gpuvm
>   */
>  enum drm_gpuvm_flags {
> +       /**
> +        * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
> by the
> +        * GPUVM's &dma_resv lock
> +        */
> +       DRM_GPUVM_RESV_PROTECTED = BIT(0),
> +
>         /**
>          * @DRM_GPUVM_USERBITS: user defined bits
>          */
> -       DRM_GPUVM_USERBITS = BIT(0),
> +       DRM_GPUVM_USERBITS = BIT(1),
>  };
>  
>  /**
> @@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range);
>  struct drm_gem_object *
>  drm_gpuvm_resv_object_alloc(struct drm_device *drm);
>  
> +/**
> + * drm_gpuvm_resv_protected() - indicates whether
> &DRM_GPUVM_RESV_PROTECTED is
> + * set
> + * @gpuvm: the &drm_gpuvm
> + *
> + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
> otherwise.
> + */
> +static inline bool
> +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
> +{
> +       return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
> +}
> +
>  /**
>   * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>   * @gpuvm__: the &drm_gpuvm
> @@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device
> *drm);
>   */
>  #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>  
> +#define drm_gpuvm_resv_held(gpuvm__) \
> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> +
> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> +
>  #define drm_gpuvm_resv_held(gpuvm__) \
>         dma_resv_held(drm_gpuvm_resv(gpuvm__))
>  
> @@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> rb.entry)
>  
> +/**
> + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
> + * &drm_gem_object combination
> + *
> + * This structure is an abstraction representing a &drm_gpuvm and
> + * &drm_gem_object combination. It serves as an indirection to
> accelerate
> + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
> + * &drm_gem_object.
> + *
> + * Furthermore it is used cache evicted GEM objects for a certain
> GPU-VM to
> + * accelerate validation.
> + *
> + * Typically, drivers want to create an instance of a struct
> drm_gpuvm_bo once
> + * a GEM object is mapped first in a GPU-VM and release the instance
> once the
> + * last mapping of the GEM object in this GPU-VM is unmapped.
> + */
> +struct drm_gpuvm_bo {
> +       /**
> +        * @vm: The &drm_gpuvm the @obj is mapped in. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gpuvm *vm;
> +
> +       /**
> +        * @obj: The &drm_gem_object being mapped in @vm. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gem_object *obj;
> +
> +       /**
> +        * @kref: The reference count for this &drm_gpuvm_bo.
> +        */
> +       struct kref kref;
> +
> +       /**
> +        * @list: Structure containing all &list_heads.
> +        */
> +       struct {
> +               /**
> +                * @gpuva: The list of linked &drm_gpuvas.
> +                *
> +                * It is safe to access entries from this list as
> long as the
> +                * GEM's gpuva lock is held. See also struct
> drm_gem_object.
> +                */
> +               struct list_head gpuva;
> +
> +               /**
> +                * @entry: Structure containing all &list_heads
> serving as
> +                * entry.
> +                */
> +               struct {
> +                       /**
> +                        * @gem: List entry to attach to the
> &drm_gem_objects
> +                        * gpuva list.
> +                        */
> +                       struct list_head gem;
> +               } entry;
> +       } list;
> +};
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
> +
> +/**
> + * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
> + *
> + * This function acquires an additional reference to @vm_bo. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm_bo *
> +drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
> +{
> +       kref_get(&vm_bo->kref);
> +       return vm_bo;
> +}
> +
> +void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj);
> +
> +/**
> + * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
> +       list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
> +
> +/**
> + * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @next__: &next &drm_gpuva to store the next step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(),
> hence
> + * it is save against removal of elements.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
> +       list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva,
> gem.entry)
> +
>  /**
>   * enum drm_gpuva_op_type - GPU VA operation type
>   *
> @@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>                                  u64 addr, u64 range);
>  
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj);
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
>  
>  void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
>                         struct drm_gpuva_ops *ops);
> @@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
>          */
>         void (*op_free)(struct drm_gpuva_op *op);
>  
> +       /**
> +        * @vm_bo_alloc: called when the &drm_gpuvm allocates
> +        * a struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * allocate memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
> +
> +       /**
> +        * @vm_bo_free: called when the &drm_gpuvm frees a
> +        * struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * free the previously allocated memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
> +
>         /**
>          * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> insert the
>          * mapping once all previous steps were completed


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-02 13:25     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 13:25 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>

> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.
> + */
> +void
> +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
> +{
> +       if (vm_bo)
> +               kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
> +
> +static struct drm_gpuvm_bo *
> +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> +               if (vm_bo->vm == gpuvm)
> +                       return vm_bo;
> +
> +       return NULL;
> +}
> +
> +/**
> + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
> + * &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
> +
> +       return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
> +
> +/**
> + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
> for the
> + * given &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
> new
> + * &drm_gpuvm_bo.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo)
> +               return vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> +       if (!vm_bo)
> +               return ERR_PTR(-ENOMEM);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
> +
> +/**
> + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
> &drm_gpuvm_bo
> + * for the given &drm_gpuvm and &drm_gem_object
> + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
> reference
> + * count is decreased. If not found @__vm_bo is returned without
> further
> + * increase of the reference count.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
> existing
> + * &drm_gpuvm_bo was found
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> +{
> +       struct drm_gpuvm *gpuvm = __vm_bo->vm;
> +       struct drm_gem_object *obj = __vm_bo->obj;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo) {
> +               drm_gpuvm_bo_put(__vm_bo);
> +               return vm_bo;
> +       }
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
> +
> +       return __vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> +
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>                    struct drm_gpuva *va)
> @@ -904,24 +1128,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  /**
>   * drm_gpuva_link() - link a &drm_gpuva
>   * @va: the &drm_gpuva to link
> + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
>   *
> - * This adds the given &va to the GPU VA list of the &drm_gem_object
> it is
> - * associated with.
> + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
> and the
> + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
> + *
> + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
> additional
> + * reference of the latter is taken.
>   *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
> -drm_gpuva_link(struct drm_gpuva *va)
> +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
>         if (unlikely(!obj))
>                 return;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_WARN_ON(gpuvm->drm, obj != vm_bo->obj);
>  
> -       list_add_tail(&va->gem.entry, &obj->gpuva.list);
> +       va->vm_bo = drm_gpuvm_bo_get(vm_bo);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_link);
>  
> @@ -932,20 +1165,31 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
>   * This removes the given &va from the GPU VA list of the
> &drm_gem_object it is
>   * associated with.
>   *
> + * This removes the given &va from the GPU VA list of the
> &drm_gpuvm_bo and
> + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
> in case
> + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
> + *
> + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
> reference of
> + * the latter is dropped.
> + *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
>  drm_gpuva_unlink(struct drm_gpuva *va)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm_bo *vm_bo = va->vm_bo;
>  
>         if (unlikely(!obj))
>                 return;
>  
>         drm_gem_gpuva_assert_lock_held(obj);
> -
>         list_del_init(&va->gem.entry);
> +
> +       va->vm_bo = NULL;
> +       drm_gpuvm_bo_put(vm_bo);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
>  
> @@ -1090,10 +1334,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
>                 struct drm_gpuva *next,
>                 struct drm_gpuva_op_remap *op)
>  {
> -       struct drm_gpuva *curr = op->unmap->va;
> -       struct drm_gpuvm *gpuvm = curr->vm;
> +       struct drm_gpuva *va = op->unmap->va;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
> -       drm_gpuva_remove(curr);
> +       drm_gpuva_remove(va);
>  
>         if (op->prev) {
>                 drm_gpuva_init_from_op(prev, op->prev);
> @@ -1735,9 +1979,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>  EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>  
>  /**
> - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> - * @gpuvm: the &drm_gpuvm representing the GPU VA space
> - * @obj: the &drm_gem_object to unmap
> + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> + * @vm_bo: the &drm_gpuvm_bo abstraction
>   *
>   * This function creates a list of operations to perform unmapping
> for every
>   * GPUVA attached to a GEM.
> @@ -1754,15 +1997,14 @@
> EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>   * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
> on failure
>   */
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj)
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gpuva_ops *ops;
>         struct drm_gpuva_op *op;
>         struct drm_gpuva *va;
>         int ret;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_gpuva_assert_lock_held(vm_bo->obj);
>  
>         ops = kzalloc(sizeof(*ops), GFP_KERNEL);
>         if (!ops)
> @@ -1770,8 +2012,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
> *gpuvm,
>  
>         INIT_LIST_HEAD(&ops->list);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               op = gpuva_op_alloc(gpuvm);
> +       drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +               op = gpuva_op_alloc(vm_bo->vm);
>                 if (!op) {
>                         ret = -ENOMEM;
>                         goto err_free_ops;
> @@ -1785,10 +2027,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
> drm_gpuvm *gpuvm,
>         return ops;
>  
>  err_free_ops:
> -       drm_gpuva_ops_free(gpuvm, ops);
> +       drm_gpuva_ops_free(vm_bo->vm, ops);
>         return ERR_PTR(ret);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
>  
>  /**
>   * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index cb2f06565c46..eda7bb8624f1 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -62,6 +62,8 @@ struct bind_job_op {
>         enum vm_bind_op op;
>         u32 flags;
>  
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         struct {
>                 u64 addr;
>                 u64 range;
> @@ -1101,22 +1103,28 @@ bind_validate_region(struct nouveau_job *job)
>  }
>  
>  static void
> -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
> nouveau_uvma_prealloc *new)
> +bind_link_gpuvas(struct bind_job_op *bop)
>  {
> +       struct nouveau_uvma_prealloc *new = &bop->new;
> +       struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
> +       struct drm_gpuva_ops *ops = bop->ops;
>         struct drm_gpuva_op *op;
>  
>         drm_gpuva_for_each_op(op, ops) {
>                 switch (op->op) {
>                 case DRM_GPUVA_OP_MAP:
> -                       drm_gpuva_link(&new->map->va);
> +                       drm_gpuva_link(&new->map->va, vm_bo);
>                         break;
> -               case DRM_GPUVA_OP_REMAP:
> +               case DRM_GPUVA_OP_REMAP: {
> +                       struct drm_gpuva *va = op->remap.unmap->va;
> +
>                         if (op->remap.prev)
> -                               drm_gpuva_link(&new->prev->va);
> +                               drm_gpuva_link(&new->prev->va, va-
> >vm_bo);
>                         if (op->remap.next)
> -                               drm_gpuva_link(&new->next->va);
> -                       drm_gpuva_unlink(op->remap.unmap->va);
> +                               drm_gpuva_link(&new->next->va, va-
> >vm_bo);
> +                       drm_gpuva_unlink(va);
>                         break;
> +               }
>                 case DRM_GPUVA_OP_UNMAP:
>                         drm_gpuva_unlink(op->unmap.va);
>                         break;
> @@ -1138,10 +1146,17 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 if (op->op == OP_MAP) {
> -                       op->gem.obj = drm_gem_object_lookup(job-
> >file_priv,
> -                                                           op-
> >gem.handle);
> -                       if (!op->gem.obj)
> +                       struct drm_gem_object *obj = op->gem.obj =
> +                               drm_gem_object_lookup(job->file_priv,
> +                                                     op-
> >gem.handle);
> +                       if (!obj)
>                                 return -ENOENT;
> +
> +                       dma_resv_lock(obj->resv, NULL);
> +                       op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
> obj);
> +                       dma_resv_unlock(obj->resv);
> +                       if (IS_ERR(op->vm_bo))
> +                               return PTR_ERR(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1352,7 +1367,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                 case OP_UNMAP_SPARSE:
>                 case OP_MAP:
>                 case OP_UNMAP:
> -                       bind_link_gpuvas(op->ops, &op->new);
> +                       bind_link_gpuvas(op);
>                         break;
>                 default:
>                         break;
> @@ -1499,6 +1514,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
> work_struct *work)
>                 if (!IS_ERR_OR_NULL(op->ops))
>                         drm_gpuva_ops_free(&uvmm->base, op->ops);
>  
> +               if (!IS_ERR_OR_NULL(op->vm_bo)) {
> +                       dma_resv_lock(obj->resv, NULL);
> +                       drm_gpuvm_bo_put(op->vm_bo);
> +                       dma_resv_unlock(obj->resv);
> +               }
> +
>                 if (obj)
>                         drm_gem_object_put(obj);
>         }
> @@ -1752,15 +1773,18 @@ void
>  nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
> *mem)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_map(uvma, mem);
> -               drm_gpuva_invalidate(va, false);
> +                       nouveau_uvma_map(uvma, mem);
> +                       drm_gpuva_invalidate(va, false);
> +               }
>         }
>  }
>  
> @@ -1768,15 +1792,18 @@ void
>  nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_unmap(uvma);
> -               drm_gpuva_invalidate(va, true);
> +                       nouveau_uvma_unmap(uvma);
> +                       drm_gpuva_invalidate(va, true);
> +               }
>         }
>  }
>  
> diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
> index 16364487fde9..369505447acd 100644
> --- a/include/drm/drm_gem.h
> +++ b/include/drm/drm_gem.h
> @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
>   * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
>   * @obj: the &drm_gem_object
>   *
> - * This initializes the &drm_gem_object's &drm_gpuva list.
> + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
>   *
>   * Calling this function is only necessary for drivers intending to
> support the
>   * &drm_driver_feature DRIVER_GEM_GPUVA.
> @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
> drm_gem_object *obj)
>  }
>  
>  /**
> - * drm_gem_for_each_gpuva() - iternator to walk over a list of
> gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
> &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
> step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> - * &drm_gpuva_manager.
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
> + * &drm_gem_object.
>   */
> -#define drm_gem_for_each_gpuva(entry__, obj__) \
> -       list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
> +       list_for_each_entry(entry__, &(obj__)->gpuva.list,
> list.entry.gem)
>  
>  /**
> - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
> list of
> - * gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @next__: &next &drm_gpuva to store the next step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
> step
> + * @next__: &next &drm_gpuvm_bo to store the next step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
>   * &drm_gem_object. It is implemented with
> list_for_each_entry_safe(), hence
>   * it is save against removal of elements.
>   */
> -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
> -       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
> +       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, list.entry.gem)
>  
>  #endif /* __DRM_GEM_H__ */
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 4e6e1fd3485a..b12fb22b0e22 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -25,6 +25,7 @@
>   * OTHER DEALINGS IN THE SOFTWARE.
>   */
>  
> +#include <linux/dma-resv.h>
>  #include <linux/list.h>
>  #include <linux/rbtree.h>
>  #include <linux/types.h>
> @@ -33,6 +34,7 @@
>  #include <drm/drm_gem.h>
>  
>  struct drm_gpuvm;
> +struct drm_gpuvm_bo;
>  struct drm_gpuvm_ops;
>  
>  /**
> @@ -73,6 +75,12 @@ struct drm_gpuva {
>          */
>         struct drm_gpuvm *vm;
>  
> +       /**
> +        * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
> +        * &drm_gem_object
> +        */
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         /**
>          * @flags: the &drm_gpuva_flags for this mapping
>          */
> @@ -108,7 +116,7 @@ struct drm_gpuva {
>                 struct drm_gem_object *obj;
>  
>                 /**
> -                * @entry: the &list_head to attach this object to a
> &drm_gem_object
> +                * @entry: the &list_head to attach this object to a
> &drm_gpuvm_bo
>                  */
>                 struct list_head entry;
>         } gem;
> @@ -141,7 +149,7 @@ struct drm_gpuva {
>  int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
>  void drm_gpuva_remove(struct drm_gpuva *va);
>  
> -void drm_gpuva_link(struct drm_gpuva *va);
> +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
> *vm_bo);
>  void drm_gpuva_unlink(struct drm_gpuva *va);
>  
>  struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
> @@ -188,10 +196,16 @@ static inline bool drm_gpuva_invalidated(struct
> drm_gpuva *va)
>   * enum drm_gpuvm_flags - flags for struct drm_gpuvm
>   */
>  enum drm_gpuvm_flags {
> +       /**
> +        * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
> by the
> +        * GPUVM's &dma_resv lock
> +        */
> +       DRM_GPUVM_RESV_PROTECTED = BIT(0),
> +
>         /**
>          * @DRM_GPUVM_USERBITS: user defined bits
>          */
> -       DRM_GPUVM_USERBITS = BIT(0),
> +       DRM_GPUVM_USERBITS = BIT(1),
>  };
>  
>  /**
> @@ -302,6 +316,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range);
>  struct drm_gem_object *
>  drm_gpuvm_resv_object_alloc(struct drm_device *drm);
>  
> +/**
> + * drm_gpuvm_resv_protected() - indicates whether
> &DRM_GPUVM_RESV_PROTECTED is
> + * set
> + * @gpuvm: the &drm_gpuvm
> + *
> + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
> otherwise.
> + */
> +static inline bool
> +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
> +{
> +       return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
> +}
> +
>  /**
>   * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>   * @gpuvm__: the &drm_gpuvm
> @@ -320,6 +347,12 @@ drm_gpuvm_resv_object_alloc(struct drm_device
> *drm);
>   */
>  #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>  
> +#define drm_gpuvm_resv_held(gpuvm__) \
> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> +
> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> +
>  #define drm_gpuvm_resv_held(gpuvm__) \
>         dma_resv_held(drm_gpuvm_resv(gpuvm__))
>  
> @@ -404,6 +437,125 @@ __drm_gpuva_next(struct drm_gpuva *va)
>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> rb.entry)
>  
> +/**
> + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
> + * &drm_gem_object combination
> + *
> + * This structure is an abstraction representing a &drm_gpuvm and
> + * &drm_gem_object combination. It serves as an indirection to
> accelerate
> + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
> + * &drm_gem_object.
> + *
> + * Furthermore it is used cache evicted GEM objects for a certain
> GPU-VM to
> + * accelerate validation.
> + *
> + * Typically, drivers want to create an instance of a struct
> drm_gpuvm_bo once
> + * a GEM object is mapped first in a GPU-VM and release the instance
> once the
> + * last mapping of the GEM object in this GPU-VM is unmapped.
> + */
> +struct drm_gpuvm_bo {
> +       /**
> +        * @vm: The &drm_gpuvm the @obj is mapped in. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gpuvm *vm;
> +
> +       /**
> +        * @obj: The &drm_gem_object being mapped in @vm. This is a
> reference
> +        * counted pointer.
> +        */
> +       struct drm_gem_object *obj;
> +
> +       /**
> +        * @kref: The reference count for this &drm_gpuvm_bo.
> +        */
> +       struct kref kref;
> +
> +       /**
> +        * @list: Structure containing all &list_heads.
> +        */
> +       struct {
> +               /**
> +                * @gpuva: The list of linked &drm_gpuvas.
> +                *
> +                * It is safe to access entries from this list as
> long as the
> +                * GEM's gpuva lock is held. See also struct
> drm_gem_object.
> +                */
> +               struct list_head gpuva;
> +
> +               /**
> +                * @entry: Structure containing all &list_heads
> serving as
> +                * entry.
> +                */
> +               struct {
> +                       /**
> +                        * @gem: List entry to attach to the
> &drm_gem_objects
> +                        * gpuva list.
> +                        */
> +                       struct list_head gem;
> +               } entry;
> +       } list;
> +};
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
> +
> +/**
> + * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
> + *
> + * This function acquires an additional reference to @vm_bo. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm_bo *
> +drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
> +{
> +       kref_get(&vm_bo->kref);
> +       return vm_bo;
> +}
> +
> +void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj);
> +
> +/**
> + * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
> +       list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
> +
> +/**
> + * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @next__: &next &drm_gpuva to store the next step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(),
> hence
> + * it is save against removal of elements.
> + *
> + * The caller must hold the GEM's gpuva lock.
> + */
> +#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
> +       list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva,
> gem.entry)
> +
>  /**
>   * enum drm_gpuva_op_type - GPU VA operation type
>   *
> @@ -673,8 +825,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>                                  u64 addr, u64 range);
>  
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj);
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
>  
>  void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
>                         struct drm_gpuva_ops *ops);
> @@ -726,6 +877,30 @@ struct drm_gpuvm_ops {
>          */
>         void (*op_free)(struct drm_gpuva_op *op);
>  
> +       /**
> +        * @vm_bo_alloc: called when the &drm_gpuvm allocates
> +        * a struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * allocate memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
> +
> +       /**
> +        * @vm_bo_free: called when the &drm_gpuvm frees a
> +        * struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * free the previously allocated memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
> +
>         /**
>          * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> insert the
>          * mapping once all previous steps were completed


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 17:09     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:09 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
copy-paste error in function name.

Also it appears like xe might put a vm from irq context so we should
document the context where this function call is allowable, and if
applicable add a might_sleep().

If this function needs to sleep we can work around that in Xe by
keeping an xe-private refcount for the xe vm container, but I'd like to
avoid that if possible and piggy-back on the refcount introduced here.

> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);

Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
reference should be taken where the pointer holding the reference is
assigned (in this case in __drm_gpuva_insert()), or document the
reference transfer from the argument close to the assignment.

But since a va itself is not refcounted it clearly can't outlive the
vm, so is a reference really needed here?

I'd suggest using an accessor that instead of using va->vm uses va-
>vm_bo->vm, to avoid needing to worry about the vm->vm refcount
altoghether.

Thanks,
Thomas


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 17:09     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:09 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
copy-paste error in function name.

Also it appears like xe might put a vm from irq context so we should
document the context where this function call is allowable, and if
applicable add a might_sleep().

If this function needs to sleep we can work around that in Xe by
keeping an xe-private refcount for the xe vm container, but I'd like to
avoid that if possible and piggy-back on the refcount introduced here.

> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);

Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
reference should be taken where the pointer holding the reference is
assigned (in this case in __drm_gpuva_insert()), or document the
reference transfer from the argument close to the assignment.

But since a va itself is not refcounted it clearly can't outlive the
vm, so is a reference really needed here?

I'd suggest using an accessor that instead of using va->vm uses va-
>vm_bo->vm, to avoid needing to worry about the vm->vm refcount
altoghether.

Thanks,
Thomas


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 17:09     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:09 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Implement reference counting for struct drm_gpuvm.
> 
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>  include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>  3 files changed, 78 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       kref_init(&gpuvm->kref);
> +
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->flags = flags;
>         gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> char *name,
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>  
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that
> still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>  {
>         gpuvm->name = NULL;
>  
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  
>         drm_gem_object_put(gpuvm->r_obj);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> drm_gpuvm, kref);
> +
> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +               return;
> +
> +       drm_gpuvm_fini(gpuvm);
> +
> +       gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
copy-paste error in function name.

Also it appears like xe might put a vm from irq context so we should
document the context where this function call is allowable, and if
applicable add a might_sleep().

If this function needs to sleep we can work around that in Xe by
keeping an xe-private refcount for the xe vm container, but I'd like to
avoid that if possible and piggy-back on the refcount introduced here.

> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +       if (gpuvm)
> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>                 return -EINVAL;
>  
> -       return __drm_gpuva_insert(gpuvm, va);
> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);

Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
reference should be taken where the pointer holding the reference is
assigned (in this case in __drm_gpuva_insert()), or document the
reference transfer from the argument close to the assignment.

But since a va itself is not refcounted it clearly can't outlive the
vm, so is a reference really needed here?

I'd suggest using an accessor that instead of using va->vm uses va-
>vm_bo->vm, to avoid needing to worry about the vm->vm refcount
altoghether.

Thanks,
Thomas


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-02 17:16     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:16 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.

Should Ideally document the context for this function as well, to avoid
future pitfalls and arguments, and also potentially a might_sleep().

Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-02 17:16     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:16 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.

Should Ideally document the context for this function as well, to avoid
future pitfalls and arguments, and also potentially a might_sleep().

Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination
@ 2023-11-02 17:16     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 17:16 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
> 
> This has multiple advantages:
> 
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
> 
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
> 
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
> 
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
> 
> Cc: Christian König <christian.koenig@amd.com>
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 336 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  63 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 185 +++++++++++++-
>  4 files changed, 530 insertions(+), 86 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6a88eafc5229..2c8fdefb19f0 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_resv_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -813,6 +848,195 @@ drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = drm_gpuvm_get(gpuvm);
> +       vm_bo->obj = obj;
> +       drm_gem_object_get(obj);
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +
> +       drm_gpuvm_put(gpuvm);
> +       drm_gem_object_put(obj);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.

Should Ideally document the context for this function as well, to avoid
future pitfalls and arguments, and also potentially a might_sleep().

Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-02 17:09     ` [Nouveau] " Thomas Hellström
  (?)
@ 2023-11-02 17:32       ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-02 17:32 UTC (permalink / raw)
  To: Thomas Hellström, airlied, daniel, matthew.brost,
	sarah.walker, donald.robson, boris.brezillon, christian.koenig,
	faith
  Cc: nouveau, linux-kernel, dri-devel

Hi Thomas,

thanks for your timely response on that!

On 11/2/23 18:09, Thomas Hellström wrote:
> On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
>> Implement reference counting for struct drm_gpuvm.
>>
>> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
>> ---
>>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
>> --
>>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>   3 files changed, 78 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>> b/drivers/gpu/drm/drm_gpuvm.c
>> index 53e2c406fb04..6a88eafc5229 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>          gpuvm->rb.tree = RB_ROOT_CACHED;
>>          INIT_LIST_HEAD(&gpuvm->rb.list);
>>   
>> +       kref_init(&gpuvm->kref);
>> +
>>          gpuvm->name = name ? name : "unknown";
>>          gpuvm->flags = flags;
>>          gpuvm->ops = ops;
>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>   }
>>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>   
>> -/**
>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>> - *
>> - * Note that it is a bug to call this function on a manager that
>> still
>> - * holds GPU VA mappings.
>> - */
>> -void
>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>> +static void
>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>   {
>>          gpuvm->name = NULL;
>>   
>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>   
>>          drm_gem_object_put(gpuvm->r_obj);
>>   }
>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>> +
>> +static void
>> +drm_gpuvm_free(struct kref *kref)
>> +{
>> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
>> drm_gpuvm, kref);
>> +
>> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +               return;
>> +
>> +       drm_gpuvm_fini(gpuvm);
>> +
>> +       gpuvm->ops->vm_free(gpuvm);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> copy-paste error in function name.
> 
> Also it appears like xe might put a vm from irq context so we should
> document the context where this function call is allowable, and if
> applicable add a might_sleep().

 From GPUVM PoV I don't see why we can't call this from an IRQ context.
It depends on the driver callbacks of GPUVM (->vm_free) and the resv GEM's
free callback. Both are controlled by the driver. Hence, I don't see the
need for a restriction here.

> 
> If this function needs to sleep we can work around that in Xe by
> keeping an xe-private refcount for the xe vm container, but I'd like to
> avoid that if possible and piggy-back on the refcount introduced here.
> 
>> + * @gpuvm: the &drm_gpuvm to release the reference of
>> + *
>> + * This releases a reference to @gpuvm.
>> + */
>> +void
>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>> +{
>> +       if (gpuvm)
>> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>   
>>   static int
>>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>                  return -EINVAL;
>>   
>> -       return __drm_gpuva_insert(gpuvm, va);
>> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> 
> Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> reference should be taken where the pointer holding the reference is
> assigned (in this case in __drm_gpuva_insert()), or document the
> reference transfer from the argument close to the assignment.

Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
doesn't work, because __drm_gpuva_insert() is used to insert the
kernel_alloc_node. And we need to __drm_gpuva_remove() the kernel_alloc_node
from drm_gpuvm_fini(), which is called when the reference count is at zero
already. In fact, the __* variants are only there to handle the
kernel_alloc_node and this one clearly doesn't need reference counting.

> 
> But since a va itself is not refcounted it clearly can't outlive the
> vm, so is a reference really needed here?

Well, technically, it can. It just doesn't make any sense and would be
considered to be a bug. The reference count comes in handy to prevent
that in the first place.

I'd like to keep the reference count and just fix up the code.

> 
> I'd suggest using an accessor that instead of using va->vm uses va-
>> vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> altoghether.

No, I want to keep that optional. Drivers should be able to use GPUVM to
track mappings without being required to implement everything else.

I think PowerVR, for instance, currently uses GPUVM only to track mappings
without everything else.

- Danilo

> 
> Thanks,
> Thomas
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 17:32       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-02 17:32 UTC (permalink / raw)
  To: Thomas Hellström, airlied, daniel, matthew.brost,
	sarah.walker, donald.robson, boris.brezillon, christian.koenig,
	faith
  Cc: nouveau, linux-kernel, dri-devel

Hi Thomas,

thanks for your timely response on that!

On 11/2/23 18:09, Thomas Hellström wrote:
> On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
>> Implement reference counting for struct drm_gpuvm.
>>
>> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
>> ---
>>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
>> --
>>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>   3 files changed, 78 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>> b/drivers/gpu/drm/drm_gpuvm.c
>> index 53e2c406fb04..6a88eafc5229 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>          gpuvm->rb.tree = RB_ROOT_CACHED;
>>          INIT_LIST_HEAD(&gpuvm->rb.list);
>>   
>> +       kref_init(&gpuvm->kref);
>> +
>>          gpuvm->name = name ? name : "unknown";
>>          gpuvm->flags = flags;
>>          gpuvm->ops = ops;
>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>   }
>>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>   
>> -/**
>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>> - *
>> - * Note that it is a bug to call this function on a manager that
>> still
>> - * holds GPU VA mappings.
>> - */
>> -void
>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>> +static void
>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>   {
>>          gpuvm->name = NULL;
>>   
>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>   
>>          drm_gem_object_put(gpuvm->r_obj);
>>   }
>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>> +
>> +static void
>> +drm_gpuvm_free(struct kref *kref)
>> +{
>> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
>> drm_gpuvm, kref);
>> +
>> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +               return;
>> +
>> +       drm_gpuvm_fini(gpuvm);
>> +
>> +       gpuvm->ops->vm_free(gpuvm);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> copy-paste error in function name.
> 
> Also it appears like xe might put a vm from irq context so we should
> document the context where this function call is allowable, and if
> applicable add a might_sleep().

 From GPUVM PoV I don't see why we can't call this from an IRQ context.
It depends on the driver callbacks of GPUVM (->vm_free) and the resv GEM's
free callback. Both are controlled by the driver. Hence, I don't see the
need for a restriction here.

> 
> If this function needs to sleep we can work around that in Xe by
> keeping an xe-private refcount for the xe vm container, but I'd like to
> avoid that if possible and piggy-back on the refcount introduced here.
> 
>> + * @gpuvm: the &drm_gpuvm to release the reference of
>> + *
>> + * This releases a reference to @gpuvm.
>> + */
>> +void
>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>> +{
>> +       if (gpuvm)
>> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>   
>>   static int
>>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>                  return -EINVAL;
>>   
>> -       return __drm_gpuva_insert(gpuvm, va);
>> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> 
> Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> reference should be taken where the pointer holding the reference is
> assigned (in this case in __drm_gpuva_insert()), or document the
> reference transfer from the argument close to the assignment.

Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
doesn't work, because __drm_gpuva_insert() is used to insert the
kernel_alloc_node. And we need to __drm_gpuva_remove() the kernel_alloc_node
from drm_gpuvm_fini(), which is called when the reference count is at zero
already. In fact, the __* variants are only there to handle the
kernel_alloc_node and this one clearly doesn't need reference counting.

> 
> But since a va itself is not refcounted it clearly can't outlive the
> vm, so is a reference really needed here?

Well, technically, it can. It just doesn't make any sense and would be
considered to be a bug. The reference count comes in handy to prevent
that in the first place.

I'd like to keep the reference count and just fix up the code.

> 
> I'd suggest using an accessor that instead of using va->vm uses va-
>> vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> altoghether.

No, I want to keep that optional. Drivers should be able to use GPUVM to
track mappings without being required to implement everything else.

I think PowerVR, for instance, currently uses GPUVM only to track mappings
without everything else.

- Danilo

> 
> Thanks,
> Thomas
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 17:32       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-02 17:32 UTC (permalink / raw)
  To: Thomas Hellström, airlied, daniel, matthew.brost,
	sarah.walker, donald.robson, boris.brezillon, christian.koenig,
	faith
  Cc: dri-devel, nouveau, linux-kernel

Hi Thomas,

thanks for your timely response on that!

On 11/2/23 18:09, Thomas Hellström wrote:
> On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
>> Implement reference counting for struct drm_gpuvm.
>>
>> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
>> ---
>>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-----
>> --
>>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>   3 files changed, 78 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>> b/drivers/gpu/drm/drm_gpuvm.c
>> index 53e2c406fb04..6a88eafc5229 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>          gpuvm->rb.tree = RB_ROOT_CACHED;
>>          INIT_LIST_HEAD(&gpuvm->rb.list);
>>   
>> +       kref_init(&gpuvm->kref);
>> +
>>          gpuvm->name = name ? name : "unknown";
>>          gpuvm->flags = flags;
>>          gpuvm->ops = ops;
>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
>> char *name,
>>   }
>>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>   
>> -/**
>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>> - *
>> - * Note that it is a bug to call this function on a manager that
>> still
>> - * holds GPU VA mappings.
>> - */
>> -void
>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>> +static void
>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>   {
>>          gpuvm->name = NULL;
>>   
>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>   
>>          drm_gem_object_put(gpuvm->r_obj);
>>   }
>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>> +
>> +static void
>> +drm_gpuvm_free(struct kref *kref)
>> +{
>> +       struct drm_gpuvm *gpuvm = container_of(kref, struct
>> drm_gpuvm, kref);
>> +
>> +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +               return;
>> +
>> +       drm_gpuvm_fini(gpuvm);
>> +
>> +       gpuvm->ops->vm_free(gpuvm);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> copy-paste error in function name.
> 
> Also it appears like xe might put a vm from irq context so we should
> document the context where this function call is allowable, and if
> applicable add a might_sleep().

 From GPUVM PoV I don't see why we can't call this from an IRQ context.
It depends on the driver callbacks of GPUVM (->vm_free) and the resv GEM's
free callback. Both are controlled by the driver. Hence, I don't see the
need for a restriction here.

> 
> If this function needs to sleep we can work around that in Xe by
> keeping an xe-private refcount for the xe vm container, but I'd like to
> avoid that if possible and piggy-back on the refcount introduced here.
> 
>> + * @gpuvm: the &drm_gpuvm to release the reference of
>> + *
>> + * This releases a reference to @gpuvm.
>> + */
>> +void
>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>> +{
>> +       if (gpuvm)
>> +               kref_put(&gpuvm->kref, drm_gpuvm_free);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>   
>>   static int
>>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>                  return -EINVAL;
>>   
>> -       return __drm_gpuva_insert(gpuvm, va);
>> +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> 
> Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> reference should be taken where the pointer holding the reference is
> assigned (in this case in __drm_gpuva_insert()), or document the
> reference transfer from the argument close to the assignment.

Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
doesn't work, because __drm_gpuva_insert() is used to insert the
kernel_alloc_node. And we need to __drm_gpuva_remove() the kernel_alloc_node
from drm_gpuvm_fini(), which is called when the reference count is at zero
already. In fact, the __* variants are only there to handle the
kernel_alloc_node and this one clearly doesn't need reference counting.

> 
> But since a va itself is not refcounted it clearly can't outlive the
> vm, so is a reference really needed here?

Well, technically, it can. It just doesn't make any sense and would be
considered to be a bug. The reference count comes in handy to prevent
that in the first place.

I'd like to keep the reference count and just fix up the code.

> 
> I'd suggest using an accessor that instead of using va->vm uses va-
>> vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> altoghether.

No, I want to keep that optional. Drivers should be able to use GPUVM to
track mappings without being required to implement everything else.

I think PowerVR, for instance, currently uses GPUVM only to track mappings
without everything else.

- Danilo

> 
> Thanks,
> Thomas
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-02 17:32       ` Danilo Krummrich
  (?)
@ 2023-11-02 18:04         ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 18:04 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 18:32 +0100, Danilo Krummrich wrote:
> Hi Thomas,
> 
> thanks for your timely response on that!
> 
> On 11/2/23 18:09, Thomas Hellström wrote:
> > On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> > > Implement reference counting for struct drm_gpuvm.
> > > 
> > > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > > ---
> > >   drivers/gpu/drm/drm_gpuvm.c            | 44
> > > +++++++++++++++++++-----
> > > --
> > >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > >   3 files changed, 78 insertions(+), 17 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > b/drivers/gpu/drm/drm_gpuvm.c
> > > index 53e2c406fb04..6a88eafc5229 100644
> > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> > > char *name,
> > >          gpuvm->rb.tree = RB_ROOT_CACHED;
> > >          INIT_LIST_HEAD(&gpuvm->rb.list);
> > >   
> > > +       kref_init(&gpuvm->kref);
> > > +
> > >          gpuvm->name = name ? name : "unknown";
> > >          gpuvm->flags = flags;
> > >          gpuvm->ops = ops;
> > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > > const
> > > char *name,
> > >   }
> > >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > >   
> > > -/**
> > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > - *
> > > - * Note that it is a bug to call this function on a manager that
> > > still
> > > - * holds GPU VA mappings.
> > > - */
> > > -void
> > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > +static void
> > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > >   {
> > >          gpuvm->name = NULL;
> > >   
> > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > >   
> > >          drm_gem_object_put(gpuvm->r_obj);
> > >   }
> > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > +
> > > +static void
> > > +drm_gpuvm_free(struct kref *kref)
> > > +{
> > > +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > drm_gpuvm, kref);
> > > +
> > > +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > +               return;
> > > +
> > > +       drm_gpuvm_fini(gpuvm);
> > > +
> > > +       gpuvm->ops->vm_free(gpuvm);
> > > +}
> > > +
> > > +/**
> > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > copy-paste error in function name.
> > 
> > Also it appears like xe might put a vm from irq context so we
> > should
> > document the context where this function call is allowable, and if
> > applicable add a might_sleep().
> 
>  From GPUVM PoV I don't see why we can't call this from an IRQ
> context.
> It depends on the driver callbacks of GPUVM (->vm_free) and the resv
> GEM's
> free callback. Both are controlled by the driver. Hence, I don't see
> the
> need for a restriction here.

OK. we should keep in mind though, that if such a restriction is needed
in the future, it might be some work to fix the drivers.

> 
> > 
> > If this function needs to sleep we can work around that in Xe by
> > keeping an xe-private refcount for the xe vm container, but I'd
> > like to
> > avoid that if possible and piggy-back on the refcount introduced
> > here.
> > 
> > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > + *
> > > + * This releases a reference to @gpuvm.
> > > + */
> > > +void
> > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > +{
> > > +       if (gpuvm)
> > > +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > >   
> > >   static int
> > >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > >          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr,
> > > range)))
> > >                  return -EINVAL;
> > >   
> > > -       return __drm_gpuva_insert(gpuvm, va);
> > > +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > 
> > Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> > reference should be taken where the pointer holding the reference
> > is
> > assigned (in this case in __drm_gpuva_insert()), or document the
> > reference transfer from the argument close to the assignment.
> 
> Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
> doesn't work, because __drm_gpuva_insert() is used to insert the
> kernel_alloc_node. And we need to __drm_gpuva_remove() the
> kernel_alloc_node
> from drm_gpuvm_fini(), which is called when the reference count is at
> zero
> already. In fact, the __* variants are only there to handle the
> kernel_alloc_node and this one clearly doesn't need reference
> counting.
> 
> > 
> > But since a va itself is not refcounted it clearly can't outlive
> > the
> > vm, so is a reference really needed here?
> 
> Well, technically, it can. It just doesn't make any sense and would
> be
> considered to be a bug. The reference count comes in handy to prevent
> that in the first place.

> 
> I'd like to keep the reference count and just fix up the code.

OK. That's probably being a bit overly cautious IMHO, but I can't see
any major drawbacks either.

> 
> > 
> > I'd suggest using an accessor that instead of using va->vm uses va-
> > > vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> > altoghether.
> 
> No, I want to keep that optional. Drivers should be able to use GPUVM
> to
> track mappings without being required to implement everything else.
> 
> I think PowerVR, for instance, currently uses GPUVM only to track
> mappings
> without everything else.

Yeah, I also realized that userptr is another potential user.
A badly though-trough suggestion..

Thanks,
Thomas


> 
> - Danilo
> 
> > 
> > Thanks,
> > Thomas
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 18:04         ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 18:04 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: nouveau, linux-kernel, dri-devel

On Thu, 2023-11-02 at 18:32 +0100, Danilo Krummrich wrote:
> Hi Thomas,
> 
> thanks for your timely response on that!
> 
> On 11/2/23 18:09, Thomas Hellström wrote:
> > On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> > > Implement reference counting for struct drm_gpuvm.
> > > 
> > > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > > ---
> > >   drivers/gpu/drm/drm_gpuvm.c            | 44
> > > +++++++++++++++++++-----
> > > --
> > >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > >   3 files changed, 78 insertions(+), 17 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > b/drivers/gpu/drm/drm_gpuvm.c
> > > index 53e2c406fb04..6a88eafc5229 100644
> > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> > > char *name,
> > >          gpuvm->rb.tree = RB_ROOT_CACHED;
> > >          INIT_LIST_HEAD(&gpuvm->rb.list);
> > >   
> > > +       kref_init(&gpuvm->kref);
> > > +
> > >          gpuvm->name = name ? name : "unknown";
> > >          gpuvm->flags = flags;
> > >          gpuvm->ops = ops;
> > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > > const
> > > char *name,
> > >   }
> > >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > >   
> > > -/**
> > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > - *
> > > - * Note that it is a bug to call this function on a manager that
> > > still
> > > - * holds GPU VA mappings.
> > > - */
> > > -void
> > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > +static void
> > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > >   {
> > >          gpuvm->name = NULL;
> > >   
> > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > >   
> > >          drm_gem_object_put(gpuvm->r_obj);
> > >   }
> > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > +
> > > +static void
> > > +drm_gpuvm_free(struct kref *kref)
> > > +{
> > > +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > drm_gpuvm, kref);
> > > +
> > > +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > +               return;
> > > +
> > > +       drm_gpuvm_fini(gpuvm);
> > > +
> > > +       gpuvm->ops->vm_free(gpuvm);
> > > +}
> > > +
> > > +/**
> > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > copy-paste error in function name.
> > 
> > Also it appears like xe might put a vm from irq context so we
> > should
> > document the context where this function call is allowable, and if
> > applicable add a might_sleep().
> 
>  From GPUVM PoV I don't see why we can't call this from an IRQ
> context.
> It depends on the driver callbacks of GPUVM (->vm_free) and the resv
> GEM's
> free callback. Both are controlled by the driver. Hence, I don't see
> the
> need for a restriction here.

OK. we should keep in mind though, that if such a restriction is needed
in the future, it might be some work to fix the drivers.

> 
> > 
> > If this function needs to sleep we can work around that in Xe by
> > keeping an xe-private refcount for the xe vm container, but I'd
> > like to
> > avoid that if possible and piggy-back on the refcount introduced
> > here.
> > 
> > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > + *
> > > + * This releases a reference to @gpuvm.
> > > + */
> > > +void
> > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > +{
> > > +       if (gpuvm)
> > > +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > >   
> > >   static int
> > >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > >          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr,
> > > range)))
> > >                  return -EINVAL;
> > >   
> > > -       return __drm_gpuva_insert(gpuvm, va);
> > > +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > 
> > Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> > reference should be taken where the pointer holding the reference
> > is
> > assigned (in this case in __drm_gpuva_insert()), or document the
> > reference transfer from the argument close to the assignment.
> 
> Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
> doesn't work, because __drm_gpuva_insert() is used to insert the
> kernel_alloc_node. And we need to __drm_gpuva_remove() the
> kernel_alloc_node
> from drm_gpuvm_fini(), which is called when the reference count is at
> zero
> already. In fact, the __* variants are only there to handle the
> kernel_alloc_node and this one clearly doesn't need reference
> counting.
> 
> > 
> > But since a va itself is not refcounted it clearly can't outlive
> > the
> > vm, so is a reference really needed here?
> 
> Well, technically, it can. It just doesn't make any sense and would
> be
> considered to be a bug. The reference count comes in handy to prevent
> that in the first place.

> 
> I'd like to keep the reference count and just fix up the code.

OK. That's probably being a bit overly cautious IMHO, but I can't see
any major drawbacks either.

> 
> > 
> > I'd suggest using an accessor that instead of using va->vm uses va-
> > > vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> > altoghether.
> 
> No, I want to keep that optional. Drivers should be able to use GPUVM
> to
> track mappings without being required to implement everything else.
> 
> I think PowerVR, for instance, currently uses GPUVM only to track
> mappings
> without everything else.

Yeah, I also realized that userptr is another potential user.
A badly though-trough suggestion..

Thanks,
Thomas


> 
> - Danilo
> 
> > 
> > Thanks,
> > Thomas
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-02 18:04         ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-02 18:04 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost, sarah.walker,
	donald.robson, boris.brezillon, christian.koenig, faith
  Cc: dri-devel, nouveau, linux-kernel

On Thu, 2023-11-02 at 18:32 +0100, Danilo Krummrich wrote:
> Hi Thomas,
> 
> thanks for your timely response on that!
> 
> On 11/2/23 18:09, Thomas Hellström wrote:
> > On Thu, 2023-11-02 at 00:31 +0100, Danilo Krummrich wrote:
> > > Implement reference counting for struct drm_gpuvm.
> > > 
> > > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > > ---
> > >   drivers/gpu/drm/drm_gpuvm.c            | 44
> > > +++++++++++++++++++-----
> > > --
> > >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > >   3 files changed, 78 insertions(+), 17 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > b/drivers/gpu/drm/drm_gpuvm.c
> > > index 53e2c406fb04..6a88eafc5229 100644
> > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const
> > > char *name,
> > >          gpuvm->rb.tree = RB_ROOT_CACHED;
> > >          INIT_LIST_HEAD(&gpuvm->rb.list);
> > >   
> > > +       kref_init(&gpuvm->kref);
> > > +
> > >          gpuvm->name = name ? name : "unknown";
> > >          gpuvm->flags = flags;
> > >          gpuvm->ops = ops;
> > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > > const
> > > char *name,
> > >   }
> > >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > >   
> > > -/**
> > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > - *
> > > - * Note that it is a bug to call this function on a manager that
> > > still
> > > - * holds GPU VA mappings.
> > > - */
> > > -void
> > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > +static void
> > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > >   {
> > >          gpuvm->name = NULL;
> > >   
> > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > >   
> > >          drm_gem_object_put(gpuvm->r_obj);
> > >   }
> > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > +
> > > +static void
> > > +drm_gpuvm_free(struct kref *kref)
> > > +{
> > > +       struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > drm_gpuvm, kref);
> > > +
> > > +       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > +               return;
> > > +
> > > +       drm_gpuvm_fini(gpuvm);
> > > +
> > > +       gpuvm->ops->vm_free(gpuvm);
> > > +}
> > > +
> > > +/**
> > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > copy-paste error in function name.
> > 
> > Also it appears like xe might put a vm from irq context so we
> > should
> > document the context where this function call is allowable, and if
> > applicable add a might_sleep().
> 
>  From GPUVM PoV I don't see why we can't call this from an IRQ
> context.
> It depends on the driver callbacks of GPUVM (->vm_free) and the resv
> GEM's
> free callback. Both are controlled by the driver. Hence, I don't see
> the
> need for a restriction here.

OK. we should keep in mind though, that if such a restriction is needed
in the future, it might be some work to fix the drivers.

> 
> > 
> > If this function needs to sleep we can work around that in Xe by
> > keeping an xe-private refcount for the xe vm container, but I'd
> > like to
> > avoid that if possible and piggy-back on the refcount introduced
> > here.
> > 
> > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > + *
> > > + * This releases a reference to @gpuvm.
> > > + */
> > > +void
> > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > +{
> > > +       if (gpuvm)
> > > +               kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > >   
> > >   static int
> > >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > >          if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr,
> > > range)))
> > >                  return -EINVAL;
> > >   
> > > -       return __drm_gpuva_insert(gpuvm, va);
> > > +       return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > 
> > Here we leak a reference if __drm_gpuva_insert() fails, and IMO the
> > reference should be taken where the pointer holding the reference
> > is
> > assigned (in this case in __drm_gpuva_insert()), or document the
> > reference transfer from the argument close to the assignment.
> 
> Ah, good catch. I had it in __drm_gpuva_insert() originally, but that
> doesn't work, because __drm_gpuva_insert() is used to insert the
> kernel_alloc_node. And we need to __drm_gpuva_remove() the
> kernel_alloc_node
> from drm_gpuvm_fini(), which is called when the reference count is at
> zero
> already. In fact, the __* variants are only there to handle the
> kernel_alloc_node and this one clearly doesn't need reference
> counting.
> 
> > 
> > But since a va itself is not refcounted it clearly can't outlive
> > the
> > vm, so is a reference really needed here?
> 
> Well, technically, it can. It just doesn't make any sense and would
> be
> considered to be a bug. The reference count comes in handy to prevent
> that in the first place.

> 
> I'd like to keep the reference count and just fix up the code.

OK. That's probably being a bit overly cautious IMHO, but I can't see
any major drawbacks either.

> 
> > 
> > I'd suggest using an accessor that instead of using va->vm uses va-
> > > vm_bo->vm, to avoid needing to worry about the vm->vm refcount
> > altoghether.
> 
> No, I want to keep that optional. Drivers should be able to use GPUVM
> to
> track mappings without being required to implement everything else.
> 
> I think PowerVR, for instance, currently uses GPUVM only to track
> mappings
> without everything else.

Yeah, I also realized that userptr is another potential user.
A badly though-trough suggestion..

Thanks,
Thomas


> 
> - Danilo
> 
> > 
> > Thanks,
> > Thomas
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-01 23:31   ` Danilo Krummrich
  (?)
@ 2023-11-03  7:18     ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-03  7:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	faith
  Cc: dri-devel, nouveau, linux-kernel

Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> Implement reference counting for struct drm_gpuvm.

 From the design point of view what is that good for?

Background is that the most common use case I see is that this object is 
embedded into something else and a reference count is then not really a 
good idea.

Thanks,
Christian.

>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>   3 files changed, 78 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   	gpuvm->rb.tree = RB_ROOT_CACHED;
>   	INIT_LIST_HEAD(&gpuvm->rb.list);
>   
> +	kref_init(&gpuvm->kref);
> +
>   	gpuvm->name = name ? name : "unknown";
>   	gpuvm->flags = flags;
>   	gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>   
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>   {
>   	gpuvm->name = NULL;
>   
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>   
>   	drm_gem_object_put(gpuvm->r_obj);
>   }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> +
> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +		return;
> +
> +	drm_gpuvm_fini(gpuvm);
> +
> +	gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +	if (gpuvm)
> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>   
>   static int
>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>   		return -EINVAL;
>   
> -	return __drm_gpuva_insert(gpuvm, va);
> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>   
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>   	}
>   
>   	__drm_gpuva_remove(va);
> +	drm_gpuvm_put(va->vm);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>   
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>   	}
>   }
>   
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +	kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +	.vm_free = nouveau_uvmm_free,
> +};
> +
>   int
>   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   			   void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   		       NOUVEAU_VA_SPACE_END,
>   		       init->kernel_managed_addr,
>   		       init->kernel_managed_size,
> -		       NULL);
> +		       &gpuvm_ops);
>   	/* GPUVM takes care from here on. */
>   	drm_gem_object_put(r_obj);
>   
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   	return 0;
>   
>   out_gpuvm_fini:
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   out_unlock:
>   	mutex_unlock(&cli->mutex);
>   	return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>   
>   	mutex_lock(&cli->mutex);
>   	nouveau_vmm_fini(&uvmm->vmm);
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   	mutex_unlock(&cli->mutex);
>   }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>   		struct list_head list;
>   	} rb;
>   
> +	/**
> +	 * @kref: reference count of this object
> +	 */
> +	struct kref kref;
> +
>   	/**
>   	 * @kernel_alloc_node:
>   	 *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   		    u64 start_offset, u64 range,
>   		    u64 reserve_offset, u64 reserve_range,
>   		    const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +	kref_get(&gpuvm->kref);
> +
> +	return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>   
>   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>    * operations to drivers.
>    */
>   struct drm_gpuvm_ops {
> +	/**
> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> +	 * dropped
> +	 *
> +	 * This callback is mandatory.
> +	 */
> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>   	/**
>   	 * @op_alloc: called when the &drm_gpuvm allocates
>   	 * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03  7:18     ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-03  7:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	faith
  Cc: nouveau, linux-kernel, dri-devel

Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> Implement reference counting for struct drm_gpuvm.

 From the design point of view what is that good for?

Background is that the most common use case I see is that this object is 
embedded into something else and a reference count is then not really a 
good idea.

Thanks,
Christian.

>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>   3 files changed, 78 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   	gpuvm->rb.tree = RB_ROOT_CACHED;
>   	INIT_LIST_HEAD(&gpuvm->rb.list);
>   
> +	kref_init(&gpuvm->kref);
> +
>   	gpuvm->name = name ? name : "unknown";
>   	gpuvm->flags = flags;
>   	gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>   
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>   {
>   	gpuvm->name = NULL;
>   
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>   
>   	drm_gem_object_put(gpuvm->r_obj);
>   }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> +
> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +		return;
> +
> +	drm_gpuvm_fini(gpuvm);
> +
> +	gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +	if (gpuvm)
> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>   
>   static int
>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>   		return -EINVAL;
>   
> -	return __drm_gpuva_insert(gpuvm, va);
> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>   
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>   	}
>   
>   	__drm_gpuva_remove(va);
> +	drm_gpuvm_put(va->vm);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>   
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>   	}
>   }
>   
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +	kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +	.vm_free = nouveau_uvmm_free,
> +};
> +
>   int
>   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   			   void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   		       NOUVEAU_VA_SPACE_END,
>   		       init->kernel_managed_addr,
>   		       init->kernel_managed_size,
> -		       NULL);
> +		       &gpuvm_ops);
>   	/* GPUVM takes care from here on. */
>   	drm_gem_object_put(r_obj);
>   
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   	return 0;
>   
>   out_gpuvm_fini:
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   out_unlock:
>   	mutex_unlock(&cli->mutex);
>   	return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>   
>   	mutex_lock(&cli->mutex);
>   	nouveau_vmm_fini(&uvmm->vmm);
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   	mutex_unlock(&cli->mutex);
>   }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>   		struct list_head list;
>   	} rb;
>   
> +	/**
> +	 * @kref: reference count of this object
> +	 */
> +	struct kref kref;
> +
>   	/**
>   	 * @kernel_alloc_node:
>   	 *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   		    u64 start_offset, u64 range,
>   		    u64 reserve_offset, u64 reserve_range,
>   		    const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +	kref_get(&gpuvm->kref);
> +
> +	return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>   
>   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>    * operations to drivers.
>    */
>   struct drm_gpuvm_ops {
> +	/**
> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> +	 * dropped
> +	 *
> +	 * This callback is mandatory.
> +	 */
> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>   	/**
>   	 * @op_alloc: called when the &drm_gpuvm allocates
>   	 * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03  7:18     ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-03  7:18 UTC (permalink / raw)
  To: Danilo Krummrich, airlied, daniel, matthew.brost,
	thomas.hellstrom, sarah.walker, donald.robson, boris.brezillon,
	faith
  Cc: nouveau, linux-kernel, dri-devel

Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> Implement reference counting for struct drm_gpuvm.

 From the design point of view what is that good for?

Background is that the most common use case I see is that this object is 
embedded into something else and a reference count is then not really a 
good idea.

Thanks,
Christian.

>
> Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> ---
>   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>   3 files changed, 78 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> index 53e2c406fb04..6a88eafc5229 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   	gpuvm->rb.tree = RB_ROOT_CACHED;
>   	INIT_LIST_HEAD(&gpuvm->rb.list);
>   
> +	kref_init(&gpuvm->kref);
> +
>   	gpuvm->name = name ? name : "unknown";
>   	gpuvm->flags = flags;
>   	gpuvm->ops = ops;
> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>   
> -/**
> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> - * @gpuvm: pointer to the &drm_gpuvm to clean up
> - *
> - * Note that it is a bug to call this function on a manager that still
> - * holds GPU VA mappings.
> - */
> -void
> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> +static void
> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>   {
>   	gpuvm->name = NULL;
>   
> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>   
>   	drm_gem_object_put(gpuvm->r_obj);
>   }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> +
> +static void
> +drm_gpuvm_free(struct kref *kref)
> +{
> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> +
> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +		return;
> +
> +	drm_gpuvm_fini(gpuvm);
> +
> +	gpuvm->ops->vm_free(gpuvm);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to release the reference of
> + *
> + * This releases a reference to @gpuvm.
> + */
> +void
> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> +{
> +	if (gpuvm)
> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>   
>   static int
>   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>   		return -EINVAL;
>   
> -	return __drm_gpuva_insert(gpuvm, va);
> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>   
> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>   	}
>   
>   	__drm_gpuva_remove(va);
> +	drm_gpuvm_put(va->vm);
>   }
>   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>   
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 54be12c1272f..cb2f06565c46 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>   	}
>   }
>   
> +static void
> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> +{
> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> +
> +	kfree(uvmm);
> +}
> +
> +static const struct drm_gpuvm_ops gpuvm_ops = {
> +	.vm_free = nouveau_uvmm_free,
> +};
> +
>   int
>   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   			   void *data,
> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   		       NOUVEAU_VA_SPACE_END,
>   		       init->kernel_managed_addr,
>   		       init->kernel_managed_size,
> -		       NULL);
> +		       &gpuvm_ops);
>   	/* GPUVM takes care from here on. */
>   	drm_gem_object_put(r_obj);
>   
> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>   	return 0;
>   
>   out_gpuvm_fini:
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   out_unlock:
>   	mutex_unlock(&cli->mutex);
>   	return ret;
> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>   
>   	mutex_lock(&cli->mutex);
>   	nouveau_vmm_fini(&uvmm->vmm);
> -	drm_gpuvm_destroy(&uvmm->base);
> -	kfree(uvmm);
> +	drm_gpuvm_put(&uvmm->base);
>   	mutex_unlock(&cli->mutex);
>   }
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0c2e24155a93..4e6e1fd3485a 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>   		struct list_head list;
>   	} rb;
>   
> +	/**
> +	 * @kref: reference count of this object
> +	 */
> +	struct kref kref;
> +
>   	/**
>   	 * @kernel_alloc_node:
>   	 *
> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>   		    u64 start_offset, u64 range,
>   		    u64 reserve_offset, u64 reserve_range,
>   		    const struct drm_gpuvm_ops *ops);
> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> +
> +/**
> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> + * @gpuvm: the &drm_gpuvm to acquire the reference of
> + *
> + * This function acquires an additional reference to @gpuvm. It is illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm *
> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> +{
> +	kref_get(&gpuvm->kref);
> +
> +	return gpuvm;
> +}
> +
> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>   
>   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>    * operations to drivers.
>    */
>   struct drm_gpuvm_ops {
> +	/**
> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> +	 * dropped
> +	 *
> +	 * This callback is mandatory.
> +	 */
> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> +
>   	/**
>   	 * @op_alloc: called when the &drm_gpuvm allocates
>   	 * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-03  7:18     ` [Nouveau] " Christian König
  (?)
@ 2023-11-03 13:14       ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 13:14 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> > Implement reference counting for struct drm_gpuvm.
> 
> From the design point of view what is that good for?

It was discussed in this thread [1].

Essentially, the idea is to make sure that vm_bo->vm is always valid without the
driver having the need to take extra care. It also ensures that GPUVM can't be
freed with mappings still held.

> 
> Background is that the most common use case I see is that this object is
> embedded into something else and a reference count is then not really a good
> idea.

Do you have a specific use-case in mind where this would interfere?

> 
> Thanks,
> Christian.

[1] https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/

> 
> > 
> > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > ---
> >   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
> >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> >   3 files changed, 78 insertions(+), 17 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> > index 53e2c406fb04..6a88eafc5229 100644
> > --- a/drivers/gpu/drm/drm_gpuvm.c
> > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   	gpuvm->rb.tree = RB_ROOT_CACHED;
> >   	INIT_LIST_HEAD(&gpuvm->rb.list);
> > +	kref_init(&gpuvm->kref);
> > +
> >   	gpuvm->name = name ? name : "unknown";
> >   	gpuvm->flags = flags;
> >   	gpuvm->ops = ops;
> > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > -/**
> > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > - *
> > - * Note that it is a bug to call this function on a manager that still
> > - * holds GPU VA mappings.
> > - */
> > -void
> > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > +static void
> > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> >   {
> >   	gpuvm->name = NULL;
> > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> >   	drm_gem_object_put(gpuvm->r_obj);
> >   }
> > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > +
> > +static void
> > +drm_gpuvm_free(struct kref *kref)
> > +{
> > +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> > +
> > +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > +		return;
> > +
> > +	drm_gpuvm_fini(gpuvm);
> > +
> > +	gpuvm->ops->vm_free(gpuvm);
> > +}
> > +
> > +/**
> > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to release the reference of
> > + *
> > + * This releases a reference to @gpuvm.
> > + */
> > +void
> > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > +{
> > +	if (gpuvm)
> > +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> >   static int
> >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> >   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> >   		return -EINVAL;
> > -	return __drm_gpuva_insert(gpuvm, va);
> > +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> >   	}
> >   	__drm_gpuva_remove(va);
> > +	drm_gpuvm_put(va->vm);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > index 54be12c1272f..cb2f06565c46 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
> >   	}
> >   }
> > +static void
> > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > +{
> > +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > +
> > +	kfree(uvmm);
> > +}
> > +
> > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > +	.vm_free = nouveau_uvmm_free,
> > +};
> > +
> >   int
> >   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   			   void *data,
> > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   		       NOUVEAU_VA_SPACE_END,
> >   		       init->kernel_managed_addr,
> >   		       init->kernel_managed_size,
> > -		       NULL);
> > +		       &gpuvm_ops);
> >   	/* GPUVM takes care from here on. */
> >   	drm_gem_object_put(r_obj);
> > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   	return 0;
> >   out_gpuvm_fini:
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   out_unlock:
> >   	mutex_unlock(&cli->mutex);
> >   	return ret;
> > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> >   	mutex_lock(&cli->mutex);
> >   	nouveau_vmm_fini(&uvmm->vmm);
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   	mutex_unlock(&cli->mutex);
> >   }
> > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > index 0c2e24155a93..4e6e1fd3485a 100644
> > --- a/include/drm/drm_gpuvm.h
> > +++ b/include/drm/drm_gpuvm.h
> > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> >   		struct list_head list;
> >   	} rb;
> > +	/**
> > +	 * @kref: reference count of this object
> > +	 */
> > +	struct kref kref;
> > +
> >   	/**
> >   	 * @kernel_alloc_node:
> >   	 *
> > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   		    u64 start_offset, u64 range,
> >   		    u64 reserve_offset, u64 reserve_range,
> >   		    const struct drm_gpuvm_ops *ops);
> > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > +
> > +/**
> > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > + *
> > + * This function acquires an additional reference to @gpuvm. It is illegal to
> > + * call this without already holding a reference. No locks required.
> > + */
> > +static inline struct drm_gpuvm *
> > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > +{
> > +	kref_get(&gpuvm->kref);
> > +
> > +	return gpuvm;
> > +}
> > +
> > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> >   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> >   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> > @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
> >    * operations to drivers.
> >    */
> >   struct drm_gpuvm_ops {
> > +	/**
> > +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> > +	 * dropped
> > +	 *
> > +	 * This callback is mandatory.
> > +	 */
> > +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> > +
> >   	/**
> >   	 * @op_alloc: called when the &drm_gpuvm allocates
> >   	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03 13:14       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 13:14 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> > Implement reference counting for struct drm_gpuvm.
> 
> From the design point of view what is that good for?

It was discussed in this thread [1].

Essentially, the idea is to make sure that vm_bo->vm is always valid without the
driver having the need to take extra care. It also ensures that GPUVM can't be
freed with mappings still held.

> 
> Background is that the most common use case I see is that this object is
> embedded into something else and a reference count is then not really a good
> idea.

Do you have a specific use-case in mind where this would interfere?

> 
> Thanks,
> Christian.

[1] https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/

> 
> > 
> > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > ---
> >   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
> >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> >   3 files changed, 78 insertions(+), 17 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> > index 53e2c406fb04..6a88eafc5229 100644
> > --- a/drivers/gpu/drm/drm_gpuvm.c
> > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   	gpuvm->rb.tree = RB_ROOT_CACHED;
> >   	INIT_LIST_HEAD(&gpuvm->rb.list);
> > +	kref_init(&gpuvm->kref);
> > +
> >   	gpuvm->name = name ? name : "unknown";
> >   	gpuvm->flags = flags;
> >   	gpuvm->ops = ops;
> > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > -/**
> > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > - *
> > - * Note that it is a bug to call this function on a manager that still
> > - * holds GPU VA mappings.
> > - */
> > -void
> > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > +static void
> > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> >   {
> >   	gpuvm->name = NULL;
> > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> >   	drm_gem_object_put(gpuvm->r_obj);
> >   }
> > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > +
> > +static void
> > +drm_gpuvm_free(struct kref *kref)
> > +{
> > +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> > +
> > +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > +		return;
> > +
> > +	drm_gpuvm_fini(gpuvm);
> > +
> > +	gpuvm->ops->vm_free(gpuvm);
> > +}
> > +
> > +/**
> > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to release the reference of
> > + *
> > + * This releases a reference to @gpuvm.
> > + */
> > +void
> > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > +{
> > +	if (gpuvm)
> > +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> >   static int
> >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> >   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> >   		return -EINVAL;
> > -	return __drm_gpuva_insert(gpuvm, va);
> > +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> >   	}
> >   	__drm_gpuva_remove(va);
> > +	drm_gpuvm_put(va->vm);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > index 54be12c1272f..cb2f06565c46 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
> >   	}
> >   }
> > +static void
> > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > +{
> > +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > +
> > +	kfree(uvmm);
> > +}
> > +
> > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > +	.vm_free = nouveau_uvmm_free,
> > +};
> > +
> >   int
> >   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   			   void *data,
> > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   		       NOUVEAU_VA_SPACE_END,
> >   		       init->kernel_managed_addr,
> >   		       init->kernel_managed_size,
> > -		       NULL);
> > +		       &gpuvm_ops);
> >   	/* GPUVM takes care from here on. */
> >   	drm_gem_object_put(r_obj);
> > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   	return 0;
> >   out_gpuvm_fini:
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   out_unlock:
> >   	mutex_unlock(&cli->mutex);
> >   	return ret;
> > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> >   	mutex_lock(&cli->mutex);
> >   	nouveau_vmm_fini(&uvmm->vmm);
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   	mutex_unlock(&cli->mutex);
> >   }
> > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > index 0c2e24155a93..4e6e1fd3485a 100644
> > --- a/include/drm/drm_gpuvm.h
> > +++ b/include/drm/drm_gpuvm.h
> > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> >   		struct list_head list;
> >   	} rb;
> > +	/**
> > +	 * @kref: reference count of this object
> > +	 */
> > +	struct kref kref;
> > +
> >   	/**
> >   	 * @kernel_alloc_node:
> >   	 *
> > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   		    u64 start_offset, u64 range,
> >   		    u64 reserve_offset, u64 reserve_range,
> >   		    const struct drm_gpuvm_ops *ops);
> > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > +
> > +/**
> > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > + *
> > + * This function acquires an additional reference to @gpuvm. It is illegal to
> > + * call this without already holding a reference. No locks required.
> > + */
> > +static inline struct drm_gpuvm *
> > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > +{
> > +	kref_get(&gpuvm->kref);
> > +
> > +	return gpuvm;
> > +}
> > +
> > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> >   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> >   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> > @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
> >    * operations to drivers.
> >    */
> >   struct drm_gpuvm_ops {
> > +	/**
> > +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> > +	 * dropped
> > +	 *
> > +	 * This callback is mandatory.
> > +	 */
> > +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> > +
> >   	/**
> >   	 * @op_alloc: called when the &drm_gpuvm allocates
> >   	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03 13:14       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 13:14 UTC (permalink / raw)
  To: Christian König
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
> > Implement reference counting for struct drm_gpuvm.
> 
> From the design point of view what is that good for?

It was discussed in this thread [1].

Essentially, the idea is to make sure that vm_bo->vm is always valid without the
driver having the need to take extra care. It also ensures that GPUVM can't be
freed with mappings still held.

> 
> Background is that the most common use case I see is that this object is
> embedded into something else and a reference count is then not really a good
> idea.

Do you have a specific use-case in mind where this would interfere?

> 
> Thanks,
> Christian.

[1] https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/

> 
> > 
> > Signed-off-by: Danilo Krummrich <dakr@redhat.com>
> > ---
> >   drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
> >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> >   include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> >   3 files changed, 78 insertions(+), 17 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> > index 53e2c406fb04..6a88eafc5229 100644
> > --- a/drivers/gpu/drm/drm_gpuvm.c
> > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   	gpuvm->rb.tree = RB_ROOT_CACHED;
> >   	INIT_LIST_HEAD(&gpuvm->rb.list);
> > +	kref_init(&gpuvm->kref);
> > +
> >   	gpuvm->name = name ? name : "unknown";
> >   	gpuvm->flags = flags;
> >   	gpuvm->ops = ops;
> > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > -/**
> > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > - *
> > - * Note that it is a bug to call this function on a manager that still
> > - * holds GPU VA mappings.
> > - */
> > -void
> > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > +static void
> > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> >   {
> >   	gpuvm->name = NULL;
> > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> >   	drm_gem_object_put(gpuvm->r_obj);
> >   }
> > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > +
> > +static void
> > +drm_gpuvm_free(struct kref *kref)
> > +{
> > +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
> > +
> > +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > +		return;
> > +
> > +	drm_gpuvm_fini(gpuvm);
> > +
> > +	gpuvm->ops->vm_free(gpuvm);
> > +}
> > +
> > +/**
> > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to release the reference of
> > + *
> > + * This releases a reference to @gpuvm.
> > + */
> > +void
> > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > +{
> > +	if (gpuvm)
> > +		kref_put(&gpuvm->kref, drm_gpuvm_free);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> >   static int
> >   __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> >   	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> >   		return -EINVAL;
> > -	return __drm_gpuva_insert(gpuvm, va);
> > +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> >   	}
> >   	__drm_gpuva_remove(va);
> > +	drm_gpuvm_put(va->vm);
> >   }
> >   EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > index 54be12c1272f..cb2f06565c46 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
> >   	}
> >   }
> > +static void
> > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > +{
> > +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > +
> > +	kfree(uvmm);
> > +}
> > +
> > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > +	.vm_free = nouveau_uvmm_free,
> > +};
> > +
> >   int
> >   nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   			   void *data,
> > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   		       NOUVEAU_VA_SPACE_END,
> >   		       init->kernel_managed_addr,
> >   		       init->kernel_managed_size,
> > -		       NULL);
> > +		       &gpuvm_ops);
> >   	/* GPUVM takes care from here on. */
> >   	drm_gem_object_put(r_obj);
> > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> >   	return 0;
> >   out_gpuvm_fini:
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   out_unlock:
> >   	mutex_unlock(&cli->mutex);
> >   	return ret;
> > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> >   	mutex_lock(&cli->mutex);
> >   	nouveau_vmm_fini(&uvmm->vmm);
> > -	drm_gpuvm_destroy(&uvmm->base);
> > -	kfree(uvmm);
> > +	drm_gpuvm_put(&uvmm->base);
> >   	mutex_unlock(&cli->mutex);
> >   }
> > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > index 0c2e24155a93..4e6e1fd3485a 100644
> > --- a/include/drm/drm_gpuvm.h
> > +++ b/include/drm/drm_gpuvm.h
> > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> >   		struct list_head list;
> >   	} rb;
> > +	/**
> > +	 * @kref: reference count of this object
> > +	 */
> > +	struct kref kref;
> > +
> >   	/**
> >   	 * @kernel_alloc_node:
> >   	 *
> > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> >   		    u64 start_offset, u64 range,
> >   		    u64 reserve_offset, u64 reserve_range,
> >   		    const struct drm_gpuvm_ops *ops);
> > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > +
> > +/**
> > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > + *
> > + * This function acquires an additional reference to @gpuvm. It is illegal to
> > + * call this without already holding a reference. No locks required.
> > + */
> > +static inline struct drm_gpuvm *
> > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > +{
> > +	kref_get(&gpuvm->kref);
> > +
> > +	return gpuvm;
> > +}
> > +
> > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> >   bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> >   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
> > @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
> >    * operations to drivers.
> >    */
> >   struct drm_gpuvm_ops {
> > +	/**
> > +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
> > +	 * dropped
> > +	 *
> > +	 * This callback is mandatory.
> > +	 */
> > +	void (*vm_free)(struct drm_gpuvm *gpuvm);
> > +
> >   	/**
> >   	 * @op_alloc: called when the &drm_gpuvm allocates
> >   	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-03 13:14       ` Danilo Krummrich
@ 2023-11-03 14:04         ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-03 14:04 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

[-- Attachment #1: Type: text/plain, Size: 8139 bytes --]

Am 03.11.23 um 14:14 schrieb Danilo Krummrich:
> On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
>> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
>>> Implement reference counting for struct drm_gpuvm.
>>  From the design point of view what is that good for?
> It was discussed in this thread [1].
>
> Essentially, the idea is to make sure that vm_bo->vm is always valid without the
> driver having the need to take extra care. It also ensures that GPUVM can't be
> freed with mappings still held.

Well in this case I have some objections to this. The lifetime of the VM 
is driver and use case specific.

Especially we most likely don't want the VM to live longer than the 
application which originally used it. If you make the GPUVM an 
independent object you actually open up driver abuse for the lifetime of 
this.

Additional to that see below for a quite real problem with this.

>> Background is that the most common use case I see is that this object is
>> embedded into something else and a reference count is then not really a good
>> idea.
> Do you have a specific use-case in mind where this would interfere?

Yes, absolutely. For an example see amdgpu_mes_self_test(), here we 
initialize a temporary amdgpu VM for an in kernel unit test which runs 
during driver load.

When the function returns I need to guarantee that the VM is destroyed 
or otherwise I will mess up normal operation.

Reference counting is nice when you don't know who else is referring to 
your VM, but the cost is that you also don't know when the object will 
guardedly be destroyed.

I can trivially work around this by saying that the generic GPUVM object 
has a different lifetime than the amdgpu specific object, but that opens 
up doors for use after free again.

Regards,
Christian.

>
>> Thanks,
>> Christian.
> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>
>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>> ---
>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>> index 53e2c406fb04..6a88eafc5229 100644
>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    	gpuvm->rb.tree = RB_ROOT_CACHED;
>>>    	INIT_LIST_HEAD(&gpuvm->rb.list);
>>> +	kref_init(&gpuvm->kref);
>>> +
>>>    	gpuvm->name = name ? name : "unknown";
>>>    	gpuvm->flags = flags;
>>>    	gpuvm->ops = ops;
>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>> -/**
>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>> - *
>>> - * Note that it is a bug to call this function on a manager that still
>>> - * holds GPU VA mappings.
>>> - */
>>> -void
>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>> +static void
>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>    {
>>>    	gpuvm->name = NULL;
>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>    	drm_gem_object_put(gpuvm->r_obj);
>>>    }
>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>> +
>>> +static void
>>> +drm_gpuvm_free(struct kref *kref)
>>> +{
>>> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
>>> +
>>> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>> +		return;
>>> +
>>> +	drm_gpuvm_fini(gpuvm);
>>> +
>>> +	gpuvm->ops->vm_free(gpuvm);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>> + *
>>> + * This releases a reference to @gpuvm.
>>> + */
>>> +void
>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	if (gpuvm)
>>> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>    static int
>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>    	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>    		return -EINVAL;
>>> -	return __drm_gpuva_insert(gpuvm, va);
>>> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>    	}
>>>    	__drm_gpuva_remove(va);
>>> +	drm_gpuvm_put(va->vm);
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> index 54be12c1272f..cb2f06565c46 100644
>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>    	}
>>>    }
>>> +static void
>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>> +
>>> +	kfree(uvmm);
>>> +}
>>> +
>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>> +	.vm_free = nouveau_uvmm_free,
>>> +};
>>> +
>>>    int
>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    			   void *data,
>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    		       NOUVEAU_VA_SPACE_END,
>>>    		       init->kernel_managed_addr,
>>>    		       init->kernel_managed_size,
>>> -		       NULL);
>>> +		       &gpuvm_ops);
>>>    	/* GPUVM takes care from here on. */
>>>    	drm_gem_object_put(r_obj);
>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    	return 0;
>>>    out_gpuvm_fini:
>>> -	drm_gpuvm_destroy(&uvmm->base);
>>> -	kfree(uvmm);
>>> +	drm_gpuvm_put(&uvmm->base);
>>>    out_unlock:
>>>    	mutex_unlock(&cli->mutex);
>>>    	return ret;
>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>    	mutex_lock(&cli->mutex);
>>>    	nouveau_vmm_fini(&uvmm->vmm);
>>> -	drm_gpuvm_destroy(&uvmm->base);
>>> -	kfree(uvmm);
>>> +	drm_gpuvm_put(&uvmm->base);
>>>    	mutex_unlock(&cli->mutex);
>>>    }
>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>> --- a/include/drm/drm_gpuvm.h
>>> +++ b/include/drm/drm_gpuvm.h
>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>    		struct list_head list;
>>>    	} rb;
>>> +	/**
>>> +	 * @kref: reference count of this object
>>> +	 */
>>> +	struct kref kref;
>>> +
>>>    	/**
>>>    	 * @kernel_alloc_node:
>>>    	 *
>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    		    u64 start_offset, u64 range,
>>>    		    u64 reserve_offset, u64 reserve_range,
>>>    		    const struct drm_gpuvm_ops *ops);
>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>> +
>>> +/**
>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>> + *
>>> + * This function acquires an additional reference to @gpuvm. It is illegal to
>>> + * call this without already holding a reference. No locks required.
>>> + */
>>> +static inline struct drm_gpuvm *
>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	kref_get(&gpuvm->kref);
>>> +
>>> +	return gpuvm;
>>> +}
>>> +
>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>     * operations to drivers.
>>>     */
>>>    struct drm_gpuvm_ops {
>>> +	/**
>>> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
>>> +	 * dropped
>>> +	 *
>>> +	 * This callback is mandatory.
>>> +	 */
>>> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
>>> +
>>>    	/**
>>>    	 * @op_alloc: called when the &drm_gpuvm allocates
>>>    	 * a struct drm_gpuva_op

[-- Attachment #2: Type: text/html, Size: 9318 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03 14:04         ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-03 14:04 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

[-- Attachment #1: Type: text/plain, Size: 8139 bytes --]

Am 03.11.23 um 14:14 schrieb Danilo Krummrich:
> On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
>> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
>>> Implement reference counting for struct drm_gpuvm.
>>  From the design point of view what is that good for?
> It was discussed in this thread [1].
>
> Essentially, the idea is to make sure that vm_bo->vm is always valid without the
> driver having the need to take extra care. It also ensures that GPUVM can't be
> freed with mappings still held.

Well in this case I have some objections to this. The lifetime of the VM 
is driver and use case specific.

Especially we most likely don't want the VM to live longer than the 
application which originally used it. If you make the GPUVM an 
independent object you actually open up driver abuse for the lifetime of 
this.

Additional to that see below for a quite real problem with this.

>> Background is that the most common use case I see is that this object is
>> embedded into something else and a reference count is then not really a good
>> idea.
> Do you have a specific use-case in mind where this would interfere?

Yes, absolutely. For an example see amdgpu_mes_self_test(), here we 
initialize a temporary amdgpu VM for an in kernel unit test which runs 
during driver load.

When the function returns I need to guarantee that the VM is destroyed 
or otherwise I will mess up normal operation.

Reference counting is nice when you don't know who else is referring to 
your VM, but the cost is that you also don't know when the object will 
guardedly be destroyed.

I can trivially work around this by saying that the generic GPUVM object 
has a different lifetime than the amdgpu specific object, but that opens 
up doors for use after free again.

Regards,
Christian.

>
>> Thanks,
>> Christian.
> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>
>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>> ---
>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>> index 53e2c406fb04..6a88eafc5229 100644
>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    	gpuvm->rb.tree = RB_ROOT_CACHED;
>>>    	INIT_LIST_HEAD(&gpuvm->rb.list);
>>> +	kref_init(&gpuvm->kref);
>>> +
>>>    	gpuvm->name = name ? name : "unknown";
>>>    	gpuvm->flags = flags;
>>>    	gpuvm->ops = ops;
>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>> -/**
>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>> - *
>>> - * Note that it is a bug to call this function on a manager that still
>>> - * holds GPU VA mappings.
>>> - */
>>> -void
>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>> +static void
>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>    {
>>>    	gpuvm->name = NULL;
>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>    	drm_gem_object_put(gpuvm->r_obj);
>>>    }
>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>> +
>>> +static void
>>> +drm_gpuvm_free(struct kref *kref)
>>> +{
>>> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
>>> +
>>> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>> +		return;
>>> +
>>> +	drm_gpuvm_fini(gpuvm);
>>> +
>>> +	gpuvm->ops->vm_free(gpuvm);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>> + *
>>> + * This releases a reference to @gpuvm.
>>> + */
>>> +void
>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	if (gpuvm)
>>> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>    static int
>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>    	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>    		return -EINVAL;
>>> -	return __drm_gpuva_insert(gpuvm, va);
>>> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>    	}
>>>    	__drm_gpuva_remove(va);
>>> +	drm_gpuvm_put(va->vm);
>>>    }
>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> index 54be12c1272f..cb2f06565c46 100644
>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>    	}
>>>    }
>>> +static void
>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>> +
>>> +	kfree(uvmm);
>>> +}
>>> +
>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>> +	.vm_free = nouveau_uvmm_free,
>>> +};
>>> +
>>>    int
>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    			   void *data,
>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    		       NOUVEAU_VA_SPACE_END,
>>>    		       init->kernel_managed_addr,
>>>    		       init->kernel_managed_size,
>>> -		       NULL);
>>> +		       &gpuvm_ops);
>>>    	/* GPUVM takes care from here on. */
>>>    	drm_gem_object_put(r_obj);
>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>    	return 0;
>>>    out_gpuvm_fini:
>>> -	drm_gpuvm_destroy(&uvmm->base);
>>> -	kfree(uvmm);
>>> +	drm_gpuvm_put(&uvmm->base);
>>>    out_unlock:
>>>    	mutex_unlock(&cli->mutex);
>>>    	return ret;
>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>    	mutex_lock(&cli->mutex);
>>>    	nouveau_vmm_fini(&uvmm->vmm);
>>> -	drm_gpuvm_destroy(&uvmm->base);
>>> -	kfree(uvmm);
>>> +	drm_gpuvm_put(&uvmm->base);
>>>    	mutex_unlock(&cli->mutex);
>>>    }
>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>> --- a/include/drm/drm_gpuvm.h
>>> +++ b/include/drm/drm_gpuvm.h
>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>    		struct list_head list;
>>>    	} rb;
>>> +	/**
>>> +	 * @kref: reference count of this object
>>> +	 */
>>> +	struct kref kref;
>>> +
>>>    	/**
>>>    	 * @kernel_alloc_node:
>>>    	 *
>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>    		    u64 start_offset, u64 range,
>>>    		    u64 reserve_offset, u64 reserve_range,
>>>    		    const struct drm_gpuvm_ops *ops);
>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>> +
>>> +/**
>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>> + *
>>> + * This function acquires an additional reference to @gpuvm. It is illegal to
>>> + * call this without already holding a reference. No locks required.
>>> + */
>>> +static inline struct drm_gpuvm *
>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>> +{
>>> +	kref_get(&gpuvm->kref);
>>> +
>>> +	return gpuvm;
>>> +}
>>> +
>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>     * operations to drivers.
>>>     */
>>>    struct drm_gpuvm_ops {
>>> +	/**
>>> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
>>> +	 * dropped
>>> +	 *
>>> +	 * This callback is mandatory.
>>> +	 */
>>> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
>>> +
>>>    	/**
>>>    	 * @op_alloc: called when the &drm_gpuvm allocates
>>>    	 * a struct drm_gpuva_op

[-- Attachment #2: Type: text/html, Size: 9318 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-03 14:04         ` Christian König
  (?)
@ 2023-11-03 15:34           ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 15:34 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

On 11/3/23 15:04, Christian König wrote:
> Am 03.11.23 um 14:14 schrieb Danilo Krummrich:
>> On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
>>> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
>>>> Implement reference counting for struct drm_gpuvm.
>>>  From the design point of view what is that good for?
>> It was discussed in this thread [1].
>>
>> Essentially, the idea is to make sure that vm_bo->vm is always valid without the
>> driver having the need to take extra care. It also ensures that GPUVM can't be
>> freed with mappings still held.
> 
> Well in this case I have some objections to this. The lifetime of the VM is driver and use case specific.

That's fine, I don't see how this changes with a reference count.

> 
> Especially we most likely don't want the VM to live longer than the application which originally used it. If you make the GPUVM an independent object you actually open up driver abuse for the lifetime of this.

Right, we don't want that. But I don't see how the reference count prevents that.

Independant object is relative. struct drm_gpuvm is still embedded into a driver
specific structure. It's working the same way as with struct drm_gem_obejct.

> 
> Additional to that see below for a quite real problem with this.
> 
>>> Background is that the most common use case I see is that this object is
>>> embedded into something else and a reference count is then not really a good
>>> idea.
>> Do you have a specific use-case in mind where this would interfere?
> 
> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we initialize a temporary amdgpu VM for an in kernel unit test which runs during driver load.
> 
> When the function returns I need to guarantee that the VM is destroyed or otherwise I will mess up normal operation.

Nothing prevents that. The reference counting is well defined. If the driver did not
take additional references (which is clearly up to the driver taking care of) and all
VM_BOs and mappings are cleaned up, the reference count is guaranteed to be 1 at this
point.

Also note that if the driver would have not cleaned up all VM_BOs and mappings before
shutting down the VM, it would have been a bug anyways and the driver would potentially
leak memory and UAF issues.

Hence, If the VM is still alive at a point where you don't expect it to be, then it's
simply a driver bug.

> 
> Reference counting is nice when you don't know who else is referring to your VM, but the cost is that you also don't know when the object will guardedly be destroyed.
> 
> I can trivially work around this by saying that the generic GPUVM object has a different lifetime than the amdgpu specific object, but that opens up doors for use after free again.

If your driver never touches the VM's reference count and exits the VM with a clean state
(no mappings and no VM_BOs left), effectively, this is the same as having no reference
count.

In the very worst case you could argue that we trade a potential UAF *and* memroy leak
(no reference count) with *only* a memory leak (with reference count), which to me seems
reasonable.

> 
> Regards,
> Christian.
> 
>>> Thanks,
>>> Christian.
>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>>
>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>> ---
>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    	gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>    	INIT_LIST_HEAD(&gpuvm->rb.list);
>>>> +	kref_init(&gpuvm->kref);
>>>> +
>>>>    	gpuvm->name = name ? name : "unknown";
>>>>    	gpuvm->flags = flags;
>>>>    	gpuvm->ops = ops;
>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>> -/**
>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>> - *
>>>> - * Note that it is a bug to call this function on a manager that still
>>>> - * holds GPU VA mappings.
>>>> - */
>>>> -void
>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>> +static void
>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>    {
>>>>    	gpuvm->name = NULL;
>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>    	drm_gem_object_put(gpuvm->r_obj);
>>>>    }
>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>> +
>>>> +static void
>>>> +drm_gpuvm_free(struct kref *kref)
>>>> +{
>>>> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
>>>> +
>>>> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>> +		return;
>>>> +
>>>> +	drm_gpuvm_fini(gpuvm);
>>>> +
>>>> +	gpuvm->ops->vm_free(gpuvm);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>> + *
>>>> + * This releases a reference to @gpuvm.
>>>> + */
>>>> +void
>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	if (gpuvm)
>>>> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>    static int
>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>    	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>    		return -EINVAL;
>>>> -	return __drm_gpuva_insert(gpuvm, va);
>>>> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>    	}
>>>>    	__drm_gpuva_remove(va);
>>>> +	drm_gpuvm_put(va->vm);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> index 54be12c1272f..cb2f06565c46 100644
>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>>    	}
>>>>    }
>>>> +static void
>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>> +
>>>> +	kfree(uvmm);
>>>> +}
>>>> +
>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>> +	.vm_free = nouveau_uvmm_free,
>>>> +};
>>>> +
>>>>    int
>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    			   void *data,
>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    		       NOUVEAU_VA_SPACE_END,
>>>>    		       init->kernel_managed_addr,
>>>>    		       init->kernel_managed_size,
>>>> -		       NULL);
>>>> +		       &gpuvm_ops);
>>>>    	/* GPUVM takes care from here on. */
>>>>    	drm_gem_object_put(r_obj);
>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    	return 0;
>>>>    out_gpuvm_fini:
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    out_unlock:
>>>>    	mutex_unlock(&cli->mutex);
>>>>    	return ret;
>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>    	mutex_lock(&cli->mutex);
>>>>    	nouveau_vmm_fini(&uvmm->vmm);
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    	mutex_unlock(&cli->mutex);
>>>>    }
>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>> --- a/include/drm/drm_gpuvm.h
>>>> +++ b/include/drm/drm_gpuvm.h
>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>    		struct list_head list;
>>>>    	} rb;
>>>> +	/**
>>>> +	 * @kref: reference count of this object
>>>> +	 */
>>>> +	struct kref kref;
>>>> +
>>>>    	/**
>>>>    	 * @kernel_alloc_node:
>>>>    	 *
>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    		    u64 start_offset, u64 range,
>>>>    		    u64 reserve_offset, u64 reserve_range,
>>>>    		    const struct drm_gpuvm_ops *ops);
>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>> + *
>>>> + * This function acquires an additional reference to @gpuvm. It is illegal to
>>>> + * call this without already holding a reference. No locks required.
>>>> + */
>>>> +static inline struct drm_gpuvm *
>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	kref_get(&gpuvm->kref);
>>>> +
>>>> +	return gpuvm;
>>>> +}
>>>> +
>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>     * operations to drivers.
>>>>     */
>>>>    struct drm_gpuvm_ops {
>>>> +	/**
>>>> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
>>>> +	 * dropped
>>>> +	 *
>>>> +	 * This callback is mandatory.
>>>> +	 */
>>>> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>> +
>>>>    	/**
>>>>    	 * @op_alloc: called when the &drm_gpuvm allocates
>>>>    	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03 15:34           ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 15:34 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

On 11/3/23 15:04, Christian König wrote:
> Am 03.11.23 um 14:14 schrieb Danilo Krummrich:
>> On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
>>> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
>>>> Implement reference counting for struct drm_gpuvm.
>>>  From the design point of view what is that good for?
>> It was discussed in this thread [1].
>>
>> Essentially, the idea is to make sure that vm_bo->vm is always valid without the
>> driver having the need to take extra care. It also ensures that GPUVM can't be
>> freed with mappings still held.
> 
> Well in this case I have some objections to this. The lifetime of the VM is driver and use case specific.

That's fine, I don't see how this changes with a reference count.

> 
> Especially we most likely don't want the VM to live longer than the application which originally used it. If you make the GPUVM an independent object you actually open up driver abuse for the lifetime of this.

Right, we don't want that. But I don't see how the reference count prevents that.

Independant object is relative. struct drm_gpuvm is still embedded into a driver
specific structure. It's working the same way as with struct drm_gem_obejct.

> 
> Additional to that see below for a quite real problem with this.
> 
>>> Background is that the most common use case I see is that this object is
>>> embedded into something else and a reference count is then not really a good
>>> idea.
>> Do you have a specific use-case in mind where this would interfere?
> 
> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we initialize a temporary amdgpu VM for an in kernel unit test which runs during driver load.
> 
> When the function returns I need to guarantee that the VM is destroyed or otherwise I will mess up normal operation.

Nothing prevents that. The reference counting is well defined. If the driver did not
take additional references (which is clearly up to the driver taking care of) and all
VM_BOs and mappings are cleaned up, the reference count is guaranteed to be 1 at this
point.

Also note that if the driver would have not cleaned up all VM_BOs and mappings before
shutting down the VM, it would have been a bug anyways and the driver would potentially
leak memory and UAF issues.

Hence, If the VM is still alive at a point where you don't expect it to be, then it's
simply a driver bug.

> 
> Reference counting is nice when you don't know who else is referring to your VM, but the cost is that you also don't know when the object will guardedly be destroyed.
> 
> I can trivially work around this by saying that the generic GPUVM object has a different lifetime than the amdgpu specific object, but that opens up doors for use after free again.

If your driver never touches the VM's reference count and exits the VM with a clean state
(no mappings and no VM_BOs left), effectively, this is the same as having no reference
count.

In the very worst case you could argue that we trade a potential UAF *and* memroy leak
(no reference count) with *only* a memory leak (with reference count), which to me seems
reasonable.

> 
> Regards,
> Christian.
> 
>>> Thanks,
>>> Christian.
>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>>
>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>> ---
>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    	gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>    	INIT_LIST_HEAD(&gpuvm->rb.list);
>>>> +	kref_init(&gpuvm->kref);
>>>> +
>>>>    	gpuvm->name = name ? name : "unknown";
>>>>    	gpuvm->flags = flags;
>>>>    	gpuvm->ops = ops;
>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>> -/**
>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>> - *
>>>> - * Note that it is a bug to call this function on a manager that still
>>>> - * holds GPU VA mappings.
>>>> - */
>>>> -void
>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>> +static void
>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>    {
>>>>    	gpuvm->name = NULL;
>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>    	drm_gem_object_put(gpuvm->r_obj);
>>>>    }
>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>> +
>>>> +static void
>>>> +drm_gpuvm_free(struct kref *kref)
>>>> +{
>>>> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
>>>> +
>>>> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>> +		return;
>>>> +
>>>> +	drm_gpuvm_fini(gpuvm);
>>>> +
>>>> +	gpuvm->ops->vm_free(gpuvm);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>> + *
>>>> + * This releases a reference to @gpuvm.
>>>> + */
>>>> +void
>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	if (gpuvm)
>>>> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>    static int
>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>    	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>    		return -EINVAL;
>>>> -	return __drm_gpuva_insert(gpuvm, va);
>>>> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>    	}
>>>>    	__drm_gpuva_remove(va);
>>>> +	drm_gpuvm_put(va->vm);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> index 54be12c1272f..cb2f06565c46 100644
>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>>    	}
>>>>    }
>>>> +static void
>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>> +
>>>> +	kfree(uvmm);
>>>> +}
>>>> +
>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>> +	.vm_free = nouveau_uvmm_free,
>>>> +};
>>>> +
>>>>    int
>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    			   void *data,
>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    		       NOUVEAU_VA_SPACE_END,
>>>>    		       init->kernel_managed_addr,
>>>>    		       init->kernel_managed_size,
>>>> -		       NULL);
>>>> +		       &gpuvm_ops);
>>>>    	/* GPUVM takes care from here on. */
>>>>    	drm_gem_object_put(r_obj);
>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    	return 0;
>>>>    out_gpuvm_fini:
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    out_unlock:
>>>>    	mutex_unlock(&cli->mutex);
>>>>    	return ret;
>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>    	mutex_lock(&cli->mutex);
>>>>    	nouveau_vmm_fini(&uvmm->vmm);
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    	mutex_unlock(&cli->mutex);
>>>>    }
>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>> --- a/include/drm/drm_gpuvm.h
>>>> +++ b/include/drm/drm_gpuvm.h
>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>    		struct list_head list;
>>>>    	} rb;
>>>> +	/**
>>>> +	 * @kref: reference count of this object
>>>> +	 */
>>>> +	struct kref kref;
>>>> +
>>>>    	/**
>>>>    	 * @kernel_alloc_node:
>>>>    	 *
>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    		    u64 start_offset, u64 range,
>>>>    		    u64 reserve_offset, u64 reserve_range,
>>>>    		    const struct drm_gpuvm_ops *ops);
>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>> + *
>>>> + * This function acquires an additional reference to @gpuvm. It is illegal to
>>>> + * call this without already holding a reference. No locks required.
>>>> + */
>>>> +static inline struct drm_gpuvm *
>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	kref_get(&gpuvm->kref);
>>>> +
>>>> +	return gpuvm;
>>>> +}
>>>> +
>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>     * operations to drivers.
>>>>     */
>>>>    struct drm_gpuvm_ops {
>>>> +	/**
>>>> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
>>>> +	 * dropped
>>>> +	 *
>>>> +	 * This callback is mandatory.
>>>> +	 */
>>>> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>> +
>>>>    	/**
>>>>    	 * @op_alloc: called when the &drm_gpuvm allocates
>>>>    	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-03 15:34           ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-03 15:34 UTC (permalink / raw)
  To: Christian König
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

On 11/3/23 15:04, Christian König wrote:
> Am 03.11.23 um 14:14 schrieb Danilo Krummrich:
>> On Fri, Nov 03, 2023 at 08:18:35AM +0100, Christian König wrote:
>>> Am 02.11.23 um 00:31 schrieb Danilo Krummrich:
>>>> Implement reference counting for struct drm_gpuvm.
>>>  From the design point of view what is that good for?
>> It was discussed in this thread [1].
>>
>> Essentially, the idea is to make sure that vm_bo->vm is always valid without the
>> driver having the need to take extra care. It also ensures that GPUVM can't be
>> freed with mappings still held.
> 
> Well in this case I have some objections to this. The lifetime of the VM is driver and use case specific.

That's fine, I don't see how this changes with a reference count.

> 
> Especially we most likely don't want the VM to live longer than the application which originally used it. If you make the GPUVM an independent object you actually open up driver abuse for the lifetime of this.

Right, we don't want that. But I don't see how the reference count prevents that.

Independant object is relative. struct drm_gpuvm is still embedded into a driver
specific structure. It's working the same way as with struct drm_gem_obejct.

> 
> Additional to that see below for a quite real problem with this.
> 
>>> Background is that the most common use case I see is that this object is
>>> embedded into something else and a reference count is then not really a good
>>> idea.
>> Do you have a specific use-case in mind where this would interfere?
> 
> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we initialize a temporary amdgpu VM for an in kernel unit test which runs during driver load.
> 
> When the function returns I need to guarantee that the VM is destroyed or otherwise I will mess up normal operation.

Nothing prevents that. The reference counting is well defined. If the driver did not
take additional references (which is clearly up to the driver taking care of) and all
VM_BOs and mappings are cleaned up, the reference count is guaranteed to be 1 at this
point.

Also note that if the driver would have not cleaned up all VM_BOs and mappings before
shutting down the VM, it would have been a bug anyways and the driver would potentially
leak memory and UAF issues.

Hence, If the VM is still alive at a point where you don't expect it to be, then it's
simply a driver bug.

> 
> Reference counting is nice when you don't know who else is referring to your VM, but the cost is that you also don't know when the object will guardedly be destroyed.
> 
> I can trivially work around this by saying that the generic GPUVM object has a different lifetime than the amdgpu specific object, but that opens up doors for use after free again.

If your driver never touches the VM's reference count and exits the VM with a clean state
(no mappings and no VM_BOs left), effectively, this is the same as having no reference
count.

In the very worst case you could argue that we trade a potential UAF *and* memroy leak
(no reference count) with *only* a memory leak (with reference count), which to me seems
reasonable.

> 
> Regards,
> Christian.
> 
>>> Thanks,
>>> Christian.
>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>>
>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>> ---
>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 +++++++++++++++++++-------
>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    	gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>    	INIT_LIST_HEAD(&gpuvm->rb.list);
>>>> +	kref_init(&gpuvm->kref);
>>>> +
>>>>    	gpuvm->name = name ? name : "unknown";
>>>>    	gpuvm->flags = flags;
>>>>    	gpuvm->ops = ops;
>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>> -/**
>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>> - *
>>>> - * Note that it is a bug to call this function on a manager that still
>>>> - * holds GPU VA mappings.
>>>> - */
>>>> -void
>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>> +static void
>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>    {
>>>>    	gpuvm->name = NULL;
>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>    	drm_gem_object_put(gpuvm->r_obj);
>>>>    }
>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>> +
>>>> +static void
>>>> +drm_gpuvm_free(struct kref *kref)
>>>> +{
>>>> +	struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
>>>> +
>>>> +	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>> +		return;
>>>> +
>>>> +	drm_gpuvm_fini(gpuvm);
>>>> +
>>>> +	gpuvm->ops->vm_free(gpuvm);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>> + *
>>>> + * This releases a reference to @gpuvm.
>>>> + */
>>>> +void
>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	if (gpuvm)
>>>> +		kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>    static int
>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>    	if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>    		return -EINVAL;
>>>> -	return __drm_gpuva_insert(gpuvm, va);
>>>> +	return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>    	}
>>>>    	__drm_gpuva_remove(va);
>>>> +	drm_gpuvm_put(va->vm);
>>>>    }
>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> index 54be12c1272f..cb2f06565c46 100644
>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>>    	}
>>>>    }
>>>> +static void
>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>> +
>>>> +	kfree(uvmm);
>>>> +}
>>>> +
>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>> +	.vm_free = nouveau_uvmm_free,
>>>> +};
>>>> +
>>>>    int
>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    			   void *data,
>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    		       NOUVEAU_VA_SPACE_END,
>>>>    		       init->kernel_managed_addr,
>>>>    		       init->kernel_managed_size,
>>>> -		       NULL);
>>>> +		       &gpuvm_ops);
>>>>    	/* GPUVM takes care from here on. */
>>>>    	drm_gem_object_put(r_obj);
>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>    	return 0;
>>>>    out_gpuvm_fini:
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    out_unlock:
>>>>    	mutex_unlock(&cli->mutex);
>>>>    	return ret;
>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>    	mutex_lock(&cli->mutex);
>>>>    	nouveau_vmm_fini(&uvmm->vmm);
>>>> -	drm_gpuvm_destroy(&uvmm->base);
>>>> -	kfree(uvmm);
>>>> +	drm_gpuvm_put(&uvmm->base);
>>>>    	mutex_unlock(&cli->mutex);
>>>>    }
>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>> --- a/include/drm/drm_gpuvm.h
>>>> +++ b/include/drm/drm_gpuvm.h
>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>    		struct list_head list;
>>>>    	} rb;
>>>> +	/**
>>>> +	 * @kref: reference count of this object
>>>> +	 */
>>>> +	struct kref kref;
>>>> +
>>>>    	/**
>>>>    	 * @kernel_alloc_node:
>>>>    	 *
>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>>    		    u64 start_offset, u64 range,
>>>>    		    u64 reserve_offset, u64 reserve_range,
>>>>    		    const struct drm_gpuvm_ops *ops);
>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>> + *
>>>> + * This function acquires an additional reference to @gpuvm. It is illegal to
>>>> + * call this without already holding a reference. No locks required.
>>>> + */
>>>> +static inline struct drm_gpuvm *
>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>> +{
>>>> +	kref_get(&gpuvm->kref);
>>>> +
>>>> +	return gpuvm;
>>>> +}
>>>> +
>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
>>>> @@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>     * operations to drivers.
>>>>     */
>>>>    struct drm_gpuvm_ops {
>>>> +	/**
>>>> +	 * @vm_free: called when the last reference of a struct drm_gpuvm is
>>>> +	 * dropped
>>>> +	 *
>>>> +	 * This callback is mandatory.
>>>> +	 */
>>>> +	void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>> +
>>>>    	/**
>>>>    	 * @op_alloc: called when the &drm_gpuvm allocates
>>>>    	 * a struct drm_gpuva_op
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-03 15:34           ` Danilo Krummrich
  (?)
@ 2023-11-06  9:14             ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06  9:14 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
[SNIP]
>>
>> Especially we most likely don't want the VM to live longer than the 
>> application which originally used it. If you make the GPUVM an 
>> independent object you actually open up driver abuse for the lifetime 
>> of this.
>
> Right, we don't want that. But I don't see how the reference count 
> prevents that.

It doesn't prevents that, it's just not the most defensive approach.

>
> Independant object is relative. struct drm_gpuvm is still embedded 
> into a driver
> specific structure. It's working the same way as with struct 
> drm_gem_obejct.
>
>>
>> Additional to that see below for a quite real problem with this.
>>
>>>> Background is that the most common use case I see is that this 
>>>> object is
>>>> embedded into something else and a reference count is then not 
>>>> really a good
>>>> idea.
>>> Do you have a specific use-case in mind where this would interfere?
>>
>> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we 
>> initialize a temporary amdgpu VM for an in kernel unit test which 
>> runs during driver load.
>>
>> When the function returns I need to guarantee that the VM is 
>> destroyed or otherwise I will mess up normal operation.
>
> Nothing prevents that. The reference counting is well defined. If the 
> driver did not
> take additional references (which is clearly up to the driver taking 
> care of) and all
> VM_BOs and mappings are cleaned up, the reference count is guaranteed 
> to be 1 at this
> point.
>
> Also note that if the driver would have not cleaned up all VM_BOs and 
> mappings before
> shutting down the VM, it would have been a bug anyways and the driver 
> would potentially
> leak memory and UAF issues.

Exactly that's what I'm talking about why I think this is an extremely 
bad idea.

It's a perfect normal operation to shutdown the VM while there are still 
mappings. This is just what happens when you kill an application.

Because of this the mapping should *never* have a reference to the VM, 
but rather the VM destroys all mapping when it is destroyed itself.

> Hence, If the VM is still alive at a point where you don't expect it 
> to be, then it's
> simply a driver bug.

Driver bugs is just what I try to prevent here. When individual mappings 
keep the VM structure alive then drivers are responsible to clean them 
up, if the VM cleans up after itself then we don't need to worry about 
it in the driver.

When the mapping is destroyed with the VM drivers can't mess this common 
operation up. That's why this is more defensive.

What is a possible requirement is that external code needs to keep 
references to the VM, but *never* the VM to itself through the mappings. 
I would consider that a major bug in the component.

Regards,
Christian.

>
>>
>> Reference counting is nice when you don't know who else is referring 
>> to your VM, but the cost is that you also don't know when the object 
>> will guardedly be destroyed.
>>
>> I can trivially work around this by saying that the generic GPUVM 
>> object has a different lifetime than the amdgpu specific object, but 
>> that opens up doors for use after free again.
>
> If your driver never touches the VM's reference count and exits the VM 
> with a clean state
> (no mappings and no VM_BOs left), effectively, this is the same as 
> having no reference
> count.
>
> In the very worst case you could argue that we trade a potential UAF 
> *and* memroy leak
> (no reference count) with *only* a memory leak (with reference count), 
> which to me seems
> reasonable.
>
>>
>> Regards,
>> Christian.
>>
>>>> Thanks,
>>>> Christian.
>>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/ 
>>>
>>>
>>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>>> ---
>>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 
>>>>> +++++++++++++++++++-------
>>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c 
>>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>        gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>>        INIT_LIST_HEAD(&gpuvm->rb.list);
>>>>> +    kref_init(&gpuvm->kref);
>>>>> +
>>>>>        gpuvm->name = name ? name : "unknown";
>>>>>        gpuvm->flags = flags;
>>>>>        gpuvm->ops = ops;
>>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>>> -/**
>>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>>> - *
>>>>> - * Note that it is a bug to call this function on a manager that 
>>>>> still
>>>>> - * holds GPU VA mappings.
>>>>> - */
>>>>> -void
>>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>> +static void
>>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>>    {
>>>>>        gpuvm->name = NULL;
>>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>        drm_gem_object_put(gpuvm->r_obj);
>>>>>    }
>>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>> +
>>>>> +static void
>>>>> +drm_gpuvm_free(struct kref *kref)
>>>>> +{
>>>>> +    struct drm_gpuvm *gpuvm = container_of(kref, struct 
>>>>> drm_gpuvm, kref);
>>>>> +
>>>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>>> +        return;
>>>>> +
>>>>> +    drm_gpuvm_fini(gpuvm);
>>>>> +
>>>>> +    gpuvm->ops->vm_free(gpuvm);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>>> + *
>>>>> + * This releases a reference to @gpuvm.
>>>>> + */
>>>>> +void
>>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    if (gpuvm)
>>>>> +        kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>>    static int
>>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>>            return -EINVAL;
>>>>> -    return __drm_gpuva_insert(gpuvm, va);
>>>>> +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>>        }
>>>>>        __drm_gpuva_remove(va);
>>>>> +    drm_gpuvm_put(va->vm);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c 
>>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> index 54be12c1272f..cb2f06565c46 100644
>>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo 
>>>>> *nvbo)
>>>>>        }
>>>>>    }
>>>>> +static void
>>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>>> +
>>>>> +    kfree(uvmm);
>>>>> +}
>>>>> +
>>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>>> +    .vm_free = nouveau_uvmm_free,
>>>>> +};
>>>>> +
>>>>>    int
>>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>>                   void *data,
>>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>                   NOUVEAU_VA_SPACE_END,
>>>>>                   init->kernel_managed_addr,
>>>>>                   init->kernel_managed_size,
>>>>> -               NULL);
>>>>> +               &gpuvm_ops);
>>>>>        /* GPUVM takes care from here on. */
>>>>>        drm_gem_object_put(r_obj);
>>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>        return 0;
>>>>>    out_gpuvm_fini:
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>    out_unlock:
>>>>>        mutex_unlock(&cli->mutex);
>>>>>        return ret;
>>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>>        mutex_lock(&cli->mutex);
>>>>>        nouveau_vmm_fini(&uvmm->vmm);
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>        mutex_unlock(&cli->mutex);
>>>>>    }
>>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>>> --- a/include/drm/drm_gpuvm.h
>>>>> +++ b/include/drm/drm_gpuvm.h
>>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>>            struct list_head list;
>>>>>        } rb;
>>>>> +    /**
>>>>> +     * @kref: reference count of this object
>>>>> +     */
>>>>> +    struct kref kref;
>>>>> +
>>>>>        /**
>>>>>         * @kernel_alloc_node:
>>>>>         *
>>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, 
>>>>> const char *name,
>>>>>                u64 start_offset, u64 range,
>>>>>                u64 reserve_offset, u64 reserve_range,
>>>>>                const struct drm_gpuvm_ops *ops);
>>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>>> + *
>>>>> + * This function acquires an additional reference to @gpuvm. It 
>>>>> is illegal to
>>>>> + * call this without already holding a reference. No locks required.
>>>>> + */
>>>>> +static inline struct drm_gpuvm *
>>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    kref_get(&gpuvm->kref);
>>>>> +
>>>>> +    return gpuvm;
>>>>> +}
>>>>> +
>>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, 
>>>>> u64 range);
>>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 
>>>>> addr, u64 range);
>>>>> @@ -673,6 +694,14 @@ static inline void 
>>>>> drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>>     * operations to drivers.
>>>>>     */
>>>>>    struct drm_gpuvm_ops {
>>>>> +    /**
>>>>> +     * @vm_free: called when the last reference of a struct 
>>>>> drm_gpuvm is
>>>>> +     * dropped
>>>>> +     *
>>>>> +     * This callback is mandatory.
>>>>> +     */
>>>>> +    void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>>        /**
>>>>>         * @op_alloc: called when the &drm_gpuvm allocates
>>>>>         * a struct drm_gpuva_op
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06  9:14             ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06  9:14 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
[SNIP]
>>
>> Especially we most likely don't want the VM to live longer than the 
>> application which originally used it. If you make the GPUVM an 
>> independent object you actually open up driver abuse for the lifetime 
>> of this.
>
> Right, we don't want that. But I don't see how the reference count 
> prevents that.

It doesn't prevents that, it's just not the most defensive approach.

>
> Independant object is relative. struct drm_gpuvm is still embedded 
> into a driver
> specific structure. It's working the same way as with struct 
> drm_gem_obejct.
>
>>
>> Additional to that see below for a quite real problem with this.
>>
>>>> Background is that the most common use case I see is that this 
>>>> object is
>>>> embedded into something else and a reference count is then not 
>>>> really a good
>>>> idea.
>>> Do you have a specific use-case in mind where this would interfere?
>>
>> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we 
>> initialize a temporary amdgpu VM for an in kernel unit test which 
>> runs during driver load.
>>
>> When the function returns I need to guarantee that the VM is 
>> destroyed or otherwise I will mess up normal operation.
>
> Nothing prevents that. The reference counting is well defined. If the 
> driver did not
> take additional references (which is clearly up to the driver taking 
> care of) and all
> VM_BOs and mappings are cleaned up, the reference count is guaranteed 
> to be 1 at this
> point.
>
> Also note that if the driver would have not cleaned up all VM_BOs and 
> mappings before
> shutting down the VM, it would have been a bug anyways and the driver 
> would potentially
> leak memory and UAF issues.

Exactly that's what I'm talking about why I think this is an extremely 
bad idea.

It's a perfect normal operation to shutdown the VM while there are still 
mappings. This is just what happens when you kill an application.

Because of this the mapping should *never* have a reference to the VM, 
but rather the VM destroys all mapping when it is destroyed itself.

> Hence, If the VM is still alive at a point where you don't expect it 
> to be, then it's
> simply a driver bug.

Driver bugs is just what I try to prevent here. When individual mappings 
keep the VM structure alive then drivers are responsible to clean them 
up, if the VM cleans up after itself then we don't need to worry about 
it in the driver.

When the mapping is destroyed with the VM drivers can't mess this common 
operation up. That's why this is more defensive.

What is a possible requirement is that external code needs to keep 
references to the VM, but *never* the VM to itself through the mappings. 
I would consider that a major bug in the component.

Regards,
Christian.

>
>>
>> Reference counting is nice when you don't know who else is referring 
>> to your VM, but the cost is that you also don't know when the object 
>> will guardedly be destroyed.
>>
>> I can trivially work around this by saying that the generic GPUVM 
>> object has a different lifetime than the amdgpu specific object, but 
>> that opens up doors for use after free again.
>
> If your driver never touches the VM's reference count and exits the VM 
> with a clean state
> (no mappings and no VM_BOs left), effectively, this is the same as 
> having no reference
> count.
>
> In the very worst case you could argue that we trade a potential UAF 
> *and* memroy leak
> (no reference count) with *only* a memory leak (with reference count), 
> which to me seems
> reasonable.
>
>>
>> Regards,
>> Christian.
>>
>>>> Thanks,
>>>> Christian.
>>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/ 
>>>
>>>
>>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>>> ---
>>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 
>>>>> +++++++++++++++++++-------
>>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c 
>>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>        gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>>        INIT_LIST_HEAD(&gpuvm->rb.list);
>>>>> +    kref_init(&gpuvm->kref);
>>>>> +
>>>>>        gpuvm->name = name ? name : "unknown";
>>>>>        gpuvm->flags = flags;
>>>>>        gpuvm->ops = ops;
>>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>>> -/**
>>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>>> - *
>>>>> - * Note that it is a bug to call this function on a manager that 
>>>>> still
>>>>> - * holds GPU VA mappings.
>>>>> - */
>>>>> -void
>>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>> +static void
>>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>>    {
>>>>>        gpuvm->name = NULL;
>>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>        drm_gem_object_put(gpuvm->r_obj);
>>>>>    }
>>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>> +
>>>>> +static void
>>>>> +drm_gpuvm_free(struct kref *kref)
>>>>> +{
>>>>> +    struct drm_gpuvm *gpuvm = container_of(kref, struct 
>>>>> drm_gpuvm, kref);
>>>>> +
>>>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>>> +        return;
>>>>> +
>>>>> +    drm_gpuvm_fini(gpuvm);
>>>>> +
>>>>> +    gpuvm->ops->vm_free(gpuvm);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>>> + *
>>>>> + * This releases a reference to @gpuvm.
>>>>> + */
>>>>> +void
>>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    if (gpuvm)
>>>>> +        kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>>    static int
>>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>>            return -EINVAL;
>>>>> -    return __drm_gpuva_insert(gpuvm, va);
>>>>> +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>>        }
>>>>>        __drm_gpuva_remove(va);
>>>>> +    drm_gpuvm_put(va->vm);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c 
>>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> index 54be12c1272f..cb2f06565c46 100644
>>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo 
>>>>> *nvbo)
>>>>>        }
>>>>>    }
>>>>> +static void
>>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>>> +
>>>>> +    kfree(uvmm);
>>>>> +}
>>>>> +
>>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>>> +    .vm_free = nouveau_uvmm_free,
>>>>> +};
>>>>> +
>>>>>    int
>>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>>                   void *data,
>>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>                   NOUVEAU_VA_SPACE_END,
>>>>>                   init->kernel_managed_addr,
>>>>>                   init->kernel_managed_size,
>>>>> -               NULL);
>>>>> +               &gpuvm_ops);
>>>>>        /* GPUVM takes care from here on. */
>>>>>        drm_gem_object_put(r_obj);
>>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>        return 0;
>>>>>    out_gpuvm_fini:
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>    out_unlock:
>>>>>        mutex_unlock(&cli->mutex);
>>>>>        return ret;
>>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>>        mutex_lock(&cli->mutex);
>>>>>        nouveau_vmm_fini(&uvmm->vmm);
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>        mutex_unlock(&cli->mutex);
>>>>>    }
>>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>>> --- a/include/drm/drm_gpuvm.h
>>>>> +++ b/include/drm/drm_gpuvm.h
>>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>>            struct list_head list;
>>>>>        } rb;
>>>>> +    /**
>>>>> +     * @kref: reference count of this object
>>>>> +     */
>>>>> +    struct kref kref;
>>>>> +
>>>>>        /**
>>>>>         * @kernel_alloc_node:
>>>>>         *
>>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, 
>>>>> const char *name,
>>>>>                u64 start_offset, u64 range,
>>>>>                u64 reserve_offset, u64 reserve_range,
>>>>>                const struct drm_gpuvm_ops *ops);
>>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>>> + *
>>>>> + * This function acquires an additional reference to @gpuvm. It 
>>>>> is illegal to
>>>>> + * call this without already holding a reference. No locks required.
>>>>> + */
>>>>> +static inline struct drm_gpuvm *
>>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    kref_get(&gpuvm->kref);
>>>>> +
>>>>> +    return gpuvm;
>>>>> +}
>>>>> +
>>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, 
>>>>> u64 range);
>>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 
>>>>> addr, u64 range);
>>>>> @@ -673,6 +694,14 @@ static inline void 
>>>>> drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>>     * operations to drivers.
>>>>>     */
>>>>>    struct drm_gpuvm_ops {
>>>>> +    /**
>>>>> +     * @vm_free: called when the last reference of a struct 
>>>>> drm_gpuvm is
>>>>> +     * dropped
>>>>> +     *
>>>>> +     * This callback is mandatory.
>>>>> +     */
>>>>> +    void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>>        /**
>>>>>         * @op_alloc: called when the &drm_gpuvm allocates
>>>>>         * a struct drm_gpuva_op
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06  9:14             ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06  9:14 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
[SNIP]
>>
>> Especially we most likely don't want the VM to live longer than the 
>> application which originally used it. If you make the GPUVM an 
>> independent object you actually open up driver abuse for the lifetime 
>> of this.
>
> Right, we don't want that. But I don't see how the reference count 
> prevents that.

It doesn't prevents that, it's just not the most defensive approach.

>
> Independant object is relative. struct drm_gpuvm is still embedded 
> into a driver
> specific structure. It's working the same way as with struct 
> drm_gem_obejct.
>
>>
>> Additional to that see below for a quite real problem with this.
>>
>>>> Background is that the most common use case I see is that this 
>>>> object is
>>>> embedded into something else and a reference count is then not 
>>>> really a good
>>>> idea.
>>> Do you have a specific use-case in mind where this would interfere?
>>
>> Yes, absolutely. For an example see amdgpu_mes_self_test(), here we 
>> initialize a temporary amdgpu VM for an in kernel unit test which 
>> runs during driver load.
>>
>> When the function returns I need to guarantee that the VM is 
>> destroyed or otherwise I will mess up normal operation.
>
> Nothing prevents that. The reference counting is well defined. If the 
> driver did not
> take additional references (which is clearly up to the driver taking 
> care of) and all
> VM_BOs and mappings are cleaned up, the reference count is guaranteed 
> to be 1 at this
> point.
>
> Also note that if the driver would have not cleaned up all VM_BOs and 
> mappings before
> shutting down the VM, it would have been a bug anyways and the driver 
> would potentially
> leak memory and UAF issues.

Exactly that's what I'm talking about why I think this is an extremely 
bad idea.

It's a perfect normal operation to shutdown the VM while there are still 
mappings. This is just what happens when you kill an application.

Because of this the mapping should *never* have a reference to the VM, 
but rather the VM destroys all mapping when it is destroyed itself.

> Hence, If the VM is still alive at a point where you don't expect it 
> to be, then it's
> simply a driver bug.

Driver bugs is just what I try to prevent here. When individual mappings 
keep the VM structure alive then drivers are responsible to clean them 
up, if the VM cleans up after itself then we don't need to worry about 
it in the driver.

When the mapping is destroyed with the VM drivers can't mess this common 
operation up. That's why this is more defensive.

What is a possible requirement is that external code needs to keep 
references to the VM, but *never* the VM to itself through the mappings. 
I would consider that a major bug in the component.

Regards,
Christian.

>
>>
>> Reference counting is nice when you don't know who else is referring 
>> to your VM, but the cost is that you also don't know when the object 
>> will guardedly be destroyed.
>>
>> I can trivially work around this by saying that the generic GPUVM 
>> object has a different lifetime than the amdgpu specific object, but 
>> that opens up doors for use after free again.
>
> If your driver never touches the VM's reference count and exits the VM 
> with a clean state
> (no mappings and no VM_BOs left), effectively, this is the same as 
> having no reference
> count.
>
> In the very worst case you could argue that we trade a potential UAF 
> *and* memroy leak
> (no reference count) with *only* a memory leak (with reference count), 
> which to me seems
> reasonable.
>
>>
>> Regards,
>> Christian.
>>
>>>> Thanks,
>>>> Christian.
>>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/ 
>>>
>>>
>>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>>> ---
>>>>>    drivers/gpu/drm/drm_gpuvm.c            | 44 
>>>>> +++++++++++++++++++-------
>>>>>    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>>    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>>    3 files changed, 78 insertions(+), 17 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c 
>>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>        gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>>        INIT_LIST_HEAD(&gpuvm->rb.list);
>>>>> +    kref_init(&gpuvm->kref);
>>>>> +
>>>>>        gpuvm->name = name ? name : "unknown";
>>>>>        gpuvm->flags = flags;
>>>>>        gpuvm->ops = ops;
>>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const 
>>>>> char *name,
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>>> -/**
>>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>>> - *
>>>>> - * Note that it is a bug to call this function on a manager that 
>>>>> still
>>>>> - * holds GPU VA mappings.
>>>>> - */
>>>>> -void
>>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>> +static void
>>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>>    {
>>>>>        gpuvm->name = NULL;
>>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>        drm_gem_object_put(gpuvm->r_obj);
>>>>>    }
>>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>> +
>>>>> +static void
>>>>> +drm_gpuvm_free(struct kref *kref)
>>>>> +{
>>>>> +    struct drm_gpuvm *gpuvm = container_of(kref, struct 
>>>>> drm_gpuvm, kref);
>>>>> +
>>>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>>> +        return;
>>>>> +
>>>>> +    drm_gpuvm_fini(gpuvm);
>>>>> +
>>>>> +    gpuvm->ops->vm_free(gpuvm);
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>>> + *
>>>>> + * This releases a reference to @gpuvm.
>>>>> + */
>>>>> +void
>>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    if (gpuvm)
>>>>> +        kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>>    static int
>>>>>    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>>            return -EINVAL;
>>>>> -    return __drm_gpuva_insert(gpuvm, va);
>>>>> +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>>        }
>>>>>        __drm_gpuva_remove(va);
>>>>> +    drm_gpuvm_put(va->vm);
>>>>>    }
>>>>>    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c 
>>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> index 54be12c1272f..cb2f06565c46 100644
>>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo 
>>>>> *nvbo)
>>>>>        }
>>>>>    }
>>>>> +static void
>>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>>> +
>>>>> +    kfree(uvmm);
>>>>> +}
>>>>> +
>>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>>> +    .vm_free = nouveau_uvmm_free,
>>>>> +};
>>>>> +
>>>>>    int
>>>>>    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>>                   void *data,
>>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>                   NOUVEAU_VA_SPACE_END,
>>>>>                   init->kernel_managed_addr,
>>>>>                   init->kernel_managed_size,
>>>>> -               NULL);
>>>>> +               &gpuvm_ops);
>>>>>        /* GPUVM takes care from here on. */
>>>>>        drm_gem_object_put(r_obj);
>>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device 
>>>>> *dev,
>>>>>        return 0;
>>>>>    out_gpuvm_fini:
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>    out_unlock:
>>>>>        mutex_unlock(&cli->mutex);
>>>>>        return ret;
>>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>>        mutex_lock(&cli->mutex);
>>>>>        nouveau_vmm_fini(&uvmm->vmm);
>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>> -    kfree(uvmm);
>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>        mutex_unlock(&cli->mutex);
>>>>>    }
>>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>>> --- a/include/drm/drm_gpuvm.h
>>>>> +++ b/include/drm/drm_gpuvm.h
>>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>>            struct list_head list;
>>>>>        } rb;
>>>>> +    /**
>>>>> +     * @kref: reference count of this object
>>>>> +     */
>>>>> +    struct kref kref;
>>>>> +
>>>>>        /**
>>>>>         * @kernel_alloc_node:
>>>>>         *
>>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, 
>>>>> const char *name,
>>>>>                u64 start_offset, u64 range,
>>>>>                u64 reserve_offset, u64 reserve_range,
>>>>>                const struct drm_gpuvm_ops *ops);
>>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>> +/**
>>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>>> + *
>>>>> + * This function acquires an additional reference to @gpuvm. It 
>>>>> is illegal to
>>>>> + * call this without already holding a reference. No locks required.
>>>>> + */
>>>>> +static inline struct drm_gpuvm *
>>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>>> +{
>>>>> +    kref_get(&gpuvm->kref);
>>>>> +
>>>>> +    return gpuvm;
>>>>> +}
>>>>> +
>>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>>    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, 
>>>>> u64 range);
>>>>>    bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 
>>>>> addr, u64 range);
>>>>> @@ -673,6 +694,14 @@ static inline void 
>>>>> drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>>     * operations to drivers.
>>>>>     */
>>>>>    struct drm_gpuvm_ops {
>>>>> +    /**
>>>>> +     * @vm_free: called when the last reference of a struct 
>>>>> drm_gpuvm is
>>>>> +     * dropped
>>>>> +     *
>>>>> +     * This callback is mandatory.
>>>>> +     */
>>>>> +    void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>>> +
>>>>>        /**
>>>>>         * @op_alloc: called when the &drm_gpuvm allocates
>>>>>         * a struct drm_gpuva_op
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06  9:14             ` [Nouveau] " Christian König
  (?)
@ 2023-11-06 12:16               ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 12:16 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

On Mon, Nov 06, 2023 at 10:14:29AM +0100, Christian König wrote:
> Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
> [SNIP]
> > > 
> > > Especially we most likely don't want the VM to live longer than the
> > > application which originally used it. If you make the GPUVM an
> > > independent object you actually open up driver abuse for the
> > > lifetime of this.
> > 
> > Right, we don't want that. But I don't see how the reference count
> > prevents that.
> 
> It doesn't prevents that, it's just not the most defensive approach.
> 
> > 
> > Independant object is relative. struct drm_gpuvm is still embedded into
> > a driver
> > specific structure. It's working the same way as with struct
> > drm_gem_obejct.
> > 
> > > 
> > > Additional to that see below for a quite real problem with this.
> > > 
> > > > > Background is that the most common use case I see is that
> > > > > this object is
> > > > > embedded into something else and a reference count is then
> > > > > not really a good
> > > > > idea.
> > > > Do you have a specific use-case in mind where this would interfere?
> > > 
> > > Yes, absolutely. For an example see amdgpu_mes_self_test(), here we
> > > initialize a temporary amdgpu VM for an in kernel unit test which
> > > runs during driver load.
> > > 
> > > When the function returns I need to guarantee that the VM is
> > > destroyed or otherwise I will mess up normal operation.
> > 
> > Nothing prevents that. The reference counting is well defined. If the
> > driver did not
> > take additional references (which is clearly up to the driver taking
> > care of) and all
> > VM_BOs and mappings are cleaned up, the reference count is guaranteed to
> > be 1 at this
> > point.
> > 
> > Also note that if the driver would have not cleaned up all VM_BOs and
> > mappings before
> > shutting down the VM, it would have been a bug anyways and the driver
> > would potentially
> > leak memory and UAF issues.
> 
> Exactly that's what I'm talking about why I think this is an extremely bad
> idea.
> 
> It's a perfect normal operation to shutdown the VM while there are still
> mappings. This is just what happens when you kill an application.

Shut down the VM in terms of removing existing mappings, doing driver specifc
cleanup operations, etc. But not freeing the VM structure yet. That's what you
gonna do after you cleaned up everything.

So, when your application gets killed, you just call your driver_vm_destroy()
function, cleaning up all mappings etc. and then you call drm_gpuvm_put(). And
if you did a decent job cleaning things up (removing existing mappings etc.)
this drm_gpuvm_put() call will result into the vm_free() callback being called.

This reference count just prevents that the VM is freed as long as other
ressources are attached to it that carry a VM pointer, such as mappings and
VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
paranoid, but it doesn't hurt either and keeps it consistant.

> 
> Because of this the mapping should *never* have a reference to the VM, but
> rather the VM destroys all mapping when it is destroyed itself.
> 
> > Hence, If the VM is still alive at a point where you don't expect it to
> > be, then it's
> > simply a driver bug.
> 
> Driver bugs is just what I try to prevent here. When individual mappings
> keep the VM structure alive then drivers are responsible to clean them up,
> if the VM cleans up after itself then we don't need to worry about it in the
> driver.

Drivers are *always* responsible for that. This has nothing to do with whether
the VM is reference counted or not. GPUVM can't clean up mappings after itself.
If the driver left mappings, GPUVM would just leak them without reference count.
It doesn't know about the drivers surrounding structures, nor does it know about
attached ressources such as PT(E)s. 

> 
> When the mapping is destroyed with the VM drivers can't mess this common
> operation up. That's why this is more defensive.
> 
> What is a possible requirement is that external code needs to keep
> references to the VM, but *never* the VM to itself through the mappings. I
> would consider that a major bug in the component.

Obviously, you just (want to) apply a different semantics to this reference
count. It is meant to reflect that the VM structure can be freed, instead of the
VM can be cleaned up. If you want to latter, you can have a driver specifc
reference count for that in the exact same way as it was before this patch.

> 
> Regards,
> Christian.
> 
> > 
> > > 
> > > Reference counting is nice when you don't know who else is referring
> > > to your VM, but the cost is that you also don't know when the object
> > > will guardedly be destroyed.
> > > 
> > > I can trivially work around this by saying that the generic GPUVM
> > > object has a different lifetime than the amdgpu specific object, but
> > > that opens up doors for use after free again.
> > 
> > If your driver never touches the VM's reference count and exits the VM
> > with a clean state
> > (no mappings and no VM_BOs left), effectively, this is the same as
> > having no reference
> > count.
> > 
> > In the very worst case you could argue that we trade a potential UAF
> > *and* memroy leak
> > (no reference count) with *only* a memory leak (with reference count),
> > which to me seems
> > reasonable.
> > 
> > > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Thanks,
> > > > > Christian.
> > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > 
> > > > 
> > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > ---
> > > > > >    drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > +++++++++++++++++++-------
> > > > > >    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > >    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > >    3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > 
> > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >        gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > >        INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > +    kref_init(&gpuvm->kref);
> > > > > > +
> > > > > >        gpuvm->name = name ? name : "unknown";
> > > > > >        gpuvm->flags = flags;
> > > > > >        gpuvm->ops = ops;
> > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > -/**
> > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > - *
> > > > > > - * Note that it is a bug to call this function on a
> > > > > > manager that still
> > > > > > - * holds GPU VA mappings.
> > > > > > - */
> > > > > > -void
> > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > +static void
> > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > >    {
> > > > > >        gpuvm->name = NULL;
> > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > >        drm_gem_object_put(gpuvm->r_obj);
> > > > > >    }
> > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > +
> > > > > > +static void
> > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > +{
> > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > drm_gpuvm, kref);
> > > > > > +
> > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > +        return;
> > > > > > +
> > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > +
> > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > +}
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > + *
> > > > > > + * This releases a reference to @gpuvm.
> > > > > > + */
> > > > > > +void
> > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    if (gpuvm)
> > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > +}
> > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > >    static int
> > > > > >    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > >        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > >            return -EINVAL;
> > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > >        }
> > > > > >        __drm_gpuva_remove(va);
> > > > > > +    drm_gpuvm_put(va->vm);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > nouveau_bo *nvbo)
> > > > > >        }
> > > > > >    }
> > > > > > +static void
> > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > +
> > > > > > +    kfree(uvmm);
> > > > > > +}
> > > > > > +
> > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > +};
> > > > > > +
> > > > > >    int
> > > > > >    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > >                   void *data,
> > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >                   NOUVEAU_VA_SPACE_END,
> > > > > >                   init->kernel_managed_addr,
> > > > > >                   init->kernel_managed_size,
> > > > > > -               NULL);
> > > > > > +               &gpuvm_ops);
> > > > > >        /* GPUVM takes care from here on. */
> > > > > >        drm_gem_object_put(r_obj);
> > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >        return 0;
> > > > > >    out_gpuvm_fini:
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >    out_unlock:
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >        return ret;
> > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > >        mutex_lock(&cli->mutex);
> > > > > >        nouveau_vmm_fini(&uvmm->vmm);
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >    }
> > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > >            struct list_head list;
> > > > > >        } rb;
> > > > > > +    /**
> > > > > > +     * @kref: reference count of this object
> > > > > > +     */
> > > > > > +    struct kref kref;
> > > > > > +
> > > > > >        /**
> > > > > >         * @kernel_alloc_node:
> > > > > >         *
> > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > >                u64 start_offset, u64 range,
> > > > > >                u64 reserve_offset, u64 reserve_range,
> > > > > >                const struct drm_gpuvm_ops *ops);
> > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > + *
> > > > > > + * This function acquires an additional reference to
> > > > > > @gpuvm. It is illegal to
> > > > > > + * call this without already holding a reference. No locks required.
> > > > > > + */
> > > > > > +static inline struct drm_gpuvm *
> > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    kref_get(&gpuvm->kref);
> > > > > > +
> > > > > > +    return gpuvm;
> > > > > > +}
> > > > > > +
> > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > >    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > u64 addr, u64 range);
> > > > > >    bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > >     * operations to drivers.
> > > > > >     */
> > > > > >    struct drm_gpuvm_ops {
> > > > > > +    /**
> > > > > > +     * @vm_free: called when the last reference of a
> > > > > > struct drm_gpuvm is
> > > > > > +     * dropped
> > > > > > +     *
> > > > > > +     * This callback is mandatory.
> > > > > > +     */
> > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > >        /**
> > > > > >         * @op_alloc: called when the &drm_gpuvm allocates
> > > > > >         * a struct drm_gpuva_op
> > > 
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 12:16               ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 12:16 UTC (permalink / raw)
  To: Christian König
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

On Mon, Nov 06, 2023 at 10:14:29AM +0100, Christian König wrote:
> Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
> [SNIP]
> > > 
> > > Especially we most likely don't want the VM to live longer than the
> > > application which originally used it. If you make the GPUVM an
> > > independent object you actually open up driver abuse for the
> > > lifetime of this.
> > 
> > Right, we don't want that. But I don't see how the reference count
> > prevents that.
> 
> It doesn't prevents that, it's just not the most defensive approach.
> 
> > 
> > Independant object is relative. struct drm_gpuvm is still embedded into
> > a driver
> > specific structure. It's working the same way as with struct
> > drm_gem_obejct.
> > 
> > > 
> > > Additional to that see below for a quite real problem with this.
> > > 
> > > > > Background is that the most common use case I see is that
> > > > > this object is
> > > > > embedded into something else and a reference count is then
> > > > > not really a good
> > > > > idea.
> > > > Do you have a specific use-case in mind where this would interfere?
> > > 
> > > Yes, absolutely. For an example see amdgpu_mes_self_test(), here we
> > > initialize a temporary amdgpu VM for an in kernel unit test which
> > > runs during driver load.
> > > 
> > > When the function returns I need to guarantee that the VM is
> > > destroyed or otherwise I will mess up normal operation.
> > 
> > Nothing prevents that. The reference counting is well defined. If the
> > driver did not
> > take additional references (which is clearly up to the driver taking
> > care of) and all
> > VM_BOs and mappings are cleaned up, the reference count is guaranteed to
> > be 1 at this
> > point.
> > 
> > Also note that if the driver would have not cleaned up all VM_BOs and
> > mappings before
> > shutting down the VM, it would have been a bug anyways and the driver
> > would potentially
> > leak memory and UAF issues.
> 
> Exactly that's what I'm talking about why I think this is an extremely bad
> idea.
> 
> It's a perfect normal operation to shutdown the VM while there are still
> mappings. This is just what happens when you kill an application.

Shut down the VM in terms of removing existing mappings, doing driver specifc
cleanup operations, etc. But not freeing the VM structure yet. That's what you
gonna do after you cleaned up everything.

So, when your application gets killed, you just call your driver_vm_destroy()
function, cleaning up all mappings etc. and then you call drm_gpuvm_put(). And
if you did a decent job cleaning things up (removing existing mappings etc.)
this drm_gpuvm_put() call will result into the vm_free() callback being called.

This reference count just prevents that the VM is freed as long as other
ressources are attached to it that carry a VM pointer, such as mappings and
VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
paranoid, but it doesn't hurt either and keeps it consistant.

> 
> Because of this the mapping should *never* have a reference to the VM, but
> rather the VM destroys all mapping when it is destroyed itself.
> 
> > Hence, If the VM is still alive at a point where you don't expect it to
> > be, then it's
> > simply a driver bug.
> 
> Driver bugs is just what I try to prevent here. When individual mappings
> keep the VM structure alive then drivers are responsible to clean them up,
> if the VM cleans up after itself then we don't need to worry about it in the
> driver.

Drivers are *always* responsible for that. This has nothing to do with whether
the VM is reference counted or not. GPUVM can't clean up mappings after itself.
If the driver left mappings, GPUVM would just leak them without reference count.
It doesn't know about the drivers surrounding structures, nor does it know about
attached ressources such as PT(E)s. 

> 
> When the mapping is destroyed with the VM drivers can't mess this common
> operation up. That's why this is more defensive.
> 
> What is a possible requirement is that external code needs to keep
> references to the VM, but *never* the VM to itself through the mappings. I
> would consider that a major bug in the component.

Obviously, you just (want to) apply a different semantics to this reference
count. It is meant to reflect that the VM structure can be freed, instead of the
VM can be cleaned up. If you want to latter, you can have a driver specifc
reference count for that in the exact same way as it was before this patch.

> 
> Regards,
> Christian.
> 
> > 
> > > 
> > > Reference counting is nice when you don't know who else is referring
> > > to your VM, but the cost is that you also don't know when the object
> > > will guardedly be destroyed.
> > > 
> > > I can trivially work around this by saying that the generic GPUVM
> > > object has a different lifetime than the amdgpu specific object, but
> > > that opens up doors for use after free again.
> > 
> > If your driver never touches the VM's reference count and exits the VM
> > with a clean state
> > (no mappings and no VM_BOs left), effectively, this is the same as
> > having no reference
> > count.
> > 
> > In the very worst case you could argue that we trade a potential UAF
> > *and* memroy leak
> > (no reference count) with *only* a memory leak (with reference count),
> > which to me seems
> > reasonable.
> > 
> > > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Thanks,
> > > > > Christian.
> > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > 
> > > > 
> > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > ---
> > > > > >    drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > +++++++++++++++++++-------
> > > > > >    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > >    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > >    3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > 
> > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >        gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > >        INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > +    kref_init(&gpuvm->kref);
> > > > > > +
> > > > > >        gpuvm->name = name ? name : "unknown";
> > > > > >        gpuvm->flags = flags;
> > > > > >        gpuvm->ops = ops;
> > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > -/**
> > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > - *
> > > > > > - * Note that it is a bug to call this function on a
> > > > > > manager that still
> > > > > > - * holds GPU VA mappings.
> > > > > > - */
> > > > > > -void
> > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > +static void
> > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > >    {
> > > > > >        gpuvm->name = NULL;
> > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > >        drm_gem_object_put(gpuvm->r_obj);
> > > > > >    }
> > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > +
> > > > > > +static void
> > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > +{
> > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > drm_gpuvm, kref);
> > > > > > +
> > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > +        return;
> > > > > > +
> > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > +
> > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > +}
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > + *
> > > > > > + * This releases a reference to @gpuvm.
> > > > > > + */
> > > > > > +void
> > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    if (gpuvm)
> > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > +}
> > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > >    static int
> > > > > >    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > >        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > >            return -EINVAL;
> > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > >        }
> > > > > >        __drm_gpuva_remove(va);
> > > > > > +    drm_gpuvm_put(va->vm);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > nouveau_bo *nvbo)
> > > > > >        }
> > > > > >    }
> > > > > > +static void
> > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > +
> > > > > > +    kfree(uvmm);
> > > > > > +}
> > > > > > +
> > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > +};
> > > > > > +
> > > > > >    int
> > > > > >    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > >                   void *data,
> > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >                   NOUVEAU_VA_SPACE_END,
> > > > > >                   init->kernel_managed_addr,
> > > > > >                   init->kernel_managed_size,
> > > > > > -               NULL);
> > > > > > +               &gpuvm_ops);
> > > > > >        /* GPUVM takes care from here on. */
> > > > > >        drm_gem_object_put(r_obj);
> > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >        return 0;
> > > > > >    out_gpuvm_fini:
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >    out_unlock:
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >        return ret;
> > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > >        mutex_lock(&cli->mutex);
> > > > > >        nouveau_vmm_fini(&uvmm->vmm);
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >    }
> > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > >            struct list_head list;
> > > > > >        } rb;
> > > > > > +    /**
> > > > > > +     * @kref: reference count of this object
> > > > > > +     */
> > > > > > +    struct kref kref;
> > > > > > +
> > > > > >        /**
> > > > > >         * @kernel_alloc_node:
> > > > > >         *
> > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > >                u64 start_offset, u64 range,
> > > > > >                u64 reserve_offset, u64 reserve_range,
> > > > > >                const struct drm_gpuvm_ops *ops);
> > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > + *
> > > > > > + * This function acquires an additional reference to
> > > > > > @gpuvm. It is illegal to
> > > > > > + * call this without already holding a reference. No locks required.
> > > > > > + */
> > > > > > +static inline struct drm_gpuvm *
> > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    kref_get(&gpuvm->kref);
> > > > > > +
> > > > > > +    return gpuvm;
> > > > > > +}
> > > > > > +
> > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > >    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > u64 addr, u64 range);
> > > > > >    bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > >     * operations to drivers.
> > > > > >     */
> > > > > >    struct drm_gpuvm_ops {
> > > > > > +    /**
> > > > > > +     * @vm_free: called when the last reference of a
> > > > > > struct drm_gpuvm is
> > > > > > +     * dropped
> > > > > > +     *
> > > > > > +     * This callback is mandatory.
> > > > > > +     */
> > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > >        /**
> > > > > >         * @op_alloc: called when the &drm_gpuvm allocates
> > > > > >         * a struct drm_gpuva_op
> > > 
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 12:16               ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 12:16 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

On Mon, Nov 06, 2023 at 10:14:29AM +0100, Christian König wrote:
> Am 03.11.23 um 16:34 schrieb Danilo Krummrich:
> [SNIP]
> > > 
> > > Especially we most likely don't want the VM to live longer than the
> > > application which originally used it. If you make the GPUVM an
> > > independent object you actually open up driver abuse for the
> > > lifetime of this.
> > 
> > Right, we don't want that. But I don't see how the reference count
> > prevents that.
> 
> It doesn't prevents that, it's just not the most defensive approach.
> 
> > 
> > Independant object is relative. struct drm_gpuvm is still embedded into
> > a driver
> > specific structure. It's working the same way as with struct
> > drm_gem_obejct.
> > 
> > > 
> > > Additional to that see below for a quite real problem with this.
> > > 
> > > > > Background is that the most common use case I see is that
> > > > > this object is
> > > > > embedded into something else and a reference count is then
> > > > > not really a good
> > > > > idea.
> > > > Do you have a specific use-case in mind where this would interfere?
> > > 
> > > Yes, absolutely. For an example see amdgpu_mes_self_test(), here we
> > > initialize a temporary amdgpu VM for an in kernel unit test which
> > > runs during driver load.
> > > 
> > > When the function returns I need to guarantee that the VM is
> > > destroyed or otherwise I will mess up normal operation.
> > 
> > Nothing prevents that. The reference counting is well defined. If the
> > driver did not
> > take additional references (which is clearly up to the driver taking
> > care of) and all
> > VM_BOs and mappings are cleaned up, the reference count is guaranteed to
> > be 1 at this
> > point.
> > 
> > Also note that if the driver would have not cleaned up all VM_BOs and
> > mappings before
> > shutting down the VM, it would have been a bug anyways and the driver
> > would potentially
> > leak memory and UAF issues.
> 
> Exactly that's what I'm talking about why I think this is an extremely bad
> idea.
> 
> It's a perfect normal operation to shutdown the VM while there are still
> mappings. This is just what happens when you kill an application.

Shut down the VM in terms of removing existing mappings, doing driver specifc
cleanup operations, etc. But not freeing the VM structure yet. That's what you
gonna do after you cleaned up everything.

So, when your application gets killed, you just call your driver_vm_destroy()
function, cleaning up all mappings etc. and then you call drm_gpuvm_put(). And
if you did a decent job cleaning things up (removing existing mappings etc.)
this drm_gpuvm_put() call will result into the vm_free() callback being called.

This reference count just prevents that the VM is freed as long as other
ressources are attached to it that carry a VM pointer, such as mappings and
VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
paranoid, but it doesn't hurt either and keeps it consistant.

> 
> Because of this the mapping should *never* have a reference to the VM, but
> rather the VM destroys all mapping when it is destroyed itself.
> 
> > Hence, If the VM is still alive at a point where you don't expect it to
> > be, then it's
> > simply a driver bug.
> 
> Driver bugs is just what I try to prevent here. When individual mappings
> keep the VM structure alive then drivers are responsible to clean them up,
> if the VM cleans up after itself then we don't need to worry about it in the
> driver.

Drivers are *always* responsible for that. This has nothing to do with whether
the VM is reference counted or not. GPUVM can't clean up mappings after itself.
If the driver left mappings, GPUVM would just leak them without reference count.
It doesn't know about the drivers surrounding structures, nor does it know about
attached ressources such as PT(E)s. 

> 
> When the mapping is destroyed with the VM drivers can't mess this common
> operation up. That's why this is more defensive.
> 
> What is a possible requirement is that external code needs to keep
> references to the VM, but *never* the VM to itself through the mappings. I
> would consider that a major bug in the component.

Obviously, you just (want to) apply a different semantics to this reference
count. It is meant to reflect that the VM structure can be freed, instead of the
VM can be cleaned up. If you want to latter, you can have a driver specifc
reference count for that in the exact same way as it was before this patch.

> 
> Regards,
> Christian.
> 
> > 
> > > 
> > > Reference counting is nice when you don't know who else is referring
> > > to your VM, but the cost is that you also don't know when the object
> > > will guardedly be destroyed.
> > > 
> > > I can trivially work around this by saying that the generic GPUVM
> > > object has a different lifetime than the amdgpu specific object, but
> > > that opens up doors for use after free again.
> > 
> > If your driver never touches the VM's reference count and exits the VM
> > with a clean state
> > (no mappings and no VM_BOs left), effectively, this is the same as
> > having no reference
> > count.
> > 
> > In the very worst case you could argue that we trade a potential UAF
> > *and* memroy leak
> > (no reference count) with *only* a memory leak (with reference count),
> > which to me seems
> > reasonable.
> > 
> > > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Thanks,
> > > > > Christian.
> > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > 
> > > > 
> > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > ---
> > > > > >    drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > +++++++++++++++++++-------
> > > > > >    drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > >    include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > >    3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > 
> > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >        gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > >        INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > +    kref_init(&gpuvm->kref);
> > > > > > +
> > > > > >        gpuvm->name = name ? name : "unknown";
> > > > > >        gpuvm->flags = flags;
> > > > > >        gpuvm->ops = ops;
> > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > *gpuvm, const char *name,
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > -/**
> > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > - *
> > > > > > - * Note that it is a bug to call this function on a
> > > > > > manager that still
> > > > > > - * holds GPU VA mappings.
> > > > > > - */
> > > > > > -void
> > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > +static void
> > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > >    {
> > > > > >        gpuvm->name = NULL;
> > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > >        drm_gem_object_put(gpuvm->r_obj);
> > > > > >    }
> > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > +
> > > > > > +static void
> > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > +{
> > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > drm_gpuvm, kref);
> > > > > > +
> > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > +        return;
> > > > > > +
> > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > +
> > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > +}
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > + *
> > > > > > + * This releases a reference to @gpuvm.
> > > > > > + */
> > > > > > +void
> > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    if (gpuvm)
> > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > +}
> > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > >    static int
> > > > > >    __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > >        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > >            return -EINVAL;
> > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > >        }
> > > > > >        __drm_gpuva_remove(va);
> > > > > > +    drm_gpuvm_put(va->vm);
> > > > > >    }
> > > > > >    EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > nouveau_bo *nvbo)
> > > > > >        }
> > > > > >    }
> > > > > > +static void
> > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > +
> > > > > > +    kfree(uvmm);
> > > > > > +}
> > > > > > +
> > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > +};
> > > > > > +
> > > > > >    int
> > > > > >    nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > >                   void *data,
> > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >                   NOUVEAU_VA_SPACE_END,
> > > > > >                   init->kernel_managed_addr,
> > > > > >                   init->kernel_managed_size,
> > > > > > -               NULL);
> > > > > > +               &gpuvm_ops);
> > > > > >        /* GPUVM takes care from here on. */
> > > > > >        drm_gem_object_put(r_obj);
> > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > drm_device *dev,
> > > > > >        return 0;
> > > > > >    out_gpuvm_fini:
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >    out_unlock:
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >        return ret;
> > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > >        mutex_lock(&cli->mutex);
> > > > > >        nouveau_vmm_fini(&uvmm->vmm);
> > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > -    kfree(uvmm);
> > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > >        mutex_unlock(&cli->mutex);
> > > > > >    }
> > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > >            struct list_head list;
> > > > > >        } rb;
> > > > > > +    /**
> > > > > > +     * @kref: reference count of this object
> > > > > > +     */
> > > > > > +    struct kref kref;
> > > > > > +
> > > > > >        /**
> > > > > >         * @kernel_alloc_node:
> > > > > >         *
> > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > >                u64 start_offset, u64 range,
> > > > > >                u64 reserve_offset, u64 reserve_range,
> > > > > >                const struct drm_gpuvm_ops *ops);
> > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > > +/**
> > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > + *
> > > > > > + * This function acquires an additional reference to
> > > > > > @gpuvm. It is illegal to
> > > > > > + * call this without already holding a reference. No locks required.
> > > > > > + */
> > > > > > +static inline struct drm_gpuvm *
> > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > +{
> > > > > > +    kref_get(&gpuvm->kref);
> > > > > > +
> > > > > > +    return gpuvm;
> > > > > > +}
> > > > > > +
> > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > >    bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > u64 addr, u64 range);
> > > > > >    bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > >     * operations to drivers.
> > > > > >     */
> > > > > >    struct drm_gpuvm_ops {
> > > > > > +    /**
> > > > > > +     * @vm_free: called when the last reference of a
> > > > > > struct drm_gpuvm is
> > > > > > +     * dropped
> > > > > > +     *
> > > > > > +     * This callback is mandatory.
> > > > > > +     */
> > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > +
> > > > > >        /**
> > > > > >         * @op_alloc: called when the &drm_gpuvm allocates
> > > > > >         * a struct drm_gpuva_op
> > > 
> > 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06 12:16               ` Danilo Krummrich
@ 2023-11-06 13:05                 ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06 13:05 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

[-- Attachment #1: Type: text/plain, Size: 11979 bytes --]

Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> [SNIP]
> This reference count just prevents that the VM is freed as long as other
> ressources are attached to it that carry a VM pointer, such as mappings and
> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> paranoid, but it doesn't hurt either and keeps it consistant.

Ah! Yeah, we have similar semantics in amdgpu as well.

But we keep the reference to the root GEM object and not the VM.

Ok, that makes much more sense then keeping one reference for each mapping.

>> Because of this the mapping should *never* have a reference to the VM, but
>> rather the VM destroys all mapping when it is destroyed itself.
>>
>>> Hence, If the VM is still alive at a point where you don't expect it to
>>> be, then it's
>>> simply a driver bug.
>> Driver bugs is just what I try to prevent here. When individual mappings
>> keep the VM structure alive then drivers are responsible to clean them up,
>> if the VM cleans up after itself then we don't need to worry about it in the
>> driver.
> Drivers are *always* responsible for that. This has nothing to do with whether
> the VM is reference counted or not. GPUVM can't clean up mappings after itself.

Why not?

At least in amdgpu we have it exactly like that. E.g. the higher level 
can cleanup the BO_VM structure at any time possible, even when there 
are mappings. The VM then keeps track which areas still need to be 
invalidated in the physical representation of the page tables.

I would expect that the generalized GPU VM handling would need something 
similar. If we leave that to the driver then each driver would have to 
implement that stuff on it's own again.

> If the driver left mappings, GPUVM would just leak them without reference count.
> It doesn't know about the drivers surrounding structures, nor does it know about
> attached ressources such as PT(E)s.

What are we talking with the word "mapping"? The BO_VM structure? Or 
each individual mapping?

E.g. what we need to prevent is that VM structure (or the root GEM 
object) is released while VM_BOs are still around. That's what I totally 
agree on.

But each individual mapping is a different story. Userspace can create 
so many of them that we probably could even overrun a 32bit counter 
quite easily.

>> When the mapping is destroyed with the VM drivers can't mess this common
>> operation up. That's why this is more defensive.
>>
>> What is a possible requirement is that external code needs to keep
>> references to the VM, but *never* the VM to itself through the mappings. I
>> would consider that a major bug in the component.
> Obviously, you just (want to) apply a different semantics to this reference
> count. It is meant to reflect that the VM structure can be freed, instead of the
> VM can be cleaned up. If you want to latter, you can have a driver specifc
> reference count for that in the exact same way as it was before this patch.

Yeah, it becomes clear that you try to solve some different problem than 
I have expected.

Regards,
Christian.

>
>> Regards,
>> Christian.
>>
>>>> Reference counting is nice when you don't know who else is referring
>>>> to your VM, but the cost is that you also don't know when the object
>>>> will guardedly be destroyed.
>>>>
>>>> I can trivially work around this by saying that the generic GPUVM
>>>> object has a different lifetime than the amdgpu specific object, but
>>>> that opens up doors for use after free again.
>>> If your driver never touches the VM's reference count and exits the VM
>>> with a clean state
>>> (no mappings and no VM_BOs left), effectively, this is the same as
>>> having no reference
>>> count.
>>>
>>> In the very worst case you could argue that we trade a potential UAF
>>> *and* memroy leak
>>> (no reference count) with *only* a memory leak (with reference count),
>>> which to me seems
>>> reasonable.
>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>> Thanks,
>>>>>> Christian.
>>>>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>>>>>
>>>>>
>>>>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>>>>> ---
>>>>>>>     drivers/gpu/drm/drm_gpuvm.c            | 44
>>>>>>> +++++++++++++++++++-------
>>>>>>>     drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>>>>     include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>>>>     3 files changed, 78 insertions(+), 17 deletions(-)
>>>>>>>
>>>>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
>>>>>>> *gpuvm, const char *name,
>>>>>>>         gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>>>>         INIT_LIST_HEAD(&gpuvm->rb.list);
>>>>>>> +    kref_init(&gpuvm->kref);
>>>>>>> +
>>>>>>>         gpuvm->name = name ? name : "unknown";
>>>>>>>         gpuvm->flags = flags;
>>>>>>>         gpuvm->ops = ops;
>>>>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
>>>>>>> *gpuvm, const char *name,
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>>>>> -/**
>>>>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>>>>> - *
>>>>>>> - * Note that it is a bug to call this function on a
>>>>>>> manager that still
>>>>>>> - * holds GPU VA mappings.
>>>>>>> - */
>>>>>>> -void
>>>>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>>> +static void
>>>>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>>>>     {
>>>>>>>         gpuvm->name = NULL;
>>>>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>>>         drm_gem_object_put(gpuvm->r_obj);
>>>>>>>     }
>>>>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>>>> +
>>>>>>> +static void
>>>>>>> +drm_gpuvm_free(struct kref *kref)
>>>>>>> +{
>>>>>>> +    struct drm_gpuvm *gpuvm = container_of(kref, struct
>>>>>>> drm_gpuvm, kref);
>>>>>>> +
>>>>>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>>>>> +        return;
>>>>>>> +
>>>>>>> +    drm_gpuvm_fini(gpuvm);
>>>>>>> +
>>>>>>> +    gpuvm->ops->vm_free(gpuvm);
>>>>>>> +}
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>>>>> + *
>>>>>>> + * This releases a reference to @gpuvm.
>>>>>>> + */
>>>>>>> +void
>>>>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    if (gpuvm)
>>>>>>> +        kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>>>>     static int
>>>>>>>     __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>>>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>>>>             return -EINVAL;
>>>>>>> -    return __drm_gpuva_insert(gpuvm, va);
>>>>>>> +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>>>>         }
>>>>>>>         __drm_gpuva_remove(va);
>>>>>>> +    drm_gpuvm_put(va->vm);
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> index 54be12c1272f..cb2f06565c46 100644
>>>>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
>>>>>>> nouveau_bo *nvbo)
>>>>>>>         }
>>>>>>>     }
>>>>>>> +static void
>>>>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>>>>> +
>>>>>>> +    kfree(uvmm);
>>>>>>> +}
>>>>>>> +
>>>>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>>>>> +    .vm_free = nouveau_uvmm_free,
>>>>>>> +};
>>>>>>> +
>>>>>>>     int
>>>>>>>     nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>>>>                    void *data,
>>>>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
>>>>>>> drm_device *dev,
>>>>>>>                    NOUVEAU_VA_SPACE_END,
>>>>>>>                    init->kernel_managed_addr,
>>>>>>>                    init->kernel_managed_size,
>>>>>>> -               NULL);
>>>>>>> +               &gpuvm_ops);
>>>>>>>         /* GPUVM takes care from here on. */
>>>>>>>         drm_gem_object_put(r_obj);
>>>>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
>>>>>>> drm_device *dev,
>>>>>>>         return 0;
>>>>>>>     out_gpuvm_fini:
>>>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>>>> -    kfree(uvmm);
>>>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>>>     out_unlock:
>>>>>>>         mutex_unlock(&cli->mutex);
>>>>>>>         return ret;
>>>>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>>>>         mutex_lock(&cli->mutex);
>>>>>>>         nouveau_vmm_fini(&uvmm->vmm);
>>>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>>>> -    kfree(uvmm);
>>>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>>>         mutex_unlock(&cli->mutex);
>>>>>>>     }
>>>>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>>>>> --- a/include/drm/drm_gpuvm.h
>>>>>>> +++ b/include/drm/drm_gpuvm.h
>>>>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>>>>             struct list_head list;
>>>>>>>         } rb;
>>>>>>> +    /**
>>>>>>> +     * @kref: reference count of this object
>>>>>>> +     */
>>>>>>> +    struct kref kref;
>>>>>>> +
>>>>>>>         /**
>>>>>>>          * @kernel_alloc_node:
>>>>>>>          *
>>>>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
>>>>>>> drm_gpuvm *gpuvm, const char *name,
>>>>>>>                 u64 start_offset, u64 range,
>>>>>>>                 u64 reserve_offset, u64 reserve_range,
>>>>>>>                 const struct drm_gpuvm_ops *ops);
>>>>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>>>>> + *
>>>>>>> + * This function acquires an additional reference to
>>>>>>> @gpuvm. It is illegal to
>>>>>>> + * call this without already holding a reference. No locks required.
>>>>>>> + */
>>>>>>> +static inline struct drm_gpuvm *
>>>>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    kref_get(&gpuvm->kref);
>>>>>>> +
>>>>>>> +    return gpuvm;
>>>>>>> +}
>>>>>>> +
>>>>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>>>>     bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>>>>>>> u64 addr, u64 range);
>>>>>>>     bool drm_gpuvm_interval_empty(struct drm_gpuvm
>>>>>>> *gpuvm, u64 addr, u64 range);
>>>>>>> @@ -673,6 +694,14 @@ static inline void
>>>>>>> drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>>>>      * operations to drivers.
>>>>>>>      */
>>>>>>>     struct drm_gpuvm_ops {
>>>>>>> +    /**
>>>>>>> +     * @vm_free: called when the last reference of a
>>>>>>> struct drm_gpuvm is
>>>>>>> +     * dropped
>>>>>>> +     *
>>>>>>> +     * This callback is mandatory.
>>>>>>> +     */
>>>>>>> +    void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>>>>> +
>>>>>>>         /**
>>>>>>>          * @op_alloc: called when the &drm_gpuvm allocates
>>>>>>>          * a struct drm_gpuva_op

[-- Attachment #2: Type: text/html, Size: 15234 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 13:05                 ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06 13:05 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

[-- Attachment #1: Type: text/plain, Size: 11979 bytes --]

Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> [SNIP]
> This reference count just prevents that the VM is freed as long as other
> ressources are attached to it that carry a VM pointer, such as mappings and
> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> paranoid, but it doesn't hurt either and keeps it consistant.

Ah! Yeah, we have similar semantics in amdgpu as well.

But we keep the reference to the root GEM object and not the VM.

Ok, that makes much more sense then keeping one reference for each mapping.

>> Because of this the mapping should *never* have a reference to the VM, but
>> rather the VM destroys all mapping when it is destroyed itself.
>>
>>> Hence, If the VM is still alive at a point where you don't expect it to
>>> be, then it's
>>> simply a driver bug.
>> Driver bugs is just what I try to prevent here. When individual mappings
>> keep the VM structure alive then drivers are responsible to clean them up,
>> if the VM cleans up after itself then we don't need to worry about it in the
>> driver.
> Drivers are *always* responsible for that. This has nothing to do with whether
> the VM is reference counted or not. GPUVM can't clean up mappings after itself.

Why not?

At least in amdgpu we have it exactly like that. E.g. the higher level 
can cleanup the BO_VM structure at any time possible, even when there 
are mappings. The VM then keeps track which areas still need to be 
invalidated in the physical representation of the page tables.

I would expect that the generalized GPU VM handling would need something 
similar. If we leave that to the driver then each driver would have to 
implement that stuff on it's own again.

> If the driver left mappings, GPUVM would just leak them without reference count.
> It doesn't know about the drivers surrounding structures, nor does it know about
> attached ressources such as PT(E)s.

What are we talking with the word "mapping"? The BO_VM structure? Or 
each individual mapping?

E.g. what we need to prevent is that VM structure (or the root GEM 
object) is released while VM_BOs are still around. That's what I totally 
agree on.

But each individual mapping is a different story. Userspace can create 
so many of them that we probably could even overrun a 32bit counter 
quite easily.

>> When the mapping is destroyed with the VM drivers can't mess this common
>> operation up. That's why this is more defensive.
>>
>> What is a possible requirement is that external code needs to keep
>> references to the VM, but *never* the VM to itself through the mappings. I
>> would consider that a major bug in the component.
> Obviously, you just (want to) apply a different semantics to this reference
> count. It is meant to reflect that the VM structure can be freed, instead of the
> VM can be cleaned up. If you want to latter, you can have a driver specifc
> reference count for that in the exact same way as it was before this patch.

Yeah, it becomes clear that you try to solve some different problem than 
I have expected.

Regards,
Christian.

>
>> Regards,
>> Christian.
>>
>>>> Reference counting is nice when you don't know who else is referring
>>>> to your VM, but the cost is that you also don't know when the object
>>>> will guardedly be destroyed.
>>>>
>>>> I can trivially work around this by saying that the generic GPUVM
>>>> object has a different lifetime than the amdgpu specific object, but
>>>> that opens up doors for use after free again.
>>> If your driver never touches the VM's reference count and exits the VM
>>> with a clean state
>>> (no mappings and no VM_BOs left), effectively, this is the same as
>>> having no reference
>>> count.
>>>
>>> In the very worst case you could argue that we trade a potential UAF
>>> *and* memroy leak
>>> (no reference count) with *only* a memory leak (with reference count),
>>> which to me seems
>>> reasonable.
>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>> Thanks,
>>>>>> Christian.
>>>>> [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
>>>>>
>>>>>
>>>>>>> Signed-off-by: Danilo Krummrich<dakr@redhat.com>
>>>>>>> ---
>>>>>>>     drivers/gpu/drm/drm_gpuvm.c            | 44
>>>>>>> +++++++++++++++++++-------
>>>>>>>     drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
>>>>>>>     include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
>>>>>>>     3 files changed, 78 insertions(+), 17 deletions(-)
>>>>>>>
>>>>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> index 53e2c406fb04..6a88eafc5229 100644
>>>>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>>>>> @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
>>>>>>> *gpuvm, const char *name,
>>>>>>>         gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>>>>         INIT_LIST_HEAD(&gpuvm->rb.list);
>>>>>>> +    kref_init(&gpuvm->kref);
>>>>>>> +
>>>>>>>         gpuvm->name = name ? name : "unknown";
>>>>>>>         gpuvm->flags = flags;
>>>>>>>         gpuvm->ops = ops;
>>>>>>> @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
>>>>>>> *gpuvm, const char *name,
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuvm_init);
>>>>>>> -/**
>>>>>>> - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
>>>>>>> - * @gpuvm: pointer to the &drm_gpuvm to clean up
>>>>>>> - *
>>>>>>> - * Note that it is a bug to call this function on a
>>>>>>> manager that still
>>>>>>> - * holds GPU VA mappings.
>>>>>>> - */
>>>>>>> -void
>>>>>>> -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>>> +static void
>>>>>>> +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
>>>>>>>     {
>>>>>>>         gpuvm->name = NULL;
>>>>>>> @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>>>>         drm_gem_object_put(gpuvm->r_obj);
>>>>>>>     }
>>>>>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>>>> +
>>>>>>> +static void
>>>>>>> +drm_gpuvm_free(struct kref *kref)
>>>>>>> +{
>>>>>>> +    struct drm_gpuvm *gpuvm = container_of(kref, struct
>>>>>>> drm_gpuvm, kref);
>>>>>>> +
>>>>>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>>>>>> +        return;
>>>>>>> +
>>>>>>> +    drm_gpuvm_fini(gpuvm);
>>>>>>> +
>>>>>>> +    gpuvm->ops->vm_free(gpuvm);
>>>>>>> +}
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
>>>>>>> + * @gpuvm: the &drm_gpuvm to release the reference of
>>>>>>> + *
>>>>>>> + * This releases a reference to @gpuvm.
>>>>>>> + */
>>>>>>> +void
>>>>>>> +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    if (gpuvm)
>>>>>>> +        kref_put(&gpuvm->kref, drm_gpuvm_free);
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
>>>>>>>     static int
>>>>>>>     __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>>> @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>>>>>         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
>>>>>>>             return -EINVAL;
>>>>>>> -    return __drm_gpuva_insert(gpuvm, va);
>>>>>>> +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuva_insert);
>>>>>>> @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
>>>>>>>         }
>>>>>>>         __drm_gpuva_remove(va);
>>>>>>> +    drm_gpuvm_put(va->vm);
>>>>>>>     }
>>>>>>>     EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> index 54be12c1272f..cb2f06565c46 100644
>>>>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>>>>> @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
>>>>>>> nouveau_bo *nvbo)
>>>>>>>         }
>>>>>>>     }
>>>>>>> +static void
>>>>>>> +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
>>>>>>> +
>>>>>>> +    kfree(uvmm);
>>>>>>> +}
>>>>>>> +
>>>>>>> +static const struct drm_gpuvm_ops gpuvm_ops = {
>>>>>>> +    .vm_free = nouveau_uvmm_free,
>>>>>>> +};
>>>>>>> +
>>>>>>>     int
>>>>>>>     nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
>>>>>>>                    void *data,
>>>>>>> @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
>>>>>>> drm_device *dev,
>>>>>>>                    NOUVEAU_VA_SPACE_END,
>>>>>>>                    init->kernel_managed_addr,
>>>>>>>                    init->kernel_managed_size,
>>>>>>> -               NULL);
>>>>>>> +               &gpuvm_ops);
>>>>>>>         /* GPUVM takes care from here on. */
>>>>>>>         drm_gem_object_put(r_obj);
>>>>>>> @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
>>>>>>> drm_device *dev,
>>>>>>>         return 0;
>>>>>>>     out_gpuvm_fini:
>>>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>>>> -    kfree(uvmm);
>>>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>>>     out_unlock:
>>>>>>>         mutex_unlock(&cli->mutex);
>>>>>>>         return ret;
>>>>>>> @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
>>>>>>>         mutex_lock(&cli->mutex);
>>>>>>>         nouveau_vmm_fini(&uvmm->vmm);
>>>>>>> -    drm_gpuvm_destroy(&uvmm->base);
>>>>>>> -    kfree(uvmm);
>>>>>>> +    drm_gpuvm_put(&uvmm->base);
>>>>>>>         mutex_unlock(&cli->mutex);
>>>>>>>     }
>>>>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>>>>> index 0c2e24155a93..4e6e1fd3485a 100644
>>>>>>> --- a/include/drm/drm_gpuvm.h
>>>>>>> +++ b/include/drm/drm_gpuvm.h
>>>>>>> @@ -247,6 +247,11 @@ struct drm_gpuvm {
>>>>>>>             struct list_head list;
>>>>>>>         } rb;
>>>>>>> +    /**
>>>>>>> +     * @kref: reference count of this object
>>>>>>> +     */
>>>>>>> +    struct kref kref;
>>>>>>> +
>>>>>>>         /**
>>>>>>>          * @kernel_alloc_node:
>>>>>>>          *
>>>>>>> @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
>>>>>>> drm_gpuvm *gpuvm, const char *name,
>>>>>>>                 u64 start_offset, u64 range,
>>>>>>>                 u64 reserve_offset, u64 reserve_range,
>>>>>>>                 const struct drm_gpuvm_ops *ops);
>>>>>>> -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
>>>>>>> + * @gpuvm: the &drm_gpuvm to acquire the reference of
>>>>>>> + *
>>>>>>> + * This function acquires an additional reference to
>>>>>>> @gpuvm. It is illegal to
>>>>>>> + * call this without already holding a reference. No locks required.
>>>>>>> + */
>>>>>>> +static inline struct drm_gpuvm *
>>>>>>> +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
>>>>>>> +{
>>>>>>> +    kref_get(&gpuvm->kref);
>>>>>>> +
>>>>>>> +    return gpuvm;
>>>>>>> +}
>>>>>>> +
>>>>>>> +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
>>>>>>>     bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>>>>>>> u64 addr, u64 range);
>>>>>>>     bool drm_gpuvm_interval_empty(struct drm_gpuvm
>>>>>>> *gpuvm, u64 addr, u64 range);
>>>>>>> @@ -673,6 +694,14 @@ static inline void
>>>>>>> drm_gpuva_init_from_op(struct drm_gpuva *va,
>>>>>>>      * operations to drivers.
>>>>>>>      */
>>>>>>>     struct drm_gpuvm_ops {
>>>>>>> +    /**
>>>>>>> +     * @vm_free: called when the last reference of a
>>>>>>> struct drm_gpuvm is
>>>>>>> +     * dropped
>>>>>>> +     *
>>>>>>> +     * This callback is mandatory.
>>>>>>> +     */
>>>>>>> +    void (*vm_free)(struct drm_gpuvm *gpuvm);
>>>>>>> +
>>>>>>>         /**
>>>>>>>          * @op_alloc: called when the &drm_gpuvm allocates
>>>>>>>          * a struct drm_gpuva_op

[-- Attachment #2: Type: text/html, Size: 15234 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06 13:05                 ` Christian König
  (?)
@ 2023-11-06 14:11                   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 14:11 UTC (permalink / raw)
  To: Christian König
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > [SNIP]
> > This reference count just prevents that the VM is freed as long as other
> > ressources are attached to it that carry a VM pointer, such as mappings and
> > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > paranoid, but it doesn't hurt either and keeps it consistant.
> 
> Ah! Yeah, we have similar semantics in amdgpu as well.
> 
> But we keep the reference to the root GEM object and not the VM.
> 
> Ok, that makes much more sense then keeping one reference for each mapping.
> 
> > > Because of this the mapping should *never* have a reference to the VM, but
> > > rather the VM destroys all mapping when it is destroyed itself.
> > > 
> > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > be, then it's
> > > > simply a driver bug.
> > > Driver bugs is just what I try to prevent here. When individual mappings
> > > keep the VM structure alive then drivers are responsible to clean them up,
> > > if the VM cleans up after itself then we don't need to worry about it in the
> > > driver.
> > Drivers are *always* responsible for that. This has nothing to do with whether
> > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> 
> Why not?

I feel like we're talking past each other here, at least to some extend.
However, I can't yet see where exactly the misunderstanding resides.

> 
> At least in amdgpu we have it exactly like that. E.g. the higher level can
> cleanup the BO_VM structure at any time possible, even when there are
> mappings.

What do you mean with "cleanup the VM_BO structue" exactly?

The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
the corresponding VM_BOs.

Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
should stay alive.

> The VM then keeps track which areas still need to be invalidated
> in the physical representation of the page tables.

And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
the VM would just remove those structures on cleanup by itself, you'd loose the
ability of cleaning up the page tables. Unless, you track this separately, which
would make the whole tracking of GPUVM itself kinda pointless.

> 
> I would expect that the generalized GPU VM handling would need something
> similar. If we leave that to the driver then each driver would have to
> implement that stuff on it's own again.

Similar to what? What exactly do you think can be generalized here?

> 
> > If the driver left mappings, GPUVM would just leak them without reference count.
> > It doesn't know about the drivers surrounding structures, nor does it know about
> > attached ressources such as PT(E)s.
> 
> What are we talking with the word "mapping"? The BO_VM structure? Or each
> individual mapping?

An individual mapping represented by struct drm_gpuva.

> 
> E.g. what we need to prevent is that VM structure (or the root GEM object)
> is released while VM_BOs are still around. That's what I totally agree on.
> 
> But each individual mapping is a different story. Userspace can create so
> many of them that we probably could even overrun a 32bit counter quite
> easily.

REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?

> 
> > > When the mapping is destroyed with the VM drivers can't mess this common
> > > operation up. That's why this is more defensive.
> > > 
> > > What is a possible requirement is that external code needs to keep
> > > references to the VM, but *never* the VM to itself through the mappings. I
> > > would consider that a major bug in the component.
> > Obviously, you just (want to) apply a different semantics to this reference
> > count. It is meant to reflect that the VM structure can be freed, instead of the
> > VM can be cleaned up. If you want to latter, you can have a driver specifc
> > reference count for that in the exact same way as it was before this patch.
> 
> Yeah, it becomes clear that you try to solve some different problem than I
> have expected.
> 
> Regards,
> Christian.
> 
> > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Reference counting is nice when you don't know who else is referring
> > > > > to your VM, but the cost is that you also don't know when the object
> > > > > will guardedly be destroyed.
> > > > > 
> > > > > I can trivially work around this by saying that the generic GPUVM
> > > > > object has a different lifetime than the amdgpu specific object, but
> > > > > that opens up doors for use after free again.
> > > > If your driver never touches the VM's reference count and exits the VM
> > > > with a clean state
> > > > (no mappings and no VM_BOs left), effectively, this is the same as
> > > > having no reference
> > > > count.
> > > > 
> > > > In the very worst case you could argue that we trade a potential UAF
> > > > *and* memroy leak
> > > > (no reference count) with *only* a memory leak (with reference count),
> > > > which to me seems
> > > > reasonable.
> > > > 
> > > > > Regards,
> > > > > Christian.
> > > > > 
> > > > > > > Thanks,
> > > > > > > Christian.
> > > > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > > > 
> > > > > > 
> > > > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > > > ---
> > > > > > > >     drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > > > +++++++++++++++++++-------
> > > > > > > >     drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > > > >     include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > > > >     3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > > > 
> > > > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >         gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > > > >         INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > > > +    kref_init(&gpuvm->kref);
> > > > > > > > +
> > > > > > > >         gpuvm->name = name ? name : "unknown";
> > > > > > > >         gpuvm->flags = flags;
> > > > > > > >         gpuvm->ops = ops;
> > > > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > > > -/**
> > > > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > > > - *
> > > > > > > > - * Note that it is a bug to call this function on a
> > > > > > > > manager that still
> > > > > > > > - * holds GPU VA mappings.
> > > > > > > > - */
> > > > > > > > -void
> > > > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > > > >     {
> > > > > > > >         gpuvm->name = NULL;
> > > > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > >         drm_gem_object_put(gpuvm->r_obj);
> > > > > > > >     }
> > > > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > > > +
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > > > +{
> > > > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > > > drm_gpuvm, kref);
> > > > > > > > +
> > > > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > > > +        return;
> > > > > > > > +
> > > > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > > > +
> > > > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > > > + *
> > > > > > > > + * This releases a reference to @gpuvm.
> > > > > > > > + */
> > > > > > > > +void
> > > > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    if (gpuvm)
> > > > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > > > +}
> > > > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > > > >     static int
> > > > > > > >     __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > >         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > > > >             return -EINVAL;
> > > > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > > > >         }
> > > > > > > >         __drm_gpuva_remove(va);
> > > > > > > > +    drm_gpuvm_put(va->vm);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > > > nouveau_bo *nvbo)
> > > > > > > >         }
> > > > > > > >     }
> > > > > > > > +static void
> > > > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > > > +
> > > > > > > > +    kfree(uvmm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > >     int
> > > > > > > >     nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > > > >                    void *data,
> > > > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >                    NOUVEAU_VA_SPACE_END,
> > > > > > > >                    init->kernel_managed_addr,
> > > > > > > >                    init->kernel_managed_size,
> > > > > > > > -               NULL);
> > > > > > > > +               &gpuvm_ops);
> > > > > > > >         /* GPUVM takes care from here on. */
> > > > > > > >         drm_gem_object_put(r_obj);
> > > > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >         return 0;
> > > > > > > >     out_gpuvm_fini:
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >     out_unlock:
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >         return ret;
> > > > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > > > >         mutex_lock(&cli->mutex);
> > > > > > > >         nouveau_vmm_fini(&uvmm->vmm);
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >     }
> > > > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > > > >             struct list_head list;
> > > > > > > >         } rb;
> > > > > > > > +    /**
> > > > > > > > +     * @kref: reference count of this object
> > > > > > > > +     */
> > > > > > > > +    struct kref kref;
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @kernel_alloc_node:
> > > > > > > >          *
> > > > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > > > >                 u64 start_offset, u64 range,
> > > > > > > >                 u64 reserve_offset, u64 reserve_range,
> > > > > > > >                 const struct drm_gpuvm_ops *ops);
> > > > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > > > + *
> > > > > > > > + * This function acquires an additional reference to
> > > > > > > > @gpuvm. It is illegal to
> > > > > > > > + * call this without already holding a reference. No locks required.
> > > > > > > > + */
> > > > > > > > +static inline struct drm_gpuvm *
> > > > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    kref_get(&gpuvm->kref);
> > > > > > > > +
> > > > > > > > +    return gpuvm;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > > > >     bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > > > u64 addr, u64 range);
> > > > > > > >     bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > > > >      * operations to drivers.
> > > > > > > >      */
> > > > > > > >     struct drm_gpuvm_ops {
> > > > > > > > +    /**
> > > > > > > > +     * @vm_free: called when the last reference of a
> > > > > > > > struct drm_gpuvm is
> > > > > > > > +     * dropped
> > > > > > > > +     *
> > > > > > > > +     * This callback is mandatory.
> > > > > > > > +     */
> > > > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @op_alloc: called when the &drm_gpuvm allocates
> > > > > > > >          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 14:11                   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 14:11 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > [SNIP]
> > This reference count just prevents that the VM is freed as long as other
> > ressources are attached to it that carry a VM pointer, such as mappings and
> > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > paranoid, but it doesn't hurt either and keeps it consistant.
> 
> Ah! Yeah, we have similar semantics in amdgpu as well.
> 
> But we keep the reference to the root GEM object and not the VM.
> 
> Ok, that makes much more sense then keeping one reference for each mapping.
> 
> > > Because of this the mapping should *never* have a reference to the VM, but
> > > rather the VM destroys all mapping when it is destroyed itself.
> > > 
> > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > be, then it's
> > > > simply a driver bug.
> > > Driver bugs is just what I try to prevent here. When individual mappings
> > > keep the VM structure alive then drivers are responsible to clean them up,
> > > if the VM cleans up after itself then we don't need to worry about it in the
> > > driver.
> > Drivers are *always* responsible for that. This has nothing to do with whether
> > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> 
> Why not?

I feel like we're talking past each other here, at least to some extend.
However, I can't yet see where exactly the misunderstanding resides.

> 
> At least in amdgpu we have it exactly like that. E.g. the higher level can
> cleanup the BO_VM structure at any time possible, even when there are
> mappings.

What do you mean with "cleanup the VM_BO structue" exactly?

The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
the corresponding VM_BOs.

Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
should stay alive.

> The VM then keeps track which areas still need to be invalidated
> in the physical representation of the page tables.

And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
the VM would just remove those structures on cleanup by itself, you'd loose the
ability of cleaning up the page tables. Unless, you track this separately, which
would make the whole tracking of GPUVM itself kinda pointless.

> 
> I would expect that the generalized GPU VM handling would need something
> similar. If we leave that to the driver then each driver would have to
> implement that stuff on it's own again.

Similar to what? What exactly do you think can be generalized here?

> 
> > If the driver left mappings, GPUVM would just leak them without reference count.
> > It doesn't know about the drivers surrounding structures, nor does it know about
> > attached ressources such as PT(E)s.
> 
> What are we talking with the word "mapping"? The BO_VM structure? Or each
> individual mapping?

An individual mapping represented by struct drm_gpuva.

> 
> E.g. what we need to prevent is that VM structure (or the root GEM object)
> is released while VM_BOs are still around. That's what I totally agree on.
> 
> But each individual mapping is a different story. Userspace can create so
> many of them that we probably could even overrun a 32bit counter quite
> easily.

REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?

> 
> > > When the mapping is destroyed with the VM drivers can't mess this common
> > > operation up. That's why this is more defensive.
> > > 
> > > What is a possible requirement is that external code needs to keep
> > > references to the VM, but *never* the VM to itself through the mappings. I
> > > would consider that a major bug in the component.
> > Obviously, you just (want to) apply a different semantics to this reference
> > count. It is meant to reflect that the VM structure can be freed, instead of the
> > VM can be cleaned up. If you want to latter, you can have a driver specifc
> > reference count for that in the exact same way as it was before this patch.
> 
> Yeah, it becomes clear that you try to solve some different problem than I
> have expected.
> 
> Regards,
> Christian.
> 
> > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Reference counting is nice when you don't know who else is referring
> > > > > to your VM, but the cost is that you also don't know when the object
> > > > > will guardedly be destroyed.
> > > > > 
> > > > > I can trivially work around this by saying that the generic GPUVM
> > > > > object has a different lifetime than the amdgpu specific object, but
> > > > > that opens up doors for use after free again.
> > > > If your driver never touches the VM's reference count and exits the VM
> > > > with a clean state
> > > > (no mappings and no VM_BOs left), effectively, this is the same as
> > > > having no reference
> > > > count.
> > > > 
> > > > In the very worst case you could argue that we trade a potential UAF
> > > > *and* memroy leak
> > > > (no reference count) with *only* a memory leak (with reference count),
> > > > which to me seems
> > > > reasonable.
> > > > 
> > > > > Regards,
> > > > > Christian.
> > > > > 
> > > > > > > Thanks,
> > > > > > > Christian.
> > > > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > > > 
> > > > > > 
> > > > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > > > ---
> > > > > > > >     drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > > > +++++++++++++++++++-------
> > > > > > > >     drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > > > >     include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > > > >     3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > > > 
> > > > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >         gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > > > >         INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > > > +    kref_init(&gpuvm->kref);
> > > > > > > > +
> > > > > > > >         gpuvm->name = name ? name : "unknown";
> > > > > > > >         gpuvm->flags = flags;
> > > > > > > >         gpuvm->ops = ops;
> > > > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > > > -/**
> > > > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > > > - *
> > > > > > > > - * Note that it is a bug to call this function on a
> > > > > > > > manager that still
> > > > > > > > - * holds GPU VA mappings.
> > > > > > > > - */
> > > > > > > > -void
> > > > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > > > >     {
> > > > > > > >         gpuvm->name = NULL;
> > > > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > >         drm_gem_object_put(gpuvm->r_obj);
> > > > > > > >     }
> > > > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > > > +
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > > > +{
> > > > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > > > drm_gpuvm, kref);
> > > > > > > > +
> > > > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > > > +        return;
> > > > > > > > +
> > > > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > > > +
> > > > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > > > + *
> > > > > > > > + * This releases a reference to @gpuvm.
> > > > > > > > + */
> > > > > > > > +void
> > > > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    if (gpuvm)
> > > > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > > > +}
> > > > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > > > >     static int
> > > > > > > >     __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > >         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > > > >             return -EINVAL;
> > > > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > > > >         }
> > > > > > > >         __drm_gpuva_remove(va);
> > > > > > > > +    drm_gpuvm_put(va->vm);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > > > nouveau_bo *nvbo)
> > > > > > > >         }
> > > > > > > >     }
> > > > > > > > +static void
> > > > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > > > +
> > > > > > > > +    kfree(uvmm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > >     int
> > > > > > > >     nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > > > >                    void *data,
> > > > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >                    NOUVEAU_VA_SPACE_END,
> > > > > > > >                    init->kernel_managed_addr,
> > > > > > > >                    init->kernel_managed_size,
> > > > > > > > -               NULL);
> > > > > > > > +               &gpuvm_ops);
> > > > > > > >         /* GPUVM takes care from here on. */
> > > > > > > >         drm_gem_object_put(r_obj);
> > > > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >         return 0;
> > > > > > > >     out_gpuvm_fini:
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >     out_unlock:
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >         return ret;
> > > > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > > > >         mutex_lock(&cli->mutex);
> > > > > > > >         nouveau_vmm_fini(&uvmm->vmm);
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >     }
> > > > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > > > >             struct list_head list;
> > > > > > > >         } rb;
> > > > > > > > +    /**
> > > > > > > > +     * @kref: reference count of this object
> > > > > > > > +     */
> > > > > > > > +    struct kref kref;
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @kernel_alloc_node:
> > > > > > > >          *
> > > > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > > > >                 u64 start_offset, u64 range,
> > > > > > > >                 u64 reserve_offset, u64 reserve_range,
> > > > > > > >                 const struct drm_gpuvm_ops *ops);
> > > > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > > > + *
> > > > > > > > + * This function acquires an additional reference to
> > > > > > > > @gpuvm. It is illegal to
> > > > > > > > + * call this without already holding a reference. No locks required.
> > > > > > > > + */
> > > > > > > > +static inline struct drm_gpuvm *
> > > > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    kref_get(&gpuvm->kref);
> > > > > > > > +
> > > > > > > > +    return gpuvm;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > > > >     bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > > > u64 addr, u64 range);
> > > > > > > >     bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > > > >      * operations to drivers.
> > > > > > > >      */
> > > > > > > >     struct drm_gpuvm_ops {
> > > > > > > > +    /**
> > > > > > > > +     * @vm_free: called when the last reference of a
> > > > > > > > struct drm_gpuvm is
> > > > > > > > +     * dropped
> > > > > > > > +     *
> > > > > > > > +     * This callback is mandatory.
> > > > > > > > +     */
> > > > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @op_alloc: called when the &drm_gpuvm allocates
> > > > > > > >          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 14:11                   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 14:11 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > [SNIP]
> > This reference count just prevents that the VM is freed as long as other
> > ressources are attached to it that carry a VM pointer, such as mappings and
> > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > paranoid, but it doesn't hurt either and keeps it consistant.
> 
> Ah! Yeah, we have similar semantics in amdgpu as well.
> 
> But we keep the reference to the root GEM object and not the VM.
> 
> Ok, that makes much more sense then keeping one reference for each mapping.
> 
> > > Because of this the mapping should *never* have a reference to the VM, but
> > > rather the VM destroys all mapping when it is destroyed itself.
> > > 
> > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > be, then it's
> > > > simply a driver bug.
> > > Driver bugs is just what I try to prevent here. When individual mappings
> > > keep the VM structure alive then drivers are responsible to clean them up,
> > > if the VM cleans up after itself then we don't need to worry about it in the
> > > driver.
> > Drivers are *always* responsible for that. This has nothing to do with whether
> > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> 
> Why not?

I feel like we're talking past each other here, at least to some extend.
However, I can't yet see where exactly the misunderstanding resides.

> 
> At least in amdgpu we have it exactly like that. E.g. the higher level can
> cleanup the BO_VM structure at any time possible, even when there are
> mappings.

What do you mean with "cleanup the VM_BO structue" exactly?

The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
the corresponding VM_BOs.

Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
should stay alive.

> The VM then keeps track which areas still need to be invalidated
> in the physical representation of the page tables.

And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
the VM would just remove those structures on cleanup by itself, you'd loose the
ability of cleaning up the page tables. Unless, you track this separately, which
would make the whole tracking of GPUVM itself kinda pointless.

> 
> I would expect that the generalized GPU VM handling would need something
> similar. If we leave that to the driver then each driver would have to
> implement that stuff on it's own again.

Similar to what? What exactly do you think can be generalized here?

> 
> > If the driver left mappings, GPUVM would just leak them without reference count.
> > It doesn't know about the drivers surrounding structures, nor does it know about
> > attached ressources such as PT(E)s.
> 
> What are we talking with the word "mapping"? The BO_VM structure? Or each
> individual mapping?

An individual mapping represented by struct drm_gpuva.

> 
> E.g. what we need to prevent is that VM structure (or the root GEM object)
> is released while VM_BOs are still around. That's what I totally agree on.
> 
> But each individual mapping is a different story. Userspace can create so
> many of them that we probably could even overrun a 32bit counter quite
> easily.

REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?

> 
> > > When the mapping is destroyed with the VM drivers can't mess this common
> > > operation up. That's why this is more defensive.
> > > 
> > > What is a possible requirement is that external code needs to keep
> > > references to the VM, but *never* the VM to itself through the mappings. I
> > > would consider that a major bug in the component.
> > Obviously, you just (want to) apply a different semantics to this reference
> > count. It is meant to reflect that the VM structure can be freed, instead of the
> > VM can be cleaned up. If you want to latter, you can have a driver specifc
> > reference count for that in the exact same way as it was before this patch.
> 
> Yeah, it becomes clear that you try to solve some different problem than I
> have expected.
> 
> Regards,
> Christian.
> 
> > 
> > > Regards,
> > > Christian.
> > > 
> > > > > Reference counting is nice when you don't know who else is referring
> > > > > to your VM, but the cost is that you also don't know when the object
> > > > > will guardedly be destroyed.
> > > > > 
> > > > > I can trivially work around this by saying that the generic GPUVM
> > > > > object has a different lifetime than the amdgpu specific object, but
> > > > > that opens up doors for use after free again.
> > > > If your driver never touches the VM's reference count and exits the VM
> > > > with a clean state
> > > > (no mappings and no VM_BOs left), effectively, this is the same as
> > > > having no reference
> > > > count.
> > > > 
> > > > In the very worst case you could argue that we trade a potential UAF
> > > > *and* memroy leak
> > > > (no reference count) with *only* a memory leak (with reference count),
> > > > which to me seems
> > > > reasonable.
> > > > 
> > > > > Regards,
> > > > > Christian.
> > > > > 
> > > > > > > Thanks,
> > > > > > > Christian.
> > > > > > [1]https://lore.kernel.org/dri-devel/6fa058a4-20d3-44b9-af58-755cfb375d75@redhat.com/
> > > > > > 
> > > > > > 
> > > > > > > > Signed-off-by: Danilo Krummrich<dakr@redhat.com>
> > > > > > > > ---
> > > > > > > >     drivers/gpu/drm/drm_gpuvm.c            | 44
> > > > > > > > +++++++++++++++++++-------
> > > > > > > >     drivers/gpu/drm/nouveau/nouveau_uvmm.c | 20 +++++++++---
> > > > > > > >     include/drm/drm_gpuvm.h                | 31 +++++++++++++++++-
> > > > > > > >     3 files changed, 78 insertions(+), 17 deletions(-)
> > > > > > > > 
> > > > > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > index 53e2c406fb04..6a88eafc5229 100644
> > > > > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > > > > @@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >         gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > > > > >         INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > > > > +    kref_init(&gpuvm->kref);
> > > > > > > > +
> > > > > > > >         gpuvm->name = name ? name : "unknown";
> > > > > > > >         gpuvm->flags = flags;
> > > > > > > >         gpuvm->ops = ops;
> > > > > > > > @@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm
> > > > > > > > *gpuvm, const char *name,
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuvm_init);
> > > > > > > > -/**
> > > > > > > > - * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
> > > > > > > > - * @gpuvm: pointer to the &drm_gpuvm to clean up
> > > > > > > > - *
> > > > > > > > - * Note that it is a bug to call this function on a
> > > > > > > > manager that still
> > > > > > > > - * holds GPU VA mappings.
> > > > > > > > - */
> > > > > > > > -void
> > > > > > > > -drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
> > > > > > > >     {
> > > > > > > >         gpuvm->name = NULL;
> > > > > > > > @@ -790,7 +785,33 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > > > > > > >         drm_gem_object_put(gpuvm->r_obj);
> > > > > > > >     }
> > > > > > > > -EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > > > > +
> > > > > > > > +static void
> > > > > > > > +drm_gpuvm_free(struct kref *kref)
> > > > > > > > +{
> > > > > > > > +    struct drm_gpuvm *gpuvm = container_of(kref, struct
> > > > > > > > drm_gpuvm, kref);
> > > > > > > > +
> > > > > > > > +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> > > > > > > > +        return;
> > > > > > > > +
> > > > > > > > +    drm_gpuvm_fini(gpuvm);
> > > > > > > > +
> > > > > > > > +    gpuvm->ops->vm_free(gpuvm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to release the reference of
> > > > > > > > + *
> > > > > > > > + * This releases a reference to @gpuvm.
> > > > > > > > + */
> > > > > > > > +void
> > > > > > > > +drm_gpuvm_put(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    if (gpuvm)
> > > > > > > > +        kref_put(&gpuvm->kref, drm_gpuvm_free);
> > > > > > > > +}
> > > > > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_put);
> > > > > > > >     static int
> > > > > > > >     __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > > @@ -843,7 +864,7 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > > > > > > >         if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
> > > > > > > >             return -EINVAL;
> > > > > > > > -    return __drm_gpuva_insert(gpuvm, va);
> > > > > > > > +    return __drm_gpuva_insert(drm_gpuvm_get(gpuvm), va);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_insert);
> > > > > > > > @@ -876,6 +897,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
> > > > > > > >         }
> > > > > > > >         __drm_gpuva_remove(va);
> > > > > > > > +    drm_gpuvm_put(va->vm);
> > > > > > > >     }
> > > > > > > >     EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > > > > > > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > index 54be12c1272f..cb2f06565c46 100644
> > > > > > > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > > > > > > @@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct
> > > > > > > > nouveau_bo *nvbo)
> > > > > > > >         }
> > > > > > > >     }
> > > > > > > > +static void
> > > > > > > > +nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
> > > > > > > > +
> > > > > > > > +    kfree(uvmm);
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static const struct drm_gpuvm_ops gpuvm_ops = {
> > > > > > > > +    .vm_free = nouveau_uvmm_free,
> > > > > > > > +};
> > > > > > > > +
> > > > > > > >     int
> > > > > > > >     nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
> > > > > > > >                    void *data,
> > > > > > > > @@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >                    NOUVEAU_VA_SPACE_END,
> > > > > > > >                    init->kernel_managed_addr,
> > > > > > > >                    init->kernel_managed_size,
> > > > > > > > -               NULL);
> > > > > > > > +               &gpuvm_ops);
> > > > > > > >         /* GPUVM takes care from here on. */
> > > > > > > >         drm_gem_object_put(r_obj);
> > > > > > > > @@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct
> > > > > > > > drm_device *dev,
> > > > > > > >         return 0;
> > > > > > > >     out_gpuvm_fini:
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >     out_unlock:
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >         return ret;
> > > > > > > > @@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
> > > > > > > >         mutex_lock(&cli->mutex);
> > > > > > > >         nouveau_vmm_fini(&uvmm->vmm);
> > > > > > > > -    drm_gpuvm_destroy(&uvmm->base);
> > > > > > > > -    kfree(uvmm);
> > > > > > > > +    drm_gpuvm_put(&uvmm->base);
> > > > > > > >         mutex_unlock(&cli->mutex);
> > > > > > > >     }
> > > > > > > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > > > > > > index 0c2e24155a93..4e6e1fd3485a 100644
> > > > > > > > --- a/include/drm/drm_gpuvm.h
> > > > > > > > +++ b/include/drm/drm_gpuvm.h
> > > > > > > > @@ -247,6 +247,11 @@ struct drm_gpuvm {
> > > > > > > >             struct list_head list;
> > > > > > > >         } rb;
> > > > > > > > +    /**
> > > > > > > > +     * @kref: reference count of this object
> > > > > > > > +     */
> > > > > > > > +    struct kref kref;
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @kernel_alloc_node:
> > > > > > > >          *
> > > > > > > > @@ -273,7 +278,23 @@ void drm_gpuvm_init(struct
> > > > > > > > drm_gpuvm *gpuvm, const char *name,
> > > > > > > >                 u64 start_offset, u64 range,
> > > > > > > >                 u64 reserve_offset, u64 reserve_range,
> > > > > > > >                 const struct drm_gpuvm_ops *ops);
> > > > > > > > -void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > > +/**
> > > > > > > > + * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
> > > > > > > > + * @gpuvm: the &drm_gpuvm to acquire the reference of
> > > > > > > > + *
> > > > > > > > + * This function acquires an additional reference to
> > > > > > > > @gpuvm. It is illegal to
> > > > > > > > + * call this without already holding a reference. No locks required.
> > > > > > > > + */
> > > > > > > > +static inline struct drm_gpuvm *
> > > > > > > > +drm_gpuvm_get(struct drm_gpuvm *gpuvm)
> > > > > > > > +{
> > > > > > > > +    kref_get(&gpuvm->kref);
> > > > > > > > +
> > > > > > > > +    return gpuvm;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
> > > > > > > >     bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
> > > > > > > > u64 addr, u64 range);
> > > > > > > >     bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > > > > > > > *gpuvm, u64 addr, u64 range);
> > > > > > > > @@ -673,6 +694,14 @@ static inline void
> > > > > > > > drm_gpuva_init_from_op(struct drm_gpuva *va,
> > > > > > > >      * operations to drivers.
> > > > > > > >      */
> > > > > > > >     struct drm_gpuvm_ops {
> > > > > > > > +    /**
> > > > > > > > +     * @vm_free: called when the last reference of a
> > > > > > > > struct drm_gpuvm is
> > > > > > > > +     * dropped
> > > > > > > > +     *
> > > > > > > > +     * This callback is mandatory.
> > > > > > > > +     */
> > > > > > > > +    void (*vm_free)(struct drm_gpuvm *gpuvm);
> > > > > > > > +
> > > > > > > >         /**
> > > > > > > >          * @op_alloc: called when the &drm_gpuvm allocates
> > > > > > > >          * a struct drm_gpuva_op


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06 14:11                   ` [Nouveau] " Danilo Krummrich
@ 2023-11-06 15:10                     ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06 15:10 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

[-- Attachment #1: Type: text/plain, Size: 4677 bytes --]

Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
> On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
>> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
>>> [SNIP]
>>> This reference count just prevents that the VM is freed as long as other
>>> ressources are attached to it that carry a VM pointer, such as mappings and
>>> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
>>> paranoid, but it doesn't hurt either and keeps it consistant.
>> Ah! Yeah, we have similar semantics in amdgpu as well.
>>
>> But we keep the reference to the root GEM object and not the VM.
>>
>> Ok, that makes much more sense then keeping one reference for each mapping.
>>
>>>> Because of this the mapping should *never* have a reference to the VM, but
>>>> rather the VM destroys all mapping when it is destroyed itself.
>>>>
>>>>> Hence, If the VM is still alive at a point where you don't expect it to
>>>>> be, then it's
>>>>> simply a driver bug.
>>>> Driver bugs is just what I try to prevent here. When individual mappings
>>>> keep the VM structure alive then drivers are responsible to clean them up,
>>>> if the VM cleans up after itself then we don't need to worry about it in the
>>>> driver.
>>> Drivers are *always* responsible for that. This has nothing to do with whether
>>> the VM is reference counted or not. GPUVM can't clean up mappings after itself.
>> Why not?
> I feel like we're talking past each other here, at least to some extend.
> However, I can't yet see where exactly the misunderstanding resides.

+1

>> At least in amdgpu we have it exactly like that. E.g. the higher level can
>> cleanup the BO_VM structure at any time possible, even when there are
>> mappings.
> What do you mean with "cleanup the VM_BO structue" exactly?
>
> The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
> being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
> the corresponding VM_BOs.
>
> Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
> should stay alive.

No, exactly the other way around. When the VM_BO structure is destroyed 
the mappings are destroyed with them.

Otherwise you would need to destroy each individual mapping separately 
before teardown which is quite inefficient.

>> The VM then keeps track which areas still need to be invalidated
>> in the physical representation of the page tables.
> And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
> the VM would just remove those structures on cleanup by itself, you'd loose the
> ability of cleaning up the page tables. Unless, you track this separately, which
> would make the whole tracking of GPUVM itself kinda pointless.

But how do you then keep track of areas which are freed and needs to be 
updated so that nobody can access the underlying memory any more?

>> I would expect that the generalized GPU VM handling would need something
>> similar. If we leave that to the driver then each driver would have to
>> implement that stuff on it's own again.
> Similar to what? What exactly do you think can be generalized here?

Similar to how amdgpu works.

 From what I can see you are basically re-inventing everything we 
already have in there and asking the same questions we stumbled over 
years ago.

>>> If the driver left mappings, GPUVM would just leak them without reference count.
>>> It doesn't know about the drivers surrounding structures, nor does it know about
>>> attached ressources such as PT(E)s.
>> What are we talking with the word "mapping"? The BO_VM structure? Or each
>> individual mapping?
> An individual mapping represented by struct drm_gpuva.

Yeah than that certainly doesn't work. See below.

>> E.g. what we need to prevent is that VM structure (or the root GEM object)
>> is released while VM_BOs are still around. That's what I totally agree on.
>>
>> But each individual mapping is a different story. Userspace can create so
>> many of them that we probably could even overrun a 32bit counter quite
>> easily.
> REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
> mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?

IIRC on amdgpu we can create something like 100k mappings per second and 
each takes ~64 bytes.

So you just need 128GiB of memory and approx 20 seconds to let the 
kernel run into a refcount overrun.

The worst I've seen in a real world game was around 19k mappings, but 
that doesn't mean that this here can't be exploited.

What can be done is to keep one reference per VM_BO structure, but I 
think per mapping is rather unrealistic.

Regards,
Christian.



[-- Attachment #2: Type: text/html, Size: 7322 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 15:10                     ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-06 15:10 UTC (permalink / raw)
  To: Danilo Krummrich
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

[-- Attachment #1: Type: text/plain, Size: 4677 bytes --]

Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
> On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
>> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
>>> [SNIP]
>>> This reference count just prevents that the VM is freed as long as other
>>> ressources are attached to it that carry a VM pointer, such as mappings and
>>> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
>>> paranoid, but it doesn't hurt either and keeps it consistant.
>> Ah! Yeah, we have similar semantics in amdgpu as well.
>>
>> But we keep the reference to the root GEM object and not the VM.
>>
>> Ok, that makes much more sense then keeping one reference for each mapping.
>>
>>>> Because of this the mapping should *never* have a reference to the VM, but
>>>> rather the VM destroys all mapping when it is destroyed itself.
>>>>
>>>>> Hence, If the VM is still alive at a point where you don't expect it to
>>>>> be, then it's
>>>>> simply a driver bug.
>>>> Driver bugs is just what I try to prevent here. When individual mappings
>>>> keep the VM structure alive then drivers are responsible to clean them up,
>>>> if the VM cleans up after itself then we don't need to worry about it in the
>>>> driver.
>>> Drivers are *always* responsible for that. This has nothing to do with whether
>>> the VM is reference counted or not. GPUVM can't clean up mappings after itself.
>> Why not?
> I feel like we're talking past each other here, at least to some extend.
> However, I can't yet see where exactly the misunderstanding resides.

+1

>> At least in amdgpu we have it exactly like that. E.g. the higher level can
>> cleanup the BO_VM structure at any time possible, even when there are
>> mappings.
> What do you mean with "cleanup the VM_BO structue" exactly?
>
> The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
> being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
> the corresponding VM_BOs.
>
> Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
> should stay alive.

No, exactly the other way around. When the VM_BO structure is destroyed 
the mappings are destroyed with them.

Otherwise you would need to destroy each individual mapping separately 
before teardown which is quite inefficient.

>> The VM then keeps track which areas still need to be invalidated
>> in the physical representation of the page tables.
> And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
> the VM would just remove those structures on cleanup by itself, you'd loose the
> ability of cleaning up the page tables. Unless, you track this separately, which
> would make the whole tracking of GPUVM itself kinda pointless.

But how do you then keep track of areas which are freed and needs to be 
updated so that nobody can access the underlying memory any more?

>> I would expect that the generalized GPU VM handling would need something
>> similar. If we leave that to the driver then each driver would have to
>> implement that stuff on it's own again.
> Similar to what? What exactly do you think can be generalized here?

Similar to how amdgpu works.

 From what I can see you are basically re-inventing everything we 
already have in there and asking the same questions we stumbled over 
years ago.

>>> If the driver left mappings, GPUVM would just leak them without reference count.
>>> It doesn't know about the drivers surrounding structures, nor does it know about
>>> attached ressources such as PT(E)s.
>> What are we talking with the word "mapping"? The BO_VM structure? Or each
>> individual mapping?
> An individual mapping represented by struct drm_gpuva.

Yeah than that certainly doesn't work. See below.

>> E.g. what we need to prevent is that VM structure (or the root GEM object)
>> is released while VM_BOs are still around. That's what I totally agree on.
>>
>> But each individual mapping is a different story. Userspace can create so
>> many of them that we probably could even overrun a 32bit counter quite
>> easily.
> REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
> mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?

IIRC on amdgpu we can create something like 100k mappings per second and 
each takes ~64 bytes.

So you just need 128GiB of memory and approx 20 seconds to let the 
kernel run into a refcount overrun.

The worst I've seen in a real world game was around 19k mappings, but 
that doesn't mean that this here can't be exploited.

What can be done is to keep one reference per VM_BO structure, but I 
think per mapping is rather unrealistic.

Regards,
Christian.



[-- Attachment #2: Type: text/html, Size: 7322 bytes --]

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06 15:10                     ` Christian König
  (?)
@ 2023-11-06 16:42                       ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 16:42 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson,
	daniel

On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
> > On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> > > Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > > > [SNIP]
> > > > This reference count just prevents that the VM is freed as long as other
> > > > ressources are attached to it that carry a VM pointer, such as mappings and
> > > > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > > > paranoid, but it doesn't hurt either and keeps it consistant.
> > > Ah! Yeah, we have similar semantics in amdgpu as well.
> > > 
> > > But we keep the reference to the root GEM object and not the VM.
> > > 
> > > Ok, that makes much more sense then keeping one reference for each mapping.
> > > 
> > > > > Because of this the mapping should *never* have a reference to the VM, but
> > > > > rather the VM destroys all mapping when it is destroyed itself.
> > > > > 
> > > > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > > > be, then it's
> > > > > > simply a driver bug.
> > > > > Driver bugs is just what I try to prevent here. When individual mappings
> > > > > keep the VM structure alive then drivers are responsible to clean them up,
> > > > > if the VM cleans up after itself then we don't need to worry about it in the
> > > > > driver.
> > > > Drivers are *always* responsible for that. This has nothing to do with whether
> > > > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> > > Why not?
> > I feel like we're talking past each other here, at least to some extend.
> > However, I can't yet see where exactly the misunderstanding resides.
> 
> +1
> 
> > > At least in amdgpu we have it exactly like that. E.g. the higher level can
> > > cleanup the BO_VM structure at any time possible, even when there are
> > > mappings.
> > What do you mean with "cleanup the VM_BO structue" exactly?
> > 
> > The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
> > being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
> > the corresponding VM_BOs.
> > 
> > Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
> > should stay alive.
> 
> No, exactly the other way around. When the VM_BO structure is destroyed the
> mappings are destroyed with them.

This seems to be the same misunderstanding as with the VM reference count.

It seems to me that you want to say that for amdgpu it seems to be a use-case
to get rid of all mappings backed by a given BO and mapped in a given VM, hence
a VM_BO. You can do that. Thers's even a helper for that in GPUVM.

But also in this case you first need to get rid of all mappings before you
*free* the VM_BO - GPUVM ensures that.

> 
> Otherwise you would need to destroy each individual mapping separately
> before teardown which is quite inefficient.

Not sure what you mean, but I don't see a difference between walking all VM_BOs
and removing their mappings and walking the VM's tree of mappings and removing
each of them. Comes down to the same effort in the end. But surely can go both
ways if you know all the existing VM_BOs.

> 
> > > The VM then keeps track which areas still need to be invalidated
> > > in the physical representation of the page tables.
> > And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
> > the VM would just remove those structures on cleanup by itself, you'd loose the
> > ability of cleaning up the page tables. Unless, you track this separately, which
> > would make the whole tracking of GPUVM itself kinda pointless.
> 
> But how do you then keep track of areas which are freed and needs to be
> updated so that nobody can access the underlying memory any more?

"areas which are freed", what do refer to? What do yo mean with that?

Do you mean areas of the VA space not containing mappings? Why would I need to
track them explicitly? When the mapping is removed the corresponding page tables
/ page table entries are gone as well, hence no subsequent access to the
underlaying memory would be possible.

> 
> > > I would expect that the generalized GPU VM handling would need something
> > > similar. If we leave that to the driver then each driver would have to
> > > implement that stuff on it's own again.
> > Similar to what? What exactly do you think can be generalized here?
> 
> Similar to how amdgpu works.

I don't think it's quite fair to just throw the "look at what amdgpu does"
argument at me. What am I supposed to do? Read and understand *every* detail of
*every* driver?

Did you read through the GPUVM code? That's a honest question and I'm asking it
because I feel like you're picking up some details from commit messages and
start questioning them (and that's perfectly fine and absolutely welcome).

But if the answers don't satisfy you or do not lead to a better understanding it
just seems you ask others to check out amdgpu rather than taking the time to go
though the proposed code yourself making suggestions to improve it or explicitly
point out the changes you require.

> 
> From what I can see you are basically re-inventing everything we already
> have in there and asking the same questions we stumbled over years ago.

I did not ask any questions in the first place. I came up with something that
Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.

They also all provided a lot of ideas and contributed code through the review
process.

Of course, I want this to work for amdgpu as well. So, if you think we're
missing something fundamential or if you see something that simply doesn't work
for other drivers, like amdgpu, please educate us. I'm surely willing to learn
and, if required, change the code.

But please don't just tell me I would re-invent amdgpu and assume that I know
all the details of this driver. None of that is reasonable.

> 
> > > > If the driver left mappings, GPUVM would just leak them without reference count.
> > > > It doesn't know about the drivers surrounding structures, nor does it know about
> > > > attached ressources such as PT(E)s.
> > > What are we talking with the word "mapping"? The BO_VM structure? Or each
> > > individual mapping?
> > An individual mapping represented by struct drm_gpuva.
> 
> Yeah than that certainly doesn't work. See below.
> 
> > > E.g. what we need to prevent is that VM structure (or the root GEM object)
> > > is released while VM_BOs are still around. That's what I totally agree on.
> > > 
> > > But each individual mapping is a different story. Userspace can create so
> > > many of them that we probably could even overrun a 32bit counter quite
> > > easily.
> > REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
> > mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
> 
> IIRC on amdgpu we can create something like 100k mappings per second and
> each takes ~64 bytes.
> 
> So you just need 128GiB of memory and approx 20 seconds to let the kernel
> run into a refcount overrun.

100.000 * 20 = 2.000.000

That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
20.000s if we can keep the pace and have enough memory. Also, it's not only the
mapping structures itself, it's also page tables, userspace structures, etc.

Again, is the number of ~2.15 Billion mappings something we really need to worry
about?

I'm still not convinced about that. But I think we can also just cap GPUVM at,
let's say, 1 Billion mappings?

> 
> The worst I've seen in a real world game was around 19k mappings, but that
> doesn't mean that this here can't be exploited.
> 
> What can be done is to keep one reference per VM_BO structure, but I think
> per mapping is rather unrealistic.
> 
> Regards,
> Christian.
> 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 16:42                       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 16:42 UTC (permalink / raw)
  To: Christian König
  Cc: matthew.brost, thomas.hellstrom, sarah.walker, nouveau,
	linux-kernel, dri-devel, faith, boris.brezillon, donald.robson

On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
> > On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> > > Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > > > [SNIP]
> > > > This reference count just prevents that the VM is freed as long as other
> > > > ressources are attached to it that carry a VM pointer, such as mappings and
> > > > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > > > paranoid, but it doesn't hurt either and keeps it consistant.
> > > Ah! Yeah, we have similar semantics in amdgpu as well.
> > > 
> > > But we keep the reference to the root GEM object and not the VM.
> > > 
> > > Ok, that makes much more sense then keeping one reference for each mapping.
> > > 
> > > > > Because of this the mapping should *never* have a reference to the VM, but
> > > > > rather the VM destroys all mapping when it is destroyed itself.
> > > > > 
> > > > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > > > be, then it's
> > > > > > simply a driver bug.
> > > > > Driver bugs is just what I try to prevent here. When individual mappings
> > > > > keep the VM structure alive then drivers are responsible to clean them up,
> > > > > if the VM cleans up after itself then we don't need to worry about it in the
> > > > > driver.
> > > > Drivers are *always* responsible for that. This has nothing to do with whether
> > > > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> > > Why not?
> > I feel like we're talking past each other here, at least to some extend.
> > However, I can't yet see where exactly the misunderstanding resides.
> 
> +1
> 
> > > At least in amdgpu we have it exactly like that. E.g. the higher level can
> > > cleanup the BO_VM structure at any time possible, even when there are
> > > mappings.
> > What do you mean with "cleanup the VM_BO structue" exactly?
> > 
> > The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
> > being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
> > the corresponding VM_BOs.
> > 
> > Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
> > should stay alive.
> 
> No, exactly the other way around. When the VM_BO structure is destroyed the
> mappings are destroyed with them.

This seems to be the same misunderstanding as with the VM reference count.

It seems to me that you want to say that for amdgpu it seems to be a use-case
to get rid of all mappings backed by a given BO and mapped in a given VM, hence
a VM_BO. You can do that. Thers's even a helper for that in GPUVM.

But also in this case you first need to get rid of all mappings before you
*free* the VM_BO - GPUVM ensures that.

> 
> Otherwise you would need to destroy each individual mapping separately
> before teardown which is quite inefficient.

Not sure what you mean, but I don't see a difference between walking all VM_BOs
and removing their mappings and walking the VM's tree of mappings and removing
each of them. Comes down to the same effort in the end. But surely can go both
ways if you know all the existing VM_BOs.

> 
> > > The VM then keeps track which areas still need to be invalidated
> > > in the physical representation of the page tables.
> > And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
> > the VM would just remove those structures on cleanup by itself, you'd loose the
> > ability of cleaning up the page tables. Unless, you track this separately, which
> > would make the whole tracking of GPUVM itself kinda pointless.
> 
> But how do you then keep track of areas which are freed and needs to be
> updated so that nobody can access the underlying memory any more?

"areas which are freed", what do refer to? What do yo mean with that?

Do you mean areas of the VA space not containing mappings? Why would I need to
track them explicitly? When the mapping is removed the corresponding page tables
/ page table entries are gone as well, hence no subsequent access to the
underlaying memory would be possible.

> 
> > > I would expect that the generalized GPU VM handling would need something
> > > similar. If we leave that to the driver then each driver would have to
> > > implement that stuff on it's own again.
> > Similar to what? What exactly do you think can be generalized here?
> 
> Similar to how amdgpu works.

I don't think it's quite fair to just throw the "look at what amdgpu does"
argument at me. What am I supposed to do? Read and understand *every* detail of
*every* driver?

Did you read through the GPUVM code? That's a honest question and I'm asking it
because I feel like you're picking up some details from commit messages and
start questioning them (and that's perfectly fine and absolutely welcome).

But if the answers don't satisfy you or do not lead to a better understanding it
just seems you ask others to check out amdgpu rather than taking the time to go
though the proposed code yourself making suggestions to improve it or explicitly
point out the changes you require.

> 
> From what I can see you are basically re-inventing everything we already
> have in there and asking the same questions we stumbled over years ago.

I did not ask any questions in the first place. I came up with something that
Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.

They also all provided a lot of ideas and contributed code through the review
process.

Of course, I want this to work for amdgpu as well. So, if you think we're
missing something fundamential or if you see something that simply doesn't work
for other drivers, like amdgpu, please educate us. I'm surely willing to learn
and, if required, change the code.

But please don't just tell me I would re-invent amdgpu and assume that I know
all the details of this driver. None of that is reasonable.

> 
> > > > If the driver left mappings, GPUVM would just leak them without reference count.
> > > > It doesn't know about the drivers surrounding structures, nor does it know about
> > > > attached ressources such as PT(E)s.
> > > What are we talking with the word "mapping"? The BO_VM structure? Or each
> > > individual mapping?
> > An individual mapping represented by struct drm_gpuva.
> 
> Yeah than that certainly doesn't work. See below.
> 
> > > E.g. what we need to prevent is that VM structure (or the root GEM object)
> > > is released while VM_BOs are still around. That's what I totally agree on.
> > > 
> > > But each individual mapping is a different story. Userspace can create so
> > > many of them that we probably could even overrun a 32bit counter quite
> > > easily.
> > REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
> > mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
> 
> IIRC on amdgpu we can create something like 100k mappings per second and
> each takes ~64 bytes.
> 
> So you just need 128GiB of memory and approx 20 seconds to let the kernel
> run into a refcount overrun.

100.000 * 20 = 2.000.000

That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
20.000s if we can keep the pace and have enough memory. Also, it's not only the
mapping structures itself, it's also page tables, userspace structures, etc.

Again, is the number of ~2.15 Billion mappings something we really need to worry
about?

I'm still not convinced about that. But I think we can also just cap GPUVM at,
let's say, 1 Billion mappings?

> 
> The worst I've seen in a real world game was around 19k mappings, but that
> doesn't mean that this here can't be exploited.
> 
> What can be done is to keep one reference per VM_BO structure, but I think
> per mapping is rather unrealistic.
> 
> Regards,
> Christian.
> 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-06 16:42                       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-06 16:42 UTC (permalink / raw)
  To: Christian König
  Cc: airlied, daniel, matthew.brost, thomas.hellstrom, sarah.walker,
	donald.robson, boris.brezillon, faith, dri-devel, nouveau,
	linux-kernel

On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
> > On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
> > > Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
> > > > [SNIP]
> > > > This reference count just prevents that the VM is freed as long as other
> > > > ressources are attached to it that carry a VM pointer, such as mappings and
> > > > VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
> > > > paranoid, but it doesn't hurt either and keeps it consistant.
> > > Ah! Yeah, we have similar semantics in amdgpu as well.
> > > 
> > > But we keep the reference to the root GEM object and not the VM.
> > > 
> > > Ok, that makes much more sense then keeping one reference for each mapping.
> > > 
> > > > > Because of this the mapping should *never* have a reference to the VM, but
> > > > > rather the VM destroys all mapping when it is destroyed itself.
> > > > > 
> > > > > > Hence, If the VM is still alive at a point where you don't expect it to
> > > > > > be, then it's
> > > > > > simply a driver bug.
> > > > > Driver bugs is just what I try to prevent here. When individual mappings
> > > > > keep the VM structure alive then drivers are responsible to clean them up,
> > > > > if the VM cleans up after itself then we don't need to worry about it in the
> > > > > driver.
> > > > Drivers are *always* responsible for that. This has nothing to do with whether
> > > > the VM is reference counted or not. GPUVM can't clean up mappings after itself.
> > > Why not?
> > I feel like we're talking past each other here, at least to some extend.
> > However, I can't yet see where exactly the misunderstanding resides.
> 
> +1
> 
> > > At least in amdgpu we have it exactly like that. E.g. the higher level can
> > > cleanup the BO_VM structure at any time possible, even when there are
> > > mappings.
> > What do you mean with "cleanup the VM_BO structue" exactly?
> > 
> > The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
> > being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
> > the corresponding VM_BOs.
> > 
> > Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
> > should stay alive.
> 
> No, exactly the other way around. When the VM_BO structure is destroyed the
> mappings are destroyed with them.

This seems to be the same misunderstanding as with the VM reference count.

It seems to me that you want to say that for amdgpu it seems to be a use-case
to get rid of all mappings backed by a given BO and mapped in a given VM, hence
a VM_BO. You can do that. Thers's even a helper for that in GPUVM.

But also in this case you first need to get rid of all mappings before you
*free* the VM_BO - GPUVM ensures that.

> 
> Otherwise you would need to destroy each individual mapping separately
> before teardown which is quite inefficient.

Not sure what you mean, but I don't see a difference between walking all VM_BOs
and removing their mappings and walking the VM's tree of mappings and removing
each of them. Comes down to the same effort in the end. But surely can go both
ways if you know all the existing VM_BOs.

> 
> > > The VM then keeps track which areas still need to be invalidated
> > > in the physical representation of the page tables.
> > And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
> > the VM would just remove those structures on cleanup by itself, you'd loose the
> > ability of cleaning up the page tables. Unless, you track this separately, which
> > would make the whole tracking of GPUVM itself kinda pointless.
> 
> But how do you then keep track of areas which are freed and needs to be
> updated so that nobody can access the underlying memory any more?

"areas which are freed", what do refer to? What do yo mean with that?

Do you mean areas of the VA space not containing mappings? Why would I need to
track them explicitly? When the mapping is removed the corresponding page tables
/ page table entries are gone as well, hence no subsequent access to the
underlaying memory would be possible.

> 
> > > I would expect that the generalized GPU VM handling would need something
> > > similar. If we leave that to the driver then each driver would have to
> > > implement that stuff on it's own again.
> > Similar to what? What exactly do you think can be generalized here?
> 
> Similar to how amdgpu works.

I don't think it's quite fair to just throw the "look at what amdgpu does"
argument at me. What am I supposed to do? Read and understand *every* detail of
*every* driver?

Did you read through the GPUVM code? That's a honest question and I'm asking it
because I feel like you're picking up some details from commit messages and
start questioning them (and that's perfectly fine and absolutely welcome).

But if the answers don't satisfy you or do not lead to a better understanding it
just seems you ask others to check out amdgpu rather than taking the time to go
though the proposed code yourself making suggestions to improve it or explicitly
point out the changes you require.

> 
> From what I can see you are basically re-inventing everything we already
> have in there and asking the same questions we stumbled over years ago.

I did not ask any questions in the first place. I came up with something that
Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.

They also all provided a lot of ideas and contributed code through the review
process.

Of course, I want this to work for amdgpu as well. So, if you think we're
missing something fundamential or if you see something that simply doesn't work
for other drivers, like amdgpu, please educate us. I'm surely willing to learn
and, if required, change the code.

But please don't just tell me I would re-invent amdgpu and assume that I know
all the details of this driver. None of that is reasonable.

> 
> > > > If the driver left mappings, GPUVM would just leak them without reference count.
> > > > It doesn't know about the drivers surrounding structures, nor does it know about
> > > > attached ressources such as PT(E)s.
> > > What are we talking with the word "mapping"? The BO_VM structure? Or each
> > > individual mapping?
> > An individual mapping represented by struct drm_gpuva.
> 
> Yeah than that certainly doesn't work. See below.
> 
> > > E.g. what we need to prevent is that VM structure (or the root GEM object)
> > > is released while VM_BOs are still around. That's what I totally agree on.
> > > 
> > > But each individual mapping is a different story. Userspace can create so
> > > many of them that we probably could even overrun a 32bit counter quite
> > > easily.
> > REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
> > mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
> 
> IIRC on amdgpu we can create something like 100k mappings per second and
> each takes ~64 bytes.
> 
> So you just need 128GiB of memory and approx 20 seconds to let the kernel
> run into a refcount overrun.

100.000 * 20 = 2.000.000

That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
20.000s if we can keep the pace and have enough memory. Also, it's not only the
mapping structures itself, it's also page tables, userspace structures, etc.

Again, is the number of ~2.15 Billion mappings something we really need to worry
about?

I'm still not convinced about that. But I think we can also just cap GPUVM at,
let's say, 1 Billion mappings?

> 
> The worst I've seen in a real world game was around 19k mappings, but that
> doesn't mean that this here can't be exploited.
> 
> What can be done is to keep one reference per VM_BO structure, but I think
> per mapping is rather unrealistic.
> 
> Regards,
> Christian.
> 
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-06 16:42                       ` Danilo Krummrich
  (?)
@ 2023-11-09 15:50                         ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-09 15:50 UTC (permalink / raw)
  To: Danilo Krummrich, Christian König
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

Danilo, Christian

On 11/6/23 17:42, Danilo Krummrich wrote:
> On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
>> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
>>> On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
>>>> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
>>>>> [SNIP]
>>>>> This reference count just prevents that the VM is freed as long as other
>>>>> ressources are attached to it that carry a VM pointer, such as mappings and
>>>>> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
>>>>> paranoid, but it doesn't hurt either and keeps it consistant.
>>>> Ah! Yeah, we have similar semantics in amdgpu as well.
>>>>
>>>> But we keep the reference to the root GEM object and not the VM.
>>>>
>>>> Ok, that makes much more sense then keeping one reference for each mapping.
>>>>
>>>>>> Because of this the mapping should *never* have a reference to the VM, but
>>>>>> rather the VM destroys all mapping when it is destroyed itself.
>>>>>>
>>>>>>> Hence, If the VM is still alive at a point where you don't expect it to
>>>>>>> be, then it's
>>>>>>> simply a driver bug.
>>>>>> Driver bugs is just what I try to prevent here. When individual mappings
>>>>>> keep the VM structure alive then drivers are responsible to clean them up,
>>>>>> if the VM cleans up after itself then we don't need to worry about it in the
>>>>>> driver.
>>>>> Drivers are *always* responsible for that. This has nothing to do with whether
>>>>> the VM is reference counted or not. GPUVM can't clean up mappings after itself.
>>>> Why not?
>>> I feel like we're talking past each other here, at least to some extend.
>>> However, I can't yet see where exactly the misunderstanding resides.
>> +1
>>
>>>> At least in amdgpu we have it exactly like that. E.g. the higher level can
>>>> cleanup the BO_VM structure at any time possible, even when there are
>>>> mappings.
>>> What do you mean with "cleanup the VM_BO structue" exactly?
>>>
>>> The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
>>> being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
>>> the corresponding VM_BOs.
>>>
>>> Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
>>> should stay alive.
>> No, exactly the other way around. When the VM_BO structure is destroyed the
>> mappings are destroyed with them.
> This seems to be the same misunderstanding as with the VM reference count.
>
> It seems to me that you want to say that for amdgpu it seems to be a use-case
> to get rid of all mappings backed by a given BO and mapped in a given VM, hence
> a VM_BO. You can do that. Thers's even a helper for that in GPUVM.
>
> But also in this case you first need to get rid of all mappings before you
> *free* the VM_BO - GPUVM ensures that.
>
>> Otherwise you would need to destroy each individual mapping separately
>> before teardown which is quite inefficient.
> Not sure what you mean, but I don't see a difference between walking all VM_BOs
> and removing their mappings and walking the VM's tree of mappings and removing
> each of them. Comes down to the same effort in the end. But surely can go both
> ways if you know all the existing VM_BOs.
>
>>>> The VM then keeps track which areas still need to be invalidated
>>>> in the physical representation of the page tables.
>>> And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
>>> the VM would just remove those structures on cleanup by itself, you'd loose the
>>> ability of cleaning up the page tables. Unless, you track this separately, which
>>> would make the whole tracking of GPUVM itself kinda pointless.
>> But how do you then keep track of areas which are freed and needs to be
>> updated so that nobody can access the underlying memory any more?
> "areas which are freed", what do refer to? What do yo mean with that?
>
> Do you mean areas of the VA space not containing mappings? Why would I need to
> track them explicitly? When the mapping is removed the corresponding page tables
> / page table entries are gone as well, hence no subsequent access to the
> underlaying memory would be possible.
>
>>>> I would expect that the generalized GPU VM handling would need something
>>>> similar. If we leave that to the driver then each driver would have to
>>>> implement that stuff on it's own again.
>>> Similar to what? What exactly do you think can be generalized here?
>> Similar to how amdgpu works.
> I don't think it's quite fair to just throw the "look at what amdgpu does"
> argument at me. What am I supposed to do? Read and understand *every* detail of
> *every* driver?
>
> Did you read through the GPUVM code? That's a honest question and I'm asking it
> because I feel like you're picking up some details from commit messages and
> start questioning them (and that's perfectly fine and absolutely welcome).
>
> But if the answers don't satisfy you or do not lead to a better understanding it
> just seems you ask others to check out amdgpu rather than taking the time to go
> though the proposed code yourself making suggestions to improve it or explicitly
> point out the changes you require.
>
>>  From what I can see you are basically re-inventing everything we already
>> have in there and asking the same questions we stumbled over years ago.
> I did not ask any questions in the first place. I came up with something that
> Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.
>
> They also all provided a lot of ideas and contributed code through the review
> process.
>
> Of course, I want this to work for amdgpu as well. So, if you think we're
> missing something fundamential or if you see something that simply doesn't work
> for other drivers, like amdgpu, please educate us. I'm surely willing to learn
> and, if required, change the code.
>
> But please don't just tell me I would re-invent amdgpu and assume that I know
> all the details of this driver. None of that is reasonable.
>
>>>>> If the driver left mappings, GPUVM would just leak them without reference count.
>>>>> It doesn't know about the drivers surrounding structures, nor does it know about
>>>>> attached ressources such as PT(E)s.
>>>> What are we talking with the word "mapping"? The BO_VM structure? Or each
>>>> individual mapping?
>>> An individual mapping represented by struct drm_gpuva.
>> Yeah than that certainly doesn't work. See below.
>>
>>>> E.g. what we need to prevent is that VM structure (or the root GEM object)
>>>> is released while VM_BOs are still around. That's what I totally agree on.
>>>>
>>>> But each individual mapping is a different story. Userspace can create so
>>>> many of them that we probably could even overrun a 32bit counter quite
>>>> easily.
>>> REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
>>> mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
>> IIRC on amdgpu we can create something like 100k mappings per second and
>> each takes ~64 bytes.
>>
>> So you just need 128GiB of memory and approx 20 seconds to let the kernel
>> run into a refcount overrun.
> 100.000 * 20 = 2.000.000
>
> That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
> 20.000s if we can keep the pace and have enough memory. Also, it's not only the
> mapping structures itself, it's also page tables, userspace structures, etc.
>
> Again, is the number of ~2.15 Billion mappings something we really need to worry
> about?
>
> I'm still not convinced about that. But I think we can also just cap GPUVM at,
> let's say, 1 Billion mappings?
>
>> The worst I've seen in a real world game was around 19k mappings, but that
>> doesn't mean that this here can't be exploited.
>>
>> What can be done is to keep one reference per VM_BO structure, but I think
>> per mapping is rather unrealistic.
>>
>> Regards,
>> Christian.
>>
>>
Did we get any resolution on this?

FWIW, my take on this is that it would be possible to get GPUVM to work 
both with and without internal refcounting; If with, the driver needs a 
vm close to resolve cyclic references, if without that's not necessary. 
If GPUVM is allowed to refcount in mappings and vm_bos, that comes with 
a slight performance drop but as Danilo pointed out, the VM lifetime 
problem iterating over a vm_bo's mapping becomes much easier and the 
code thus becomes easier to maintain moving forward. That convinced me 
it's a good thing.

Another issue Christian brought up is that something intended to be 
embeddable (a base class) shouldn't really have its own refcount. I 
think that's a valid point. If you at some point need to derive from 
multiple such structs each having its own refcount, things will start to 
get weird. One way to resolve that would be to have the driver's 
subclass provide get() and put() ops, and export a destructor for the 
base-class, rather than to have the base-class provide the refcount and 
a destructor  ops.

That would also make it possible for the driver to decide the context 
for the put() call: If the driver needs to be able to call put() from 
irq / atomic context but the base-class'es destructor doesn't allow 
atomic context, the driver can push freeing out to a work item if needed.

Finally, the refcount overflow Christian pointed out. Limiting the 
number of mapping sounds like a reasonable remedy to me.

But I think all of this is fixable as follow-ups if needed, unless I'm 
missing something crucial.

Just my 2 cents.

/Thomas



^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 15:50                         ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-09 15:50 UTC (permalink / raw)
  To: Danilo Krummrich, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

Danilo, Christian

On 11/6/23 17:42, Danilo Krummrich wrote:
> On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
>> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
>>> On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
>>>> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
>>>>> [SNIP]
>>>>> This reference count just prevents that the VM is freed as long as other
>>>>> ressources are attached to it that carry a VM pointer, such as mappings and
>>>>> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
>>>>> paranoid, but it doesn't hurt either and keeps it consistant.
>>>> Ah! Yeah, we have similar semantics in amdgpu as well.
>>>>
>>>> But we keep the reference to the root GEM object and not the VM.
>>>>
>>>> Ok, that makes much more sense then keeping one reference for each mapping.
>>>>
>>>>>> Because of this the mapping should *never* have a reference to the VM, but
>>>>>> rather the VM destroys all mapping when it is destroyed itself.
>>>>>>
>>>>>>> Hence, If the VM is still alive at a point where you don't expect it to
>>>>>>> be, then it's
>>>>>>> simply a driver bug.
>>>>>> Driver bugs is just what I try to prevent here. When individual mappings
>>>>>> keep the VM structure alive then drivers are responsible to clean them up,
>>>>>> if the VM cleans up after itself then we don't need to worry about it in the
>>>>>> driver.
>>>>> Drivers are *always* responsible for that. This has nothing to do with whether
>>>>> the VM is reference counted or not. GPUVM can't clean up mappings after itself.
>>>> Why not?
>>> I feel like we're talking past each other here, at least to some extend.
>>> However, I can't yet see where exactly the misunderstanding resides.
>> +1
>>
>>>> At least in amdgpu we have it exactly like that. E.g. the higher level can
>>>> cleanup the BO_VM structure at any time possible, even when there are
>>>> mappings.
>>> What do you mean with "cleanup the VM_BO structue" exactly?
>>>
>>> The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
>>> being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
>>> the corresponding VM_BOs.
>>>
>>> Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
>>> should stay alive.
>> No, exactly the other way around. When the VM_BO structure is destroyed the
>> mappings are destroyed with them.
> This seems to be the same misunderstanding as with the VM reference count.
>
> It seems to me that you want to say that for amdgpu it seems to be a use-case
> to get rid of all mappings backed by a given BO and mapped in a given VM, hence
> a VM_BO. You can do that. Thers's even a helper for that in GPUVM.
>
> But also in this case you first need to get rid of all mappings before you
> *free* the VM_BO - GPUVM ensures that.
>
>> Otherwise you would need to destroy each individual mapping separately
>> before teardown which is quite inefficient.
> Not sure what you mean, but I don't see a difference between walking all VM_BOs
> and removing their mappings and walking the VM's tree of mappings and removing
> each of them. Comes down to the same effort in the end. But surely can go both
> ways if you know all the existing VM_BOs.
>
>>>> The VM then keeps track which areas still need to be invalidated
>>>> in the physical representation of the page tables.
>>> And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
>>> the VM would just remove those structures on cleanup by itself, you'd loose the
>>> ability of cleaning up the page tables. Unless, you track this separately, which
>>> would make the whole tracking of GPUVM itself kinda pointless.
>> But how do you then keep track of areas which are freed and needs to be
>> updated so that nobody can access the underlying memory any more?
> "areas which are freed", what do refer to? What do yo mean with that?
>
> Do you mean areas of the VA space not containing mappings? Why would I need to
> track them explicitly? When the mapping is removed the corresponding page tables
> / page table entries are gone as well, hence no subsequent access to the
> underlaying memory would be possible.
>
>>>> I would expect that the generalized GPU VM handling would need something
>>>> similar. If we leave that to the driver then each driver would have to
>>>> implement that stuff on it's own again.
>>> Similar to what? What exactly do you think can be generalized here?
>> Similar to how amdgpu works.
> I don't think it's quite fair to just throw the "look at what amdgpu does"
> argument at me. What am I supposed to do? Read and understand *every* detail of
> *every* driver?
>
> Did you read through the GPUVM code? That's a honest question and I'm asking it
> because I feel like you're picking up some details from commit messages and
> start questioning them (and that's perfectly fine and absolutely welcome).
>
> But if the answers don't satisfy you or do not lead to a better understanding it
> just seems you ask others to check out amdgpu rather than taking the time to go
> though the proposed code yourself making suggestions to improve it or explicitly
> point out the changes you require.
>
>>  From what I can see you are basically re-inventing everything we already
>> have in there and asking the same questions we stumbled over years ago.
> I did not ask any questions in the first place. I came up with something that
> Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.
>
> They also all provided a lot of ideas and contributed code through the review
> process.
>
> Of course, I want this to work for amdgpu as well. So, if you think we're
> missing something fundamential or if you see something that simply doesn't work
> for other drivers, like amdgpu, please educate us. I'm surely willing to learn
> and, if required, change the code.
>
> But please don't just tell me I would re-invent amdgpu and assume that I know
> all the details of this driver. None of that is reasonable.
>
>>>>> If the driver left mappings, GPUVM would just leak them without reference count.
>>>>> It doesn't know about the drivers surrounding structures, nor does it know about
>>>>> attached ressources such as PT(E)s.
>>>> What are we talking with the word "mapping"? The BO_VM structure? Or each
>>>> individual mapping?
>>> An individual mapping represented by struct drm_gpuva.
>> Yeah than that certainly doesn't work. See below.
>>
>>>> E.g. what we need to prevent is that VM structure (or the root GEM object)
>>>> is released while VM_BOs are still around. That's what I totally agree on.
>>>>
>>>> But each individual mapping is a different story. Userspace can create so
>>>> many of them that we probably could even overrun a 32bit counter quite
>>>> easily.
>>> REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
>>> mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
>> IIRC on amdgpu we can create something like 100k mappings per second and
>> each takes ~64 bytes.
>>
>> So you just need 128GiB of memory and approx 20 seconds to let the kernel
>> run into a refcount overrun.
> 100.000 * 20 = 2.000.000
>
> That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
> 20.000s if we can keep the pace and have enough memory. Also, it's not only the
> mapping structures itself, it's also page tables, userspace structures, etc.
>
> Again, is the number of ~2.15 Billion mappings something we really need to worry
> about?
>
> I'm still not convinced about that. But I think we can also just cap GPUVM at,
> let's say, 1 Billion mappings?
>
>> The worst I've seen in a real world game was around 19k mappings, but that
>> doesn't mean that this here can't be exploited.
>>
>> What can be done is to keep one reference per VM_BO structure, but I think
>> per mapping is rather unrealistic.
>>
>> Regards,
>> Christian.
>>
>>
Did we get any resolution on this?

FWIW, my take on this is that it would be possible to get GPUVM to work 
both with and without internal refcounting; If with, the driver needs a 
vm close to resolve cyclic references, if without that's not necessary. 
If GPUVM is allowed to refcount in mappings and vm_bos, that comes with 
a slight performance drop but as Danilo pointed out, the VM lifetime 
problem iterating over a vm_bo's mapping becomes much easier and the 
code thus becomes easier to maintain moving forward. That convinced me 
it's a good thing.

Another issue Christian brought up is that something intended to be 
embeddable (a base class) shouldn't really have its own refcount. I 
think that's a valid point. If you at some point need to derive from 
multiple such structs each having its own refcount, things will start to 
get weird. One way to resolve that would be to have the driver's 
subclass provide get() and put() ops, and export a destructor for the 
base-class, rather than to have the base-class provide the refcount and 
a destructor  ops.

That would also make it possible for the driver to decide the context 
for the put() call: If the driver needs to be able to call put() from 
irq / atomic context but the base-class'es destructor doesn't allow 
atomic context, the driver can push freeing out to a work item if needed.

Finally, the refcount overflow Christian pointed out. Limiting the 
number of mapping sounds like a reasonable remedy to me.

But I think all of this is fixable as follow-ups if needed, unless I'm 
missing something crucial.

Just my 2 cents.

/Thomas



^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 15:50                         ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-09 15:50 UTC (permalink / raw)
  To: Danilo Krummrich, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

Danilo, Christian

On 11/6/23 17:42, Danilo Krummrich wrote:
> On Mon, Nov 06, 2023 at 04:10:50PM +0100, Christian König wrote:
>> Am 06.11.23 um 15:11 schrieb Danilo Krummrich:
>>> On Mon, Nov 06, 2023 at 02:05:13PM +0100, Christian König wrote:
>>>> Am 06.11.23 um 13:16 schrieb Danilo Krummrich:
>>>>> [SNIP]
>>>>> This reference count just prevents that the VM is freed as long as other
>>>>> ressources are attached to it that carry a VM pointer, such as mappings and
>>>>> VM_BOs. The motivation for that are VM_BOs. For mappings it's indeed a bit
>>>>> paranoid, but it doesn't hurt either and keeps it consistant.
>>>> Ah! Yeah, we have similar semantics in amdgpu as well.
>>>>
>>>> But we keep the reference to the root GEM object and not the VM.
>>>>
>>>> Ok, that makes much more sense then keeping one reference for each mapping.
>>>>
>>>>>> Because of this the mapping should *never* have a reference to the VM, but
>>>>>> rather the VM destroys all mapping when it is destroyed itself.
>>>>>>
>>>>>>> Hence, If the VM is still alive at a point where you don't expect it to
>>>>>>> be, then it's
>>>>>>> simply a driver bug.
>>>>>> Driver bugs is just what I try to prevent here. When individual mappings
>>>>>> keep the VM structure alive then drivers are responsible to clean them up,
>>>>>> if the VM cleans up after itself then we don't need to worry about it in the
>>>>>> driver.
>>>>> Drivers are *always* responsible for that. This has nothing to do with whether
>>>>> the VM is reference counted or not. GPUVM can't clean up mappings after itself.
>>>> Why not?
>>> I feel like we're talking past each other here, at least to some extend.
>>> However, I can't yet see where exactly the misunderstanding resides.
>> +1
>>
>>>> At least in amdgpu we have it exactly like that. E.g. the higher level can
>>>> cleanup the BO_VM structure at any time possible, even when there are
>>>> mappings.
>>> What do you mean with "cleanup the VM_BO structue" exactly?
>>>
>>> The VM_BO structure keeps track of all the mappings mapped in the VM_BO's VM
>>> being backed by the VM_BO's GEM object. And the GEM objects keeps a list of
>>> the corresponding VM_BOs.
>>>
>>> Hence, as long as there are mappings that this VM_BO keeps track of, this VM_BO
>>> should stay alive.
>> No, exactly the other way around. When the VM_BO structure is destroyed the
>> mappings are destroyed with them.
> This seems to be the same misunderstanding as with the VM reference count.
>
> It seems to me that you want to say that for amdgpu it seems to be a use-case
> to get rid of all mappings backed by a given BO and mapped in a given VM, hence
> a VM_BO. You can do that. Thers's even a helper for that in GPUVM.
>
> But also in this case you first need to get rid of all mappings before you
> *free* the VM_BO - GPUVM ensures that.
>
>> Otherwise you would need to destroy each individual mapping separately
>> before teardown which is quite inefficient.
> Not sure what you mean, but I don't see a difference between walking all VM_BOs
> and removing their mappings and walking the VM's tree of mappings and removing
> each of them. Comes down to the same effort in the end. But surely can go both
> ways if you know all the existing VM_BOs.
>
>>>> The VM then keeps track which areas still need to be invalidated
>>>> in the physical representation of the page tables.
>>> And the VM does that through its tree of mappings (struct drm_gpuva). Hence, if
>>> the VM would just remove those structures on cleanup by itself, you'd loose the
>>> ability of cleaning up the page tables. Unless, you track this separately, which
>>> would make the whole tracking of GPUVM itself kinda pointless.
>> But how do you then keep track of areas which are freed and needs to be
>> updated so that nobody can access the underlying memory any more?
> "areas which are freed", what do refer to? What do yo mean with that?
>
> Do you mean areas of the VA space not containing mappings? Why would I need to
> track them explicitly? When the mapping is removed the corresponding page tables
> / page table entries are gone as well, hence no subsequent access to the
> underlaying memory would be possible.
>
>>>> I would expect that the generalized GPU VM handling would need something
>>>> similar. If we leave that to the driver then each driver would have to
>>>> implement that stuff on it's own again.
>>> Similar to what? What exactly do you think can be generalized here?
>> Similar to how amdgpu works.
> I don't think it's quite fair to just throw the "look at what amdgpu does"
> argument at me. What am I supposed to do? Read and understand *every* detail of
> *every* driver?
>
> Did you read through the GPUVM code? That's a honest question and I'm asking it
> because I feel like you're picking up some details from commit messages and
> start questioning them (and that's perfectly fine and absolutely welcome).
>
> But if the answers don't satisfy you or do not lead to a better understanding it
> just seems you ask others to check out amdgpu rather than taking the time to go
> though the proposed code yourself making suggestions to improve it or explicitly
> point out the changes you require.
>
>>  From what I can see you are basically re-inventing everything we already
>> have in there and asking the same questions we stumbled over years ago.
> I did not ask any questions in the first place. I came up with something that
> Nouveau, Xe, Panthor, PowerVR, etc. required and that works for them.
>
> They also all provided a lot of ideas and contributed code through the review
> process.
>
> Of course, I want this to work for amdgpu as well. So, if you think we're
> missing something fundamential or if you see something that simply doesn't work
> for other drivers, like amdgpu, please educate us. I'm surely willing to learn
> and, if required, change the code.
>
> But please don't just tell me I would re-invent amdgpu and assume that I know
> all the details of this driver. None of that is reasonable.
>
>>>>> If the driver left mappings, GPUVM would just leak them without reference count.
>>>>> It doesn't know about the drivers surrounding structures, nor does it know about
>>>>> attached ressources such as PT(E)s.
>>>> What are we talking with the word "mapping"? The BO_VM structure? Or each
>>>> individual mapping?
>>> An individual mapping represented by struct drm_gpuva.
>> Yeah than that certainly doesn't work. See below.
>>
>>>> E.g. what we need to prevent is that VM structure (or the root GEM object)
>>>> is released while VM_BOs are still around. That's what I totally agree on.
>>>>
>>>> But each individual mapping is a different story. Userspace can create so
>>>> many of them that we probably could even overrun a 32bit counter quite
>>>> easily.
>>> REFCOUNT_MAX is specified as 0x7fff_ffff. I agree there can be a lot of
>>> mappings, but (including the VM_BO references) more than 2.147.483.647 per VM?
>> IIRC on amdgpu we can create something like 100k mappings per second and
>> each takes ~64 bytes.
>>
>> So you just need 128GiB of memory and approx 20 seconds to let the kernel
>> run into a refcount overrun.
> 100.000 * 20 = 2.000.000
>
> That's pretty far from REFCOUNT_MAX with 2.147.483.647. So, it's more like
> 20.000s if we can keep the pace and have enough memory. Also, it's not only the
> mapping structures itself, it's also page tables, userspace structures, etc.
>
> Again, is the number of ~2.15 Billion mappings something we really need to worry
> about?
>
> I'm still not convinced about that. But I think we can also just cap GPUVM at,
> let's say, 1 Billion mappings?
>
>> The worst I've seen in a real world game was around 19k mappings, but that
>> doesn't mean that this here can't be exploited.
>>
>> What can be done is to keep one reference per VM_BO structure, but I think
>> per mapping is rather unrealistic.
>>
>> Regards,
>> Christian.
>>
>>
Did we get any resolution on this?

FWIW, my take on this is that it would be possible to get GPUVM to work 
both with and without internal refcounting; If with, the driver needs a 
vm close to resolve cyclic references, if without that's not necessary. 
If GPUVM is allowed to refcount in mappings and vm_bos, that comes with 
a slight performance drop but as Danilo pointed out, the VM lifetime 
problem iterating over a vm_bo's mapping becomes much easier and the 
code thus becomes easier to maintain moving forward. That convinced me 
it's a good thing.

Another issue Christian brought up is that something intended to be 
embeddable (a base class) shouldn't really have its own refcount. I 
think that's a valid point. If you at some point need to derive from 
multiple such structs each having its own refcount, things will start to 
get weird. One way to resolve that would be to have the driver's 
subclass provide get() and put() ops, and export a destructor for the 
base-class, rather than to have the base-class provide the refcount and 
a destructor  ops.

That would also make it possible for the driver to decide the context 
for the put() call: If the driver needs to be able to call put() from 
irq / atomic context but the base-class'es destructor doesn't allow 
atomic context, the driver can push freeing out to a work item if needed.

Finally, the refcount overflow Christian pointed out. Limiting the 
number of mapping sounds like a reasonable remedy to me.

But I think all of this is fixable as follow-ups if needed, unless I'm 
missing something crucial.

Just my 2 cents.

/Thomas



^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-09 15:50                         ` Thomas Hellström
  (?)
@ 2023-11-09 16:03                           ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-09 16:03 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

Am 09.11.23 um 16:50 schrieb Thomas Hellström:
> [SNIP]
>>>
> Did we get any resolution on this?
>
> FWIW, my take on this is that it would be possible to get GPUVM to 
> work both with and without internal refcounting; If with, the driver 
> needs a vm close to resolve cyclic references, if without that's not 
> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
> that comes with a slight performance drop but as Danilo pointed out, 
> the VM lifetime problem iterating over a vm_bo's mapping becomes much 
> easier and the code thus becomes easier to maintain moving forward. 
> That convinced me it's a good thing.

I strongly believe you guys stumbled over one of the core problems with 
the VM here and I think that reference counting is the right answer to 
solving this.

The big question is that what is reference counted and in which 
direction does the dependency points, e.g. we have here VM, BO, BO_VM 
and Mapping objects.

Those patches here suggest a counted Mapping -> VM reference and I'm 
pretty sure that this isn't a good idea. What we should rather really 
have is a BO -> VM or BO_VM ->VM reference. In other words that each BO 
which is part of the VM keeps a reference to the VM.

BTW: At least in amdgpu we can have BOs which (temporary) doesn't have 
any mappings, but are still considered part of the VM.

>
> Another issue Christian brought up is that something intended to be 
> embeddable (a base class) shouldn't really have its own refcount. I 
> think that's a valid point. If you at some point need to derive from 
> multiple such structs each having its own refcount, things will start 
> to get weird. One way to resolve that would be to have the driver's 
> subclass provide get() and put() ops, and export a destructor for the 
> base-class, rather than to have the base-class provide the refcount 
> and a destructor  ops.

Well, I have never seen stuff like that in the kernel. Might be that 
this works, but I would rather not try if avoidable.

>
> That would also make it possible for the driver to decide the context 
> for the put() call: If the driver needs to be able to call put() from 
> irq / atomic context but the base-class'es destructor doesn't allow 
> atomic context, the driver can push freeing out to a work item if needed.
>
> Finally, the refcount overflow Christian pointed out. Limiting the 
> number of mapping sounds like a reasonable remedy to me.

Well that depends, I would rather avoid having a dependency for mappings.

Taking the CPU VM handling as example as far as I know vm_area_structs 
doesn't grab a reference to their mm_struct either. Instead they get 
automatically destroyed when the mm_struct is destroyed.

Which makes sense in that case because when the mm_struct is gone the 
vm_area_struct doesn't make sense any more either.

What we clearly need is a reference to prevent the VM or at least the 
shared resv to go away to early.

Regards,
Christian.

>
> But I think all of this is fixable as follow-ups if needed, unless I'm 
> missing something crucial.
>
> Just my 2 cents.
>
> /Thomas
>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 16:03                           ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-09 16:03 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

Am 09.11.23 um 16:50 schrieb Thomas Hellström:
> [SNIP]
>>>
> Did we get any resolution on this?
>
> FWIW, my take on this is that it would be possible to get GPUVM to 
> work both with and without internal refcounting; If with, the driver 
> needs a vm close to resolve cyclic references, if without that's not 
> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
> that comes with a slight performance drop but as Danilo pointed out, 
> the VM lifetime problem iterating over a vm_bo's mapping becomes much 
> easier and the code thus becomes easier to maintain moving forward. 
> That convinced me it's a good thing.

I strongly believe you guys stumbled over one of the core problems with 
the VM here and I think that reference counting is the right answer to 
solving this.

The big question is that what is reference counted and in which 
direction does the dependency points, e.g. we have here VM, BO, BO_VM 
and Mapping objects.

Those patches here suggest a counted Mapping -> VM reference and I'm 
pretty sure that this isn't a good idea. What we should rather really 
have is a BO -> VM or BO_VM ->VM reference. In other words that each BO 
which is part of the VM keeps a reference to the VM.

BTW: At least in amdgpu we can have BOs which (temporary) doesn't have 
any mappings, but are still considered part of the VM.

>
> Another issue Christian brought up is that something intended to be 
> embeddable (a base class) shouldn't really have its own refcount. I 
> think that's a valid point. If you at some point need to derive from 
> multiple such structs each having its own refcount, things will start 
> to get weird. One way to resolve that would be to have the driver's 
> subclass provide get() and put() ops, and export a destructor for the 
> base-class, rather than to have the base-class provide the refcount 
> and a destructor  ops.

Well, I have never seen stuff like that in the kernel. Might be that 
this works, but I would rather not try if avoidable.

>
> That would also make it possible for the driver to decide the context 
> for the put() call: If the driver needs to be able to call put() from 
> irq / atomic context but the base-class'es destructor doesn't allow 
> atomic context, the driver can push freeing out to a work item if needed.
>
> Finally, the refcount overflow Christian pointed out. Limiting the 
> number of mapping sounds like a reasonable remedy to me.

Well that depends, I would rather avoid having a dependency for mappings.

Taking the CPU VM handling as example as far as I know vm_area_structs 
doesn't grab a reference to their mm_struct either. Instead they get 
automatically destroyed when the mm_struct is destroyed.

Which makes sense in that case because when the mm_struct is gone the 
vm_area_struct doesn't make sense any more either.

What we clearly need is a reference to prevent the VM or at least the 
shared resv to go away to early.

Regards,
Christian.

>
> But I think all of this is fixable as follow-ups if needed, unless I'm 
> missing something crucial.
>
> Just my 2 cents.
>
> /Thomas
>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 16:03                           ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-09 16:03 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

Am 09.11.23 um 16:50 schrieb Thomas Hellström:
> [SNIP]
>>>
> Did we get any resolution on this?
>
> FWIW, my take on this is that it would be possible to get GPUVM to 
> work both with and without internal refcounting; If with, the driver 
> needs a vm close to resolve cyclic references, if without that's not 
> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
> that comes with a slight performance drop but as Danilo pointed out, 
> the VM lifetime problem iterating over a vm_bo's mapping becomes much 
> easier and the code thus becomes easier to maintain moving forward. 
> That convinced me it's a good thing.

I strongly believe you guys stumbled over one of the core problems with 
the VM here and I think that reference counting is the right answer to 
solving this.

The big question is that what is reference counted and in which 
direction does the dependency points, e.g. we have here VM, BO, BO_VM 
and Mapping objects.

Those patches here suggest a counted Mapping -> VM reference and I'm 
pretty sure that this isn't a good idea. What we should rather really 
have is a BO -> VM or BO_VM ->VM reference. In other words that each BO 
which is part of the VM keeps a reference to the VM.

BTW: At least in amdgpu we can have BOs which (temporary) doesn't have 
any mappings, but are still considered part of the VM.

>
> Another issue Christian brought up is that something intended to be 
> embeddable (a base class) shouldn't really have its own refcount. I 
> think that's a valid point. If you at some point need to derive from 
> multiple such structs each having its own refcount, things will start 
> to get weird. One way to resolve that would be to have the driver's 
> subclass provide get() and put() ops, and export a destructor for the 
> base-class, rather than to have the base-class provide the refcount 
> and a destructor  ops.

Well, I have never seen stuff like that in the kernel. Might be that 
this works, but I would rather not try if avoidable.

>
> That would also make it possible for the driver to decide the context 
> for the put() call: If the driver needs to be able to call put() from 
> irq / atomic context but the base-class'es destructor doesn't allow 
> atomic context, the driver can push freeing out to a work item if needed.
>
> Finally, the refcount overflow Christian pointed out. Limiting the 
> number of mapping sounds like a reasonable remedy to me.

Well that depends, I would rather avoid having a dependency for mappings.

Taking the CPU VM handling as example as far as I know vm_area_structs 
doesn't grab a reference to their mm_struct either. Instead they get 
automatically destroyed when the mm_struct is destroyed.

Which makes sense in that case because when the mm_struct is gone the 
vm_area_struct doesn't make sense any more either.

What we clearly need is a reference to prevent the VM or at least the 
shared resv to go away to early.

Regards,
Christian.

>
> But I think all of this is fixable as follow-ups if needed, unless I'm 
> missing something crucial.
>
> Just my 2 cents.
>
> /Thomas
>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-09 16:03                           ` [Nouveau] " Christian König
  (?)
@ 2023-11-09 18:34                             ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-09 18:34 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

On 11/9/23 17:03, Christian König wrote:
> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>> [SNIP]
>>>>
>> Did we get any resolution on this?
>>
>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
> 
> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
> 
> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
> 
> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.

We have both. Please see the subsequent patch introducing VM_BO structures for that.

As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
in and besides that it doesn't make sense to free a VM that still contains mappings,
the reference count ensures that. This simply ensures memory safety.

> 
> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.

That should be possible.

> 
>>
>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.

GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?

> 
> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
> 
>>
>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>
>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
> 
> Well that depends, I would rather avoid having a dependency for mappings.
> 
> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.

Certainly, that would be possible. However, thinking about it, this might call for
huge trouble.

First of all, we'd still need to reference count a GPUVM and take a reference for each
VM_BO, as we do already. Now instead of simply increasing the reference count for each
mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
reference count drops to zero. Maybe something like vm_destroy().

The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
by itself, since drivers might use them for tracking their allocated page tables and/or
other stuff.

Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
potentially the corresponding object's dma-resv lock if they're not the same already). If
destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
be called with those locks being held as well.

I feel like doing this finally opens the doors of the locking hell entirely. I think we should
really avoid that.

> 
> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
> 
> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.

Yeah, that was a good hint and we've covered that.

> 
> Regards,
> Christian.
> 
>>
>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.

Fully agree, I think at this point we should go ahead and land this series.

>>
>> Just my 2 cents.
>>
>> /Thomas
>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 18:34                             ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-09 18:34 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

On 11/9/23 17:03, Christian König wrote:
> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>> [SNIP]
>>>>
>> Did we get any resolution on this?
>>
>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
> 
> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
> 
> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
> 
> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.

We have both. Please see the subsequent patch introducing VM_BO structures for that.

As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
in and besides that it doesn't make sense to free a VM that still contains mappings,
the reference count ensures that. This simply ensures memory safety.

> 
> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.

That should be possible.

> 
>>
>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.

GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?

> 
> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
> 
>>
>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>
>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
> 
> Well that depends, I would rather avoid having a dependency for mappings.
> 
> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.

Certainly, that would be possible. However, thinking about it, this might call for
huge trouble.

First of all, we'd still need to reference count a GPUVM and take a reference for each
VM_BO, as we do already. Now instead of simply increasing the reference count for each
mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
reference count drops to zero. Maybe something like vm_destroy().

The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
by itself, since drivers might use them for tracking their allocated page tables and/or
other stuff.

Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
potentially the corresponding object's dma-resv lock if they're not the same already). If
destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
be called with those locks being held as well.

I feel like doing this finally opens the doors of the locking hell entirely. I think we should
really avoid that.

> 
> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
> 
> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.

Yeah, that was a good hint and we've covered that.

> 
> Regards,
> Christian.
> 
>>
>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.

Fully agree, I think at this point we should go ahead and land this series.

>>
>> Just my 2 cents.
>>
>> /Thomas
>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-09 18:34                             ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-09 18:34 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

On 11/9/23 17:03, Christian König wrote:
> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>> [SNIP]
>>>>
>> Did we get any resolution on this?
>>
>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
> 
> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
> 
> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
> 
> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.

We have both. Please see the subsequent patch introducing VM_BO structures for that.

As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
in and besides that it doesn't make sense to free a VM that still contains mappings,
the reference count ensures that. This simply ensures memory safety.

> 
> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.

That should be possible.

> 
>>
>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.

GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?

> 
> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
> 
>>
>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>
>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
> 
> Well that depends, I would rather avoid having a dependency for mappings.
> 
> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.

Certainly, that would be possible. However, thinking about it, this might call for
huge trouble.

First of all, we'd still need to reference count a GPUVM and take a reference for each
VM_BO, as we do already. Now instead of simply increasing the reference count for each
mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
reference count drops to zero. Maybe something like vm_destroy().

The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
by itself, since drivers might use them for tracking their allocated page tables and/or
other stuff.

Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
potentially the corresponding object's dma-resv lock if they're not the same already). If
destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
be called with those locks being held as well.

I feel like doing this finally opens the doors of the locking hell entirely. I think we should
really avoid that.

> 
> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
> 
> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.

Yeah, that was a good hint and we've covered that.

> 
> Regards,
> Christian.
> 
>>
>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.

Fully agree, I think at this point we should go ahead and land this series.

>>
>> Just my 2 cents.
>>
>> /Thomas
>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-09 18:34                             ` Danilo Krummrich
  (?)
@ 2023-11-10  8:50                               ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10  8:50 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
> On 11/9/23 17:03, Christian König wrote:
>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>> [SNIP]
>>>>>
>>> Did we get any resolution on this?
>>>
>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>> work both with and without internal refcounting; If with, the driver 
>>> needs a vm close to resolve cyclic references, if without that's not 
>>> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
>>> that comes with a slight performance drop but as Danilo pointed out, 
>>> the VM lifetime problem iterating over a vm_bo's mapping becomes 
>>> much easier and the code thus becomes easier to maintain moving 
>>> forward. That convinced me it's a good thing.
>>
>> I strongly believe you guys stumbled over one of the core problems 
>> with the VM here and I think that reference counting is the right 
>> answer to solving this.
>>
>> The big question is that what is reference counted and in which 
>> direction does the dependency points, e.g. we have here VM, BO, BO_VM 
>> and Mapping objects.
>>
>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>> pretty sure that this isn't a good idea. What we should rather really 
>> have is a BO -> VM or BO_VM ->VM reference. In other words that each 
>> BO which is part of the VM keeps a reference to the VM.
>
> We have both. Please see the subsequent patch introducing VM_BO 
> structures for that.
>
> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM 
> they're mapped
> in and besides that it doesn't make sense to free a VM that still 
> contains mappings,
> the reference count ensures that. This simply ensures memory safety.
>
>>
>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>> have any mappings, but are still considered part of the VM.
>
> That should be possible.
>
>>
>>>
>>> Another issue Christian brought up is that something intended to be 
>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>> think that's a valid point. If you at some point need to derive from 
>>> multiple such structs each having its own refcount, things will 
>>> start to get weird. One way to resolve that would be to have the 
>>> driver's subclass provide get() and put() ops, and export a 
>>> destructor for the base-class, rather than to have the base-class 
>>> provide the refcount and a destructor  ops.
>
> GPUVM simply follows the same pattern we have with drm_gem_objects. 
> And I think it makes
> sense. Why would we want to embed two struct drm_gpuvm in a single 
> driver structure?

Because you need one drm_gpuvm structure for each application using the 
driver? Or am I missing something?

As far as I can see a driver would want to embed that into your fpriv 
structure which is allocated during drm_driver.open callback.

>
>>
>> Well, I have never seen stuff like that in the kernel. Might be that 
>> this works, but I would rather not try if avoidable.
>>
>>>
>>> That would also make it possible for the driver to decide the 
>>> context for the put() call: If the driver needs to be able to call 
>>> put() from irq / atomic context but the base-class'es destructor 
>>> doesn't allow atomic context, the driver can push freeing out to a 
>>> work item if needed.
>>>
>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>> number of mapping sounds like a reasonable remedy to me.
>>
>> Well that depends, I would rather avoid having a dependency for 
>> mappings.
>>
>> Taking the CPU VM handling as example as far as I know 
>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>> Instead they get automatically destroyed when the mm_struct is 
>> destroyed.
>
> Certainly, that would be possible. However, thinking about it, this 
> might call for
> huge trouble.
>
> First of all, we'd still need to reference count a GPUVM and take a 
> reference for each
> VM_BO, as we do already. Now instead of simply increasing the 
> reference count for each
> mapping as well, we'd need a *mandatory* driver callback that is 
> called when the GPUVM
> reference count drops to zero. Maybe something like vm_destroy().
>
> The reason is that GPUVM can't just remove all mappings from the tree 
> nor can it free them
> by itself, since drivers might use them for tracking their allocated 
> page tables and/or
> other stuff.
>
> Now, let's think about the scope this callback might be called from. 
> When a VM_BO is destroyed
> the driver might hold a couple of locks (for Xe it would be the VM's 
> shared dma-resv lock and
> potentially the corresponding object's dma-resv lock if they're not 
> the same already). If
> destroying this VM_BO leads to the VM being destroyed, the drivers 
> vm_destroy() callback would
> be called with those locks being held as well.
>
> I feel like doing this finally opens the doors of the locking hell 
> entirely. I think we should
> really avoid that.

That's a really good point, but I fear exactly that's the use case.

I would expect that VM_BO structures are added in the 
drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.

Since it is perfectly legal for userspace to close a BO while there are 
still mappings (can trivial be that the app is killed) I would expect 
that the drm_gem_object_funcs.close handling is something like asking 
drm_gpuvm destroying the VM_BO and getting the mappings which should be 
cleared in the page table in return.

In amdgpu we even go a step further and the VM structure keeps track of 
all the mappings of deleted VM_BOs so that higher level can query those 
and clear them later on.

Background is that the drm_gem_object_funcs.close can't fail, but it can 
perfectly be that the app is killed because of an OOM situation and we 
can't do page tables updates in that moment because of this.

>
>>
>> Which makes sense in that case because when the mm_struct is gone the 
>> vm_area_struct doesn't make sense any more either.
>>
>> What we clearly need is a reference to prevent the VM or at least the 
>> shared resv to go away to early.
>
> Yeah, that was a good hint and we've covered that.
>
>>
>> Regards,
>> Christian.
>>
>>>
>>> But I think all of this is fixable as follow-ups if needed, unless 
>>> I'm missing something crucial.
>
> Fully agree, I think at this point we should go ahead and land this 
> series.

Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my 
acked-by as well if you want.

Only keep in mind that when you give drivers some functionality in a 
common component they usually expect to keep that functionality.

For example changing the dma_resv object to make sure that drivers can't 
cause use after free errors any more was an extremely annoying 
experience since every user of those interface had to change at once.

Regards,
Christian.

>
>>>
>>> Just my 2 cents.
>>>
>>> /Thomas
>>>
>>>
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10  8:50                               ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10  8:50 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
> On 11/9/23 17:03, Christian König wrote:
>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>> [SNIP]
>>>>>
>>> Did we get any resolution on this?
>>>
>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>> work both with and without internal refcounting; If with, the driver 
>>> needs a vm close to resolve cyclic references, if without that's not 
>>> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
>>> that comes with a slight performance drop but as Danilo pointed out, 
>>> the VM lifetime problem iterating over a vm_bo's mapping becomes 
>>> much easier and the code thus becomes easier to maintain moving 
>>> forward. That convinced me it's a good thing.
>>
>> I strongly believe you guys stumbled over one of the core problems 
>> with the VM here and I think that reference counting is the right 
>> answer to solving this.
>>
>> The big question is that what is reference counted and in which 
>> direction does the dependency points, e.g. we have here VM, BO, BO_VM 
>> and Mapping objects.
>>
>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>> pretty sure that this isn't a good idea. What we should rather really 
>> have is a BO -> VM or BO_VM ->VM reference. In other words that each 
>> BO which is part of the VM keeps a reference to the VM.
>
> We have both. Please see the subsequent patch introducing VM_BO 
> structures for that.
>
> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM 
> they're mapped
> in and besides that it doesn't make sense to free a VM that still 
> contains mappings,
> the reference count ensures that. This simply ensures memory safety.
>
>>
>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>> have any mappings, but are still considered part of the VM.
>
> That should be possible.
>
>>
>>>
>>> Another issue Christian brought up is that something intended to be 
>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>> think that's a valid point. If you at some point need to derive from 
>>> multiple such structs each having its own refcount, things will 
>>> start to get weird. One way to resolve that would be to have the 
>>> driver's subclass provide get() and put() ops, and export a 
>>> destructor for the base-class, rather than to have the base-class 
>>> provide the refcount and a destructor  ops.
>
> GPUVM simply follows the same pattern we have with drm_gem_objects. 
> And I think it makes
> sense. Why would we want to embed two struct drm_gpuvm in a single 
> driver structure?

Because you need one drm_gpuvm structure for each application using the 
driver? Or am I missing something?

As far as I can see a driver would want to embed that into your fpriv 
structure which is allocated during drm_driver.open callback.

>
>>
>> Well, I have never seen stuff like that in the kernel. Might be that 
>> this works, but I would rather not try if avoidable.
>>
>>>
>>> That would also make it possible for the driver to decide the 
>>> context for the put() call: If the driver needs to be able to call 
>>> put() from irq / atomic context but the base-class'es destructor 
>>> doesn't allow atomic context, the driver can push freeing out to a 
>>> work item if needed.
>>>
>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>> number of mapping sounds like a reasonable remedy to me.
>>
>> Well that depends, I would rather avoid having a dependency for 
>> mappings.
>>
>> Taking the CPU VM handling as example as far as I know 
>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>> Instead they get automatically destroyed when the mm_struct is 
>> destroyed.
>
> Certainly, that would be possible. However, thinking about it, this 
> might call for
> huge trouble.
>
> First of all, we'd still need to reference count a GPUVM and take a 
> reference for each
> VM_BO, as we do already. Now instead of simply increasing the 
> reference count for each
> mapping as well, we'd need a *mandatory* driver callback that is 
> called when the GPUVM
> reference count drops to zero. Maybe something like vm_destroy().
>
> The reason is that GPUVM can't just remove all mappings from the tree 
> nor can it free them
> by itself, since drivers might use them for tracking their allocated 
> page tables and/or
> other stuff.
>
> Now, let's think about the scope this callback might be called from. 
> When a VM_BO is destroyed
> the driver might hold a couple of locks (for Xe it would be the VM's 
> shared dma-resv lock and
> potentially the corresponding object's dma-resv lock if they're not 
> the same already). If
> destroying this VM_BO leads to the VM being destroyed, the drivers 
> vm_destroy() callback would
> be called with those locks being held as well.
>
> I feel like doing this finally opens the doors of the locking hell 
> entirely. I think we should
> really avoid that.

That's a really good point, but I fear exactly that's the use case.

I would expect that VM_BO structures are added in the 
drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.

Since it is perfectly legal for userspace to close a BO while there are 
still mappings (can trivial be that the app is killed) I would expect 
that the drm_gem_object_funcs.close handling is something like asking 
drm_gpuvm destroying the VM_BO and getting the mappings which should be 
cleared in the page table in return.

In amdgpu we even go a step further and the VM structure keeps track of 
all the mappings of deleted VM_BOs so that higher level can query those 
and clear them later on.

Background is that the drm_gem_object_funcs.close can't fail, but it can 
perfectly be that the app is killed because of an OOM situation and we 
can't do page tables updates in that moment because of this.

>
>>
>> Which makes sense in that case because when the mm_struct is gone the 
>> vm_area_struct doesn't make sense any more either.
>>
>> What we clearly need is a reference to prevent the VM or at least the 
>> shared resv to go away to early.
>
> Yeah, that was a good hint and we've covered that.
>
>>
>> Regards,
>> Christian.
>>
>>>
>>> But I think all of this is fixable as follow-ups if needed, unless 
>>> I'm missing something crucial.
>
> Fully agree, I think at this point we should go ahead and land this 
> series.

Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my 
acked-by as well if you want.

Only keep in mind that when you give drivers some functionality in a 
common component they usually expect to keep that functionality.

For example changing the dma_resv object to make sure that drivers can't 
cause use after free errors any more was an extremely annoying 
experience since every user of those interface had to change at once.

Regards,
Christian.

>
>>>
>>> Just my 2 cents.
>>>
>>> /Thomas
>>>
>>>
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10  8:50                               ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10  8:50 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
> On 11/9/23 17:03, Christian König wrote:
>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>> [SNIP]
>>>>>
>>> Did we get any resolution on this?
>>>
>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>> work both with and without internal refcounting; If with, the driver 
>>> needs a vm close to resolve cyclic references, if without that's not 
>>> necessary. If GPUVM is allowed to refcount in mappings and vm_bos, 
>>> that comes with a slight performance drop but as Danilo pointed out, 
>>> the VM lifetime problem iterating over a vm_bo's mapping becomes 
>>> much easier and the code thus becomes easier to maintain moving 
>>> forward. That convinced me it's a good thing.
>>
>> I strongly believe you guys stumbled over one of the core problems 
>> with the VM here and I think that reference counting is the right 
>> answer to solving this.
>>
>> The big question is that what is reference counted and in which 
>> direction does the dependency points, e.g. we have here VM, BO, BO_VM 
>> and Mapping objects.
>>
>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>> pretty sure that this isn't a good idea. What we should rather really 
>> have is a BO -> VM or BO_VM ->VM reference. In other words that each 
>> BO which is part of the VM keeps a reference to the VM.
>
> We have both. Please see the subsequent patch introducing VM_BO 
> structures for that.
>
> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM 
> they're mapped
> in and besides that it doesn't make sense to free a VM that still 
> contains mappings,
> the reference count ensures that. This simply ensures memory safety.
>
>>
>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>> have any mappings, but are still considered part of the VM.
>
> That should be possible.
>
>>
>>>
>>> Another issue Christian brought up is that something intended to be 
>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>> think that's a valid point. If you at some point need to derive from 
>>> multiple such structs each having its own refcount, things will 
>>> start to get weird. One way to resolve that would be to have the 
>>> driver's subclass provide get() and put() ops, and export a 
>>> destructor for the base-class, rather than to have the base-class 
>>> provide the refcount and a destructor  ops.
>
> GPUVM simply follows the same pattern we have with drm_gem_objects. 
> And I think it makes
> sense. Why would we want to embed two struct drm_gpuvm in a single 
> driver structure?

Because you need one drm_gpuvm structure for each application using the 
driver? Or am I missing something?

As far as I can see a driver would want to embed that into your fpriv 
structure which is allocated during drm_driver.open callback.

>
>>
>> Well, I have never seen stuff like that in the kernel. Might be that 
>> this works, but I would rather not try if avoidable.
>>
>>>
>>> That would also make it possible for the driver to decide the 
>>> context for the put() call: If the driver needs to be able to call 
>>> put() from irq / atomic context but the base-class'es destructor 
>>> doesn't allow atomic context, the driver can push freeing out to a 
>>> work item if needed.
>>>
>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>> number of mapping sounds like a reasonable remedy to me.
>>
>> Well that depends, I would rather avoid having a dependency for 
>> mappings.
>>
>> Taking the CPU VM handling as example as far as I know 
>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>> Instead they get automatically destroyed when the mm_struct is 
>> destroyed.
>
> Certainly, that would be possible. However, thinking about it, this 
> might call for
> huge trouble.
>
> First of all, we'd still need to reference count a GPUVM and take a 
> reference for each
> VM_BO, as we do already. Now instead of simply increasing the 
> reference count for each
> mapping as well, we'd need a *mandatory* driver callback that is 
> called when the GPUVM
> reference count drops to zero. Maybe something like vm_destroy().
>
> The reason is that GPUVM can't just remove all mappings from the tree 
> nor can it free them
> by itself, since drivers might use them for tracking their allocated 
> page tables and/or
> other stuff.
>
> Now, let's think about the scope this callback might be called from. 
> When a VM_BO is destroyed
> the driver might hold a couple of locks (for Xe it would be the VM's 
> shared dma-resv lock and
> potentially the corresponding object's dma-resv lock if they're not 
> the same already). If
> destroying this VM_BO leads to the VM being destroyed, the drivers 
> vm_destroy() callback would
> be called with those locks being held as well.
>
> I feel like doing this finally opens the doors of the locking hell 
> entirely. I think we should
> really avoid that.

That's a really good point, but I fear exactly that's the use case.

I would expect that VM_BO structures are added in the 
drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.

Since it is perfectly legal for userspace to close a BO while there are 
still mappings (can trivial be that the app is killed) I would expect 
that the drm_gem_object_funcs.close handling is something like asking 
drm_gpuvm destroying the VM_BO and getting the mappings which should be 
cleared in the page table in return.

In amdgpu we even go a step further and the VM structure keeps track of 
all the mappings of deleted VM_BOs so that higher level can query those 
and clear them later on.

Background is that the drm_gem_object_funcs.close can't fail, but it can 
perfectly be that the app is killed because of an OOM situation and we 
can't do page tables updates in that moment because of this.

>
>>
>> Which makes sense in that case because when the mm_struct is gone the 
>> vm_area_struct doesn't make sense any more either.
>>
>> What we clearly need is a reference to prevent the VM or at least the 
>> shared resv to go away to early.
>
> Yeah, that was a good hint and we've covered that.
>
>>
>> Regards,
>> Christian.
>>
>>>
>>> But I think all of this is fixable as follow-ups if needed, unless 
>>> I'm missing something crucial.
>
> Fully agree, I think at this point we should go ahead and land this 
> series.

Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my 
acked-by as well if you want.

Only keep in mind that when you give drivers some functionality in a 
common component they usually expect to keep that functionality.

For example changing the dma_resv object to make sure that drivers can't 
cause use after free errors any more was an extremely annoying 
experience since every user of those interface had to change at once.

Regards,
Christian.

>
>>>
>>> Just my 2 cents.
>>>
>>> /Thomas
>>>
>>>
>>
>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10  8:50                               ` Christian König
  (?)
@ 2023-11-10  9:39                                 ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10  9:39 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel


On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>>> work both with and without internal refcounting; If with, the 
>>>> driver needs a vm close to resolve cyclic references, if without 
>>>> that's not necessary. If GPUVM is allowed to refcount in mappings 
>>>> and vm_bos, that comes with a slight performance drop but as Danilo 
>>>> pointed out, the VM lifetime problem iterating over a vm_bo's 
>>>> mapping becomes much easier and the code thus becomes easier to 
>>>> maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems 
>>> with the VM here and I think that reference counting is the right 
>>> answer to solving this.
>>>
>>> The big question is that what is reference counted and in which 
>>> direction does the dependency points, e.g. we have here VM, BO, 
>>> BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>>> pretty sure that this isn't a good idea. What we should rather 
>>> really have is a BO -> VM or BO_VM ->VM reference. In other words 
>>> that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO 
>> structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their 
>> VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still 
>> contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>>> have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be 
>>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>>> think that's a valid point. If you at some point need to derive 
>>>> from multiple such structs each having its own refcount, things 
>>>> will start to get weird. One way to resolve that would be to have 
>>>> the driver's subclass provide get() and put() ops, and export a 
>>>> destructor for the base-class, rather than to have the base-class 
>>>> provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>> And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>> driver structure?
>
> Because you need one drm_gpuvm structure for each application using 
> the driver? Or am I missing something?
>
> As far as I can see a driver would want to embed that into your fpriv 
> structure which is allocated during drm_driver.open callback.

I was thinking more of the general design of a base-class that needs to 
be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and 
yet another base-class that supplies its own refcount. What's the 
best-practice way to do refcounting? All base-classes supplying a 
refcount of its own, or the subclass supplying a refcount and the 
base-classes supply destroy helpers.

But to be clear this is nothing I see needing urgent attention.

>
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that 
>>> this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the 
>>>> context for the put() call: If the driver needs to be able to call 
>>>> put() from irq / atomic context but the base-class'es destructor 
>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>> work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>> number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for 
>>> mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know 
>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>> Instead they get automatically destroyed when the mm_struct is 
>>> destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this 
>> might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a 
>> reference for each
>> VM_BO, as we do already. Now instead of simply increasing the 
>> reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is 
>> called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree 
>> nor can it free them
>> by itself, since drivers might use them for tracking their allocated 
>> page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. 
>> When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's 
>> shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not 
>> the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>> vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell 
>> entirely. I think we should
>> really avoid that.

I don't think we need to worry much about this particular locking hell 
because if we hold, for example a vm and bo resv when putting the vm_bo, 
we need to keep additional strong references for the bo / vm pointer we 
use for unlocking. Hence putting the vm_bo under those locks can never 
lead to the vm getting destroyed.

Also, don't we already sort of have a mandatory vm_destroy callback?

+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;



>
> That's a really good point, but I fear exactly that's the use case.
>
> I would expect that VM_BO structures are added in the 
> drm_gem_object_funcs.open callback and freed in 
> drm_gem_object_funcs.close.
>
> Since it is perfectly legal for userspace to close a BO while there 
> are still mappings (can trivial be that the app is killed) I would 
> expect that the drm_gem_object_funcs.close handling is something like 
> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
> should be cleared in the page table in return.
>
> In amdgpu we even go a step further and the VM structure keeps track 
> of all the mappings of deleted VM_BOs so that higher level can query 
> those and clear them later on.
>
> Background is that the drm_gem_object_funcs.close can't fail, but it 
> can perfectly be that the app is killed because of an OOM situation 
> and we can't do page tables updates in that moment because of this.
>
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone 
>>> the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least 
>>> the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>> I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this 
>> series.

+1.

/Thomas


>>
>
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
> my acked-by as well if you want.
>
> Only keep in mind that when you give drivers some functionality in a 
> common component they usually expect to keep that functionality.
>
> For example changing the dma_resv object to make sure that drivers 
> can't cause use after free errors any more was an extremely annoying 
> experience since every user of those interface had to change at once.
>
> Regards,
> Christian.
>
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10  9:39                                 ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10  9:39 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson


On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>>> work both with and without internal refcounting; If with, the 
>>>> driver needs a vm close to resolve cyclic references, if without 
>>>> that's not necessary. If GPUVM is allowed to refcount in mappings 
>>>> and vm_bos, that comes with a slight performance drop but as Danilo 
>>>> pointed out, the VM lifetime problem iterating over a vm_bo's 
>>>> mapping becomes much easier and the code thus becomes easier to 
>>>> maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems 
>>> with the VM here and I think that reference counting is the right 
>>> answer to solving this.
>>>
>>> The big question is that what is reference counted and in which 
>>> direction does the dependency points, e.g. we have here VM, BO, 
>>> BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>>> pretty sure that this isn't a good idea. What we should rather 
>>> really have is a BO -> VM or BO_VM ->VM reference. In other words 
>>> that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO 
>> structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their 
>> VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still 
>> contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>>> have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be 
>>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>>> think that's a valid point. If you at some point need to derive 
>>>> from multiple such structs each having its own refcount, things 
>>>> will start to get weird. One way to resolve that would be to have 
>>>> the driver's subclass provide get() and put() ops, and export a 
>>>> destructor for the base-class, rather than to have the base-class 
>>>> provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>> And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>> driver structure?
>
> Because you need one drm_gpuvm structure for each application using 
> the driver? Or am I missing something?
>
> As far as I can see a driver would want to embed that into your fpriv 
> structure which is allocated during drm_driver.open callback.

I was thinking more of the general design of a base-class that needs to 
be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and 
yet another base-class that supplies its own refcount. What's the 
best-practice way to do refcounting? All base-classes supplying a 
refcount of its own, or the subclass supplying a refcount and the 
base-classes supply destroy helpers.

But to be clear this is nothing I see needing urgent attention.

>
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that 
>>> this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the 
>>>> context for the put() call: If the driver needs to be able to call 
>>>> put() from irq / atomic context but the base-class'es destructor 
>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>> work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>> number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for 
>>> mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know 
>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>> Instead they get automatically destroyed when the mm_struct is 
>>> destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this 
>> might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a 
>> reference for each
>> VM_BO, as we do already. Now instead of simply increasing the 
>> reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is 
>> called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree 
>> nor can it free them
>> by itself, since drivers might use them for tracking their allocated 
>> page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. 
>> When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's 
>> shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not 
>> the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>> vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell 
>> entirely. I think we should
>> really avoid that.

I don't think we need to worry much about this particular locking hell 
because if we hold, for example a vm and bo resv when putting the vm_bo, 
we need to keep additional strong references for the bo / vm pointer we 
use for unlocking. Hence putting the vm_bo under those locks can never 
lead to the vm getting destroyed.

Also, don't we already sort of have a mandatory vm_destroy callback?

+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;



>
> That's a really good point, but I fear exactly that's the use case.
>
> I would expect that VM_BO structures are added in the 
> drm_gem_object_funcs.open callback and freed in 
> drm_gem_object_funcs.close.
>
> Since it is perfectly legal for userspace to close a BO while there 
> are still mappings (can trivial be that the app is killed) I would 
> expect that the drm_gem_object_funcs.close handling is something like 
> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
> should be cleared in the page table in return.
>
> In amdgpu we even go a step further and the VM structure keeps track 
> of all the mappings of deleted VM_BOs so that higher level can query 
> those and clear them later on.
>
> Background is that the drm_gem_object_funcs.close can't fail, but it 
> can perfectly be that the app is killed because of an OOM situation 
> and we can't do page tables updates in that moment because of this.
>
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone 
>>> the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least 
>>> the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>> I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this 
>> series.

+1.

/Thomas


>>
>
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
> my acked-by as well if you want.
>
> Only keep in mind that when you give drivers some functionality in a 
> common component they usually expect to keep that functionality.
>
> For example changing the dma_resv object to make sure that drivers 
> can't cause use after free errors any more was an extremely annoying 
> experience since every user of those interface had to change at once.
>
> Regards,
> Christian.
>
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10  9:39                                 ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10  9:39 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel


On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to 
>>>> work both with and without internal refcounting; If with, the 
>>>> driver needs a vm close to resolve cyclic references, if without 
>>>> that's not necessary. If GPUVM is allowed to refcount in mappings 
>>>> and vm_bos, that comes with a slight performance drop but as Danilo 
>>>> pointed out, the VM lifetime problem iterating over a vm_bo's 
>>>> mapping becomes much easier and the code thus becomes easier to 
>>>> maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems 
>>> with the VM here and I think that reference counting is the right 
>>> answer to solving this.
>>>
>>> The big question is that what is reference counted and in which 
>>> direction does the dependency points, e.g. we have here VM, BO, 
>>> BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm 
>>> pretty sure that this isn't a good idea. What we should rather 
>>> really have is a BO -> VM or BO_VM ->VM reference. In other words 
>>> that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO 
>> structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their 
>> VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still 
>> contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't 
>>> have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be 
>>>> embeddable (a base class) shouldn't really have its own refcount. I 
>>>> think that's a valid point. If you at some point need to derive 
>>>> from multiple such structs each having its own refcount, things 
>>>> will start to get weird. One way to resolve that would be to have 
>>>> the driver's subclass provide get() and put() ops, and export a 
>>>> destructor for the base-class, rather than to have the base-class 
>>>> provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>> And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>> driver structure?
>
> Because you need one drm_gpuvm structure for each application using 
> the driver? Or am I missing something?
>
> As far as I can see a driver would want to embed that into your fpriv 
> structure which is allocated during drm_driver.open callback.

I was thinking more of the general design of a base-class that needs to 
be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and 
yet another base-class that supplies its own refcount. What's the 
best-practice way to do refcounting? All base-classes supplying a 
refcount of its own, or the subclass supplying a refcount and the 
base-classes supply destroy helpers.

But to be clear this is nothing I see needing urgent attention.

>
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that 
>>> this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the 
>>>> context for the put() call: If the driver needs to be able to call 
>>>> put() from irq / atomic context but the base-class'es destructor 
>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>> work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>> number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for 
>>> mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know 
>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>> Instead they get automatically destroyed when the mm_struct is 
>>> destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this 
>> might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a 
>> reference for each
>> VM_BO, as we do already. Now instead of simply increasing the 
>> reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is 
>> called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree 
>> nor can it free them
>> by itself, since drivers might use them for tracking their allocated 
>> page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. 
>> When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's 
>> shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not 
>> the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>> vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell 
>> entirely. I think we should
>> really avoid that.

I don't think we need to worry much about this particular locking hell 
because if we hold, for example a vm and bo resv when putting the vm_bo, 
we need to keep additional strong references for the bo / vm pointer we 
use for unlocking. Hence putting the vm_bo under those locks can never 
lead to the vm getting destroyed.

Also, don't we already sort of have a mandatory vm_destroy callback?

+	if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+		return;



>
> That's a really good point, but I fear exactly that's the use case.
>
> I would expect that VM_BO structures are added in the 
> drm_gem_object_funcs.open callback and freed in 
> drm_gem_object_funcs.close.
>
> Since it is perfectly legal for userspace to close a BO while there 
> are still mappings (can trivial be that the app is killed) I would 
> expect that the drm_gem_object_funcs.close handling is something like 
> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
> should be cleared in the page table in return.
>
> In amdgpu we even go a step further and the VM structure keeps track 
> of all the mappings of deleted VM_BOs so that higher level can query 
> those and clear them later on.
>
> Background is that the drm_gem_object_funcs.close can't fail, but it 
> can perfectly be that the app is killed because of an OOM situation 
> and we can't do page tables updates in that moment because of this.
>
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone 
>>> the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least 
>>> the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>> I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this 
>> series.

+1.

/Thomas


>>
>
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
> my acked-by as well if you want.
>
> Only keep in mind that when you give drivers some functionality in a 
> common component they usually expect to keep that functionality.
>
> For example changing the dma_resv object to make sure that drivers 
> can't cause use after free errors any more was an extremely annoying 
> experience since every user of those interface had to change at once.
>
> Regards,
> Christian.
>
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10  9:39                                 ` Thomas Hellström
  (?)
@ 2023-11-10 10:42                                   ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10 10:42 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>
> [SNIP]

> I was thinking more of the general design of a base-class that needs 
> to be refcounted. Say a driver vm that inherits from gpu-vm, 
> gem_object and yet another base-class that supplies its own refcount. 
> What's the best-practice way to do refcounting? All base-classes 
> supplying a refcount of its own, or the subclass supplying a refcount 
> and the base-classes supply destroy helpers.

 From my experience the most common design pattern in the Linux kernel 
is that you either have reference counted objects which contain a 
private pointer (like struct file, struct inode etc..) or the lifetime 
is defined by the user of the object instead of reference counting and 
in this case you can embed it into your own object.

>
> But to be clear this is nothing I see needing urgent attention.
>
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>> that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the 
>>>>> context for the put() call: If the driver needs to be able to call 
>>>>> put() from irq / atomic context but the base-class'es destructor 
>>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>>> work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>>> number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for 
>>>> mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know 
>>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>>> Instead they get automatically destroyed when the mm_struct is 
>>>> destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this 
>>> might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a 
>>> reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the 
>>> reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>> called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the 
>>> tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated 
>>> page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. 
>>> When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's 
>>> shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not 
>>> the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>> vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell 
>>> entirely. I think we should
>>> really avoid that.
>
> I don't think we need to worry much about this particular locking hell 
> because if we hold

I have to agree with Danilo here. Especially you have cases where you 
usually lock BO->VM (for example eviction) as well as cases where you 
need to lock VM->BO (command submission).

Because of this in amdgpu we used (or abused?) the dma_resv of the root 
BO as lock for the VM. Since this is a ww_mutex locking it in both VM, 
BO as well as BO, VM order works.

Regards,
Christian.

> , for example a vm and bo resv when putting the vm_bo, we need to keep 
> additional strong references for the bo / vm pointer we use for 
> unlocking. Hence putting the vm_bo under those locks can never lead to 
> the vm getting destroyed.
>
> Also, don't we already sort of have a mandatory vm_destroy callback?
>
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;
>
>
>
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the 
>> drm_gem_object_funcs.open callback and freed in 
>> drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there 
>> are still mappings (can trivial be that the app is killed) I would 
>> expect that the drm_gem_object_funcs.close handling is something like 
>> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
>> should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track 
>> of all the mappings of deleted VM_BOs so that higher level can query 
>> those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>> can perfectly be that the app is killed because of an OOM situation 
>> and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone 
>>>> the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least 
>>>> the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>>> I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this 
>>> series.
>
> +1.
>
> /Thomas
>
>
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
>> my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a 
>> common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers 
>> can't cause use after free errors any more was an extremely annoying 
>> experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 10:42                                   ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10 10:42 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>
> [SNIP]

> I was thinking more of the general design of a base-class that needs 
> to be refcounted. Say a driver vm that inherits from gpu-vm, 
> gem_object and yet another base-class that supplies its own refcount. 
> What's the best-practice way to do refcounting? All base-classes 
> supplying a refcount of its own, or the subclass supplying a refcount 
> and the base-classes supply destroy helpers.

 From my experience the most common design pattern in the Linux kernel 
is that you either have reference counted objects which contain a 
private pointer (like struct file, struct inode etc..) or the lifetime 
is defined by the user of the object instead of reference counting and 
in this case you can embed it into your own object.

>
> But to be clear this is nothing I see needing urgent attention.
>
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>> that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the 
>>>>> context for the put() call: If the driver needs to be able to call 
>>>>> put() from irq / atomic context but the base-class'es destructor 
>>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>>> work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>>> number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for 
>>>> mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know 
>>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>>> Instead they get automatically destroyed when the mm_struct is 
>>>> destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this 
>>> might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a 
>>> reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the 
>>> reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>> called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the 
>>> tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated 
>>> page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. 
>>> When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's 
>>> shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not 
>>> the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>> vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell 
>>> entirely. I think we should
>>> really avoid that.
>
> I don't think we need to worry much about this particular locking hell 
> because if we hold

I have to agree with Danilo here. Especially you have cases where you 
usually lock BO->VM (for example eviction) as well as cases where you 
need to lock VM->BO (command submission).

Because of this in amdgpu we used (or abused?) the dma_resv of the root 
BO as lock for the VM. Since this is a ww_mutex locking it in both VM, 
BO as well as BO, VM order works.

Regards,
Christian.

> , for example a vm and bo resv when putting the vm_bo, we need to keep 
> additional strong references for the bo / vm pointer we use for 
> unlocking. Hence putting the vm_bo under those locks can never lead to 
> the vm getting destroyed.
>
> Also, don't we already sort of have a mandatory vm_destroy callback?
>
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;
>
>
>
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the 
>> drm_gem_object_funcs.open callback and freed in 
>> drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there 
>> are still mappings (can trivial be that the app is killed) I would 
>> expect that the drm_gem_object_funcs.close handling is something like 
>> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
>> should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track 
>> of all the mappings of deleted VM_BOs so that higher level can query 
>> those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>> can perfectly be that the app is killed because of an OOM situation 
>> and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone 
>>>> the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least 
>>>> the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>>> I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this 
>>> series.
>
> +1.
>
> /Thomas
>
>
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
>> my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a 
>> common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers 
>> can't cause use after free errors any more was an extremely annoying 
>> experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 10:42                                   ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-10 10:42 UTC (permalink / raw)
  To: Thomas Hellström, Danilo Krummrich
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>
> [SNIP]

> I was thinking more of the general design of a base-class that needs 
> to be refcounted. Say a driver vm that inherits from gpu-vm, 
> gem_object and yet another base-class that supplies its own refcount. 
> What's the best-practice way to do refcounting? All base-classes 
> supplying a refcount of its own, or the subclass supplying a refcount 
> and the base-classes supply destroy helpers.

 From my experience the most common design pattern in the Linux kernel 
is that you either have reference counted objects which contain a 
private pointer (like struct file, struct inode etc..) or the lifetime 
is defined by the user of the object instead of reference counting and 
in this case you can embed it into your own object.

>
> But to be clear this is nothing I see needing urgent attention.
>
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>> that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the 
>>>>> context for the put() call: If the driver needs to be able to call 
>>>>> put() from irq / atomic context but the base-class'es destructor 
>>>>> doesn't allow atomic context, the driver can push freeing out to a 
>>>>> work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the 
>>>>> number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for 
>>>> mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know 
>>>> vm_area_structs doesn't grab a reference to their mm_struct either. 
>>>> Instead they get automatically destroyed when the mm_struct is 
>>>> destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this 
>>> might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a 
>>> reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the 
>>> reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>> called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the 
>>> tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated 
>>> page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. 
>>> When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's 
>>> shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not 
>>> the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>> vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell 
>>> entirely. I think we should
>>> really avoid that.
>
> I don't think we need to worry much about this particular locking hell 
> because if we hold

I have to agree with Danilo here. Especially you have cases where you 
usually lock BO->VM (for example eviction) as well as cases where you 
need to lock VM->BO (command submission).

Because of this in amdgpu we used (or abused?) the dma_resv of the root 
BO as lock for the VM. Since this is a ww_mutex locking it in both VM, 
BO as well as BO, VM order works.

Regards,
Christian.

> , for example a vm and bo resv when putting the vm_bo, we need to keep 
> additional strong references for the bo / vm pointer we use for 
> unlocking. Hence putting the vm_bo under those locks can never lead to 
> the vm getting destroyed.
>
> Also, don't we already sort of have a mandatory vm_destroy callback?
>
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;
>
>
>
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the 
>> drm_gem_object_funcs.open callback and freed in 
>> drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there 
>> are still mappings (can trivial be that the app is killed) I would 
>> expect that the drm_gem_object_funcs.close handling is something like 
>> asking drm_gpuvm destroying the VM_BO and getting the mappings which 
>> should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track 
>> of all the mappings of deleted VM_BOs so that higher level can query 
>> those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>> can perfectly be that the app is killed because of an OOM situation 
>> and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone 
>>>> the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least 
>>>> the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless 
>>>>> I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this 
>>> series.
>
> +1.
>
> /Thomas
>
>
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add 
>> my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a 
>> common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers 
>> can't cause use after free errors any more was an extremely annoying 
>> experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10 10:42                                   ` Christian König
  (?)
@ 2023-11-10 10:52                                     ` Thomas Hellström
  -1 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10 10:52 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel


On 11/10/23 11:42, Christian König wrote:
> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>
>> [SNIP]
>
>> I was thinking more of the general design of a base-class that needs 
>> to be refcounted. Say a driver vm that inherits from gpu-vm, 
>> gem_object and yet another base-class that supplies its own refcount. 
>> What's the best-practice way to do refcounting? All base-classes 
>> supplying a refcount of its own, or the subclass supplying a refcount 
>> and the base-classes supply destroy helpers.
>
> From my experience the most common design pattern in the Linux kernel 
> is that you either have reference counted objects which contain a 
> private pointer (like struct file, struct inode etc..) or the lifetime 
> is defined by the user of the object instead of reference counting and 
> in this case you can embed it into your own object.
>
>>
>> But to be clear this is nothing I see needing urgent attention.
>>
>>>
>>>>
>>>>>
>>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>>> that this works, but I would rather not try if avoidable.
>>>>>
>>>>>>
>>>>>> That would also make it possible for the driver to decide the 
>>>>>> context for the put() call: If the driver needs to be able to 
>>>>>> call put() from irq / atomic context but the base-class'es 
>>>>>> destructor doesn't allow atomic context, the driver can push 
>>>>>> freeing out to a work item if needed.
>>>>>>
>>>>>> Finally, the refcount overflow Christian pointed out. Limiting 
>>>>>> the number of mapping sounds like a reasonable remedy to me.
>>>>>
>>>>> Well that depends, I would rather avoid having a dependency for 
>>>>> mappings.
>>>>>
>>>>> Taking the CPU VM handling as example as far as I know 
>>>>> vm_area_structs doesn't grab a reference to their mm_struct 
>>>>> either. Instead they get automatically destroyed when the 
>>>>> mm_struct is destroyed.
>>>>
>>>> Certainly, that would be possible. However, thinking about it, this 
>>>> might call for
>>>> huge trouble.
>>>>
>>>> First of all, we'd still need to reference count a GPUVM and take a 
>>>> reference for each
>>>> VM_BO, as we do already. Now instead of simply increasing the 
>>>> reference count for each
>>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>>> called when the GPUVM
>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>
>>>> The reason is that GPUVM can't just remove all mappings from the 
>>>> tree nor can it free them
>>>> by itself, since drivers might use them for tracking their 
>>>> allocated page tables and/or
>>>> other stuff.
>>>>
>>>> Now, let's think about the scope this callback might be called 
>>>> from. When a VM_BO is destroyed
>>>> the driver might hold a couple of locks (for Xe it would be the 
>>>> VM's shared dma-resv lock and
>>>> potentially the corresponding object's dma-resv lock if they're not 
>>>> the same already). If
>>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>>> vm_destroy() callback would
>>>> be called with those locks being held as well.
>>>>
>>>> I feel like doing this finally opens the doors of the locking hell 
>>>> entirely. I think we should
>>>> really avoid that.
>>
>> I don't think we need to worry much about this particular locking 
>> hell because if we hold
>
> I have to agree with Danilo here. Especially you have cases where you 
> usually lock BO->VM (for example eviction) as well as cases where you 
> need to lock VM->BO (command submission).
>
> Because of this in amdgpu we used (or abused?) the dma_resv of the 
> root BO as lock for the VM. Since this is a ww_mutex locking it in 
> both VM, BO as well as BO, VM order works.

Yes, gpuvm is doing the same. (although not necessarily using the 
page-table root bo, but any bo of the driver's choice). But I read it as 
Danilo feared the case where the VM destructor was called with a VM resv 
(or possibly bo resv) held. I meant the driver can easily ensure that's 
not happening, and in some cases it can't happen.

Thanks,

Thomas



>
> Regards,
> Christian.
>
>> , for example a vm and bo resv when putting the vm_bo, we need to 
>> keep additional strong references for the bo / vm pointer we use for 
>> unlocking. Hence putting the vm_bo under those locks can never lead 
>> to the vm getting destroyed.
>>
>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>
>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +        return;
>>
>>
>>
>>>
>>> That's a really good point, but I fear exactly that's the use case.
>>>
>>> I would expect that VM_BO structures are added in the 
>>> drm_gem_object_funcs.open callback and freed in 
>>> drm_gem_object_funcs.close.
>>>
>>> Since it is perfectly legal for userspace to close a BO while there 
>>> are still mappings (can trivial be that the app is killed) I would 
>>> expect that the drm_gem_object_funcs.close handling is something 
>>> like asking drm_gpuvm destroying the VM_BO and getting the mappings 
>>> which should be cleared in the page table in return.
>>>
>>> In amdgpu we even go a step further and the VM structure keeps track 
>>> of all the mappings of deleted VM_BOs so that higher level can query 
>>> those and clear them later on.
>>>
>>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>>> can perfectly be that the app is killed because of an OOM situation 
>>> and we can't do page tables updates in that moment because of this.
>>>
>>>>
>>>>>
>>>>> Which makes sense in that case because when the mm_struct is gone 
>>>>> the vm_area_struct doesn't make sense any more either.
>>>>>
>>>>> What we clearly need is a reference to prevent the VM or at least 
>>>>> the shared resv to go away to early.
>>>>
>>>> Yeah, that was a good hint and we've covered that.
>>>>
>>>>>
>>>>> Regards,
>>>>> Christian.
>>>>>
>>>>>>
>>>>>> But I think all of this is fixable as follow-ups if needed, 
>>>>>> unless I'm missing something crucial.
>>>>
>>>> Fully agree, I think at this point we should go ahead and land this 
>>>> series.
>>
>> +1.
>>
>> /Thomas
>>
>>
>>>>
>>>
>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to 
>>> add my acked-by as well if you want.
>>>
>>> Only keep in mind that when you give drivers some functionality in a 
>>> common component they usually expect to keep that functionality.
>>>
>>> For example changing the dma_resv object to make sure that drivers 
>>> can't cause use after free errors any more was an extremely annoying 
>>> experience since every user of those interface had to change at once.
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>>>>
>>>>>> Just my 2 cents.
>>>>>>
>>>>>> /Thomas
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 10:52                                     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10 10:52 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson


On 11/10/23 11:42, Christian König wrote:
> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>
>> [SNIP]
>
>> I was thinking more of the general design of a base-class that needs 
>> to be refcounted. Say a driver vm that inherits from gpu-vm, 
>> gem_object and yet another base-class that supplies its own refcount. 
>> What's the best-practice way to do refcounting? All base-classes 
>> supplying a refcount of its own, or the subclass supplying a refcount 
>> and the base-classes supply destroy helpers.
>
> From my experience the most common design pattern in the Linux kernel 
> is that you either have reference counted objects which contain a 
> private pointer (like struct file, struct inode etc..) or the lifetime 
> is defined by the user of the object instead of reference counting and 
> in this case you can embed it into your own object.
>
>>
>> But to be clear this is nothing I see needing urgent attention.
>>
>>>
>>>>
>>>>>
>>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>>> that this works, but I would rather not try if avoidable.
>>>>>
>>>>>>
>>>>>> That would also make it possible for the driver to decide the 
>>>>>> context for the put() call: If the driver needs to be able to 
>>>>>> call put() from irq / atomic context but the base-class'es 
>>>>>> destructor doesn't allow atomic context, the driver can push 
>>>>>> freeing out to a work item if needed.
>>>>>>
>>>>>> Finally, the refcount overflow Christian pointed out. Limiting 
>>>>>> the number of mapping sounds like a reasonable remedy to me.
>>>>>
>>>>> Well that depends, I would rather avoid having a dependency for 
>>>>> mappings.
>>>>>
>>>>> Taking the CPU VM handling as example as far as I know 
>>>>> vm_area_structs doesn't grab a reference to their mm_struct 
>>>>> either. Instead they get automatically destroyed when the 
>>>>> mm_struct is destroyed.
>>>>
>>>> Certainly, that would be possible. However, thinking about it, this 
>>>> might call for
>>>> huge trouble.
>>>>
>>>> First of all, we'd still need to reference count a GPUVM and take a 
>>>> reference for each
>>>> VM_BO, as we do already. Now instead of simply increasing the 
>>>> reference count for each
>>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>>> called when the GPUVM
>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>
>>>> The reason is that GPUVM can't just remove all mappings from the 
>>>> tree nor can it free them
>>>> by itself, since drivers might use them for tracking their 
>>>> allocated page tables and/or
>>>> other stuff.
>>>>
>>>> Now, let's think about the scope this callback might be called 
>>>> from. When a VM_BO is destroyed
>>>> the driver might hold a couple of locks (for Xe it would be the 
>>>> VM's shared dma-resv lock and
>>>> potentially the corresponding object's dma-resv lock if they're not 
>>>> the same already). If
>>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>>> vm_destroy() callback would
>>>> be called with those locks being held as well.
>>>>
>>>> I feel like doing this finally opens the doors of the locking hell 
>>>> entirely. I think we should
>>>> really avoid that.
>>
>> I don't think we need to worry much about this particular locking 
>> hell because if we hold
>
> I have to agree with Danilo here. Especially you have cases where you 
> usually lock BO->VM (for example eviction) as well as cases where you 
> need to lock VM->BO (command submission).
>
> Because of this in amdgpu we used (or abused?) the dma_resv of the 
> root BO as lock for the VM. Since this is a ww_mutex locking it in 
> both VM, BO as well as BO, VM order works.

Yes, gpuvm is doing the same. (although not necessarily using the 
page-table root bo, but any bo of the driver's choice). But I read it as 
Danilo feared the case where the VM destructor was called with a VM resv 
(or possibly bo resv) held. I meant the driver can easily ensure that's 
not happening, and in some cases it can't happen.

Thanks,

Thomas



>
> Regards,
> Christian.
>
>> , for example a vm and bo resv when putting the vm_bo, we need to 
>> keep additional strong references for the bo / vm pointer we use for 
>> unlocking. Hence putting the vm_bo under those locks can never lead 
>> to the vm getting destroyed.
>>
>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>
>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +        return;
>>
>>
>>
>>>
>>> That's a really good point, but I fear exactly that's the use case.
>>>
>>> I would expect that VM_BO structures are added in the 
>>> drm_gem_object_funcs.open callback and freed in 
>>> drm_gem_object_funcs.close.
>>>
>>> Since it is perfectly legal for userspace to close a BO while there 
>>> are still mappings (can trivial be that the app is killed) I would 
>>> expect that the drm_gem_object_funcs.close handling is something 
>>> like asking drm_gpuvm destroying the VM_BO and getting the mappings 
>>> which should be cleared in the page table in return.
>>>
>>> In amdgpu we even go a step further and the VM structure keeps track 
>>> of all the mappings of deleted VM_BOs so that higher level can query 
>>> those and clear them later on.
>>>
>>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>>> can perfectly be that the app is killed because of an OOM situation 
>>> and we can't do page tables updates in that moment because of this.
>>>
>>>>
>>>>>
>>>>> Which makes sense in that case because when the mm_struct is gone 
>>>>> the vm_area_struct doesn't make sense any more either.
>>>>>
>>>>> What we clearly need is a reference to prevent the VM or at least 
>>>>> the shared resv to go away to early.
>>>>
>>>> Yeah, that was a good hint and we've covered that.
>>>>
>>>>>
>>>>> Regards,
>>>>> Christian.
>>>>>
>>>>>>
>>>>>> But I think all of this is fixable as follow-ups if needed, 
>>>>>> unless I'm missing something crucial.
>>>>
>>>> Fully agree, I think at this point we should go ahead and land this 
>>>> series.
>>
>> +1.
>>
>> /Thomas
>>
>>
>>>>
>>>
>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to 
>>> add my acked-by as well if you want.
>>>
>>> Only keep in mind that when you give drivers some functionality in a 
>>> common component they usually expect to keep that functionality.
>>>
>>> For example changing the dma_resv object to make sure that drivers 
>>> can't cause use after free errors any more was an extremely annoying 
>>> experience since every user of those interface had to change at once.
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>>>>
>>>>>> Just my 2 cents.
>>>>>>
>>>>>> /Thomas
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 10:52                                     ` Thomas Hellström
  0 siblings, 0 replies; 141+ messages in thread
From: Thomas Hellström @ 2023-11-10 10:52 UTC (permalink / raw)
  To: Christian König, Danilo Krummrich
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel


On 11/10/23 11:42, Christian König wrote:
> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>
>> [SNIP]
>
>> I was thinking more of the general design of a base-class that needs 
>> to be refcounted. Say a driver vm that inherits from gpu-vm, 
>> gem_object and yet another base-class that supplies its own refcount. 
>> What's the best-practice way to do refcounting? All base-classes 
>> supplying a refcount of its own, or the subclass supplying a refcount 
>> and the base-classes supply destroy helpers.
>
> From my experience the most common design pattern in the Linux kernel 
> is that you either have reference counted objects which contain a 
> private pointer (like struct file, struct inode etc..) or the lifetime 
> is defined by the user of the object instead of reference counting and 
> in this case you can embed it into your own object.
>
>>
>> But to be clear this is nothing I see needing urgent attention.
>>
>>>
>>>>
>>>>>
>>>>> Well, I have never seen stuff like that in the kernel. Might be 
>>>>> that this works, but I would rather not try if avoidable.
>>>>>
>>>>>>
>>>>>> That would also make it possible for the driver to decide the 
>>>>>> context for the put() call: If the driver needs to be able to 
>>>>>> call put() from irq / atomic context but the base-class'es 
>>>>>> destructor doesn't allow atomic context, the driver can push 
>>>>>> freeing out to a work item if needed.
>>>>>>
>>>>>> Finally, the refcount overflow Christian pointed out. Limiting 
>>>>>> the number of mapping sounds like a reasonable remedy to me.
>>>>>
>>>>> Well that depends, I would rather avoid having a dependency for 
>>>>> mappings.
>>>>>
>>>>> Taking the CPU VM handling as example as far as I know 
>>>>> vm_area_structs doesn't grab a reference to their mm_struct 
>>>>> either. Instead they get automatically destroyed when the 
>>>>> mm_struct is destroyed.
>>>>
>>>> Certainly, that would be possible. However, thinking about it, this 
>>>> might call for
>>>> huge trouble.
>>>>
>>>> First of all, we'd still need to reference count a GPUVM and take a 
>>>> reference for each
>>>> VM_BO, as we do already. Now instead of simply increasing the 
>>>> reference count for each
>>>> mapping as well, we'd need a *mandatory* driver callback that is 
>>>> called when the GPUVM
>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>
>>>> The reason is that GPUVM can't just remove all mappings from the 
>>>> tree nor can it free them
>>>> by itself, since drivers might use them for tracking their 
>>>> allocated page tables and/or
>>>> other stuff.
>>>>
>>>> Now, let's think about the scope this callback might be called 
>>>> from. When a VM_BO is destroyed
>>>> the driver might hold a couple of locks (for Xe it would be the 
>>>> VM's shared dma-resv lock and
>>>> potentially the corresponding object's dma-resv lock if they're not 
>>>> the same already). If
>>>> destroying this VM_BO leads to the VM being destroyed, the drivers 
>>>> vm_destroy() callback would
>>>> be called with those locks being held as well.
>>>>
>>>> I feel like doing this finally opens the doors of the locking hell 
>>>> entirely. I think we should
>>>> really avoid that.
>>
>> I don't think we need to worry much about this particular locking 
>> hell because if we hold
>
> I have to agree with Danilo here. Especially you have cases where you 
> usually lock BO->VM (for example eviction) as well as cases where you 
> need to lock VM->BO (command submission).
>
> Because of this in amdgpu we used (or abused?) the dma_resv of the 
> root BO as lock for the VM. Since this is a ww_mutex locking it in 
> both VM, BO as well as BO, VM order works.

Yes, gpuvm is doing the same. (although not necessarily using the 
page-table root bo, but any bo of the driver's choice). But I read it as 
Danilo feared the case where the VM destructor was called with a VM resv 
(or possibly bo resv) held. I meant the driver can easily ensure that's 
not happening, and in some cases it can't happen.

Thanks,

Thomas



>
> Regards,
> Christian.
>
>> , for example a vm and bo resv when putting the vm_bo, we need to 
>> keep additional strong references for the bo / vm pointer we use for 
>> unlocking. Hence putting the vm_bo under those locks can never lead 
>> to the vm getting destroyed.
>>
>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>
>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>> +        return;
>>
>>
>>
>>>
>>> That's a really good point, but I fear exactly that's the use case.
>>>
>>> I would expect that VM_BO structures are added in the 
>>> drm_gem_object_funcs.open callback and freed in 
>>> drm_gem_object_funcs.close.
>>>
>>> Since it is perfectly legal for userspace to close a BO while there 
>>> are still mappings (can trivial be that the app is killed) I would 
>>> expect that the drm_gem_object_funcs.close handling is something 
>>> like asking drm_gpuvm destroying the VM_BO and getting the mappings 
>>> which should be cleared in the page table in return.
>>>
>>> In amdgpu we even go a step further and the VM structure keeps track 
>>> of all the mappings of deleted VM_BOs so that higher level can query 
>>> those and clear them later on.
>>>
>>> Background is that the drm_gem_object_funcs.close can't fail, but it 
>>> can perfectly be that the app is killed because of an OOM situation 
>>> and we can't do page tables updates in that moment because of this.
>>>
>>>>
>>>>>
>>>>> Which makes sense in that case because when the mm_struct is gone 
>>>>> the vm_area_struct doesn't make sense any more either.
>>>>>
>>>>> What we clearly need is a reference to prevent the VM or at least 
>>>>> the shared resv to go away to early.
>>>>
>>>> Yeah, that was a good hint and we've covered that.
>>>>
>>>>>
>>>>> Regards,
>>>>> Christian.
>>>>>
>>>>>>
>>>>>> But I think all of this is fixable as follow-ups if needed, 
>>>>>> unless I'm missing something crucial.
>>>>
>>>> Fully agree, I think at this point we should go ahead and land this 
>>>> series.
>>
>> +1.
>>
>> /Thomas
>>
>>
>>>>
>>>
>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to 
>>> add my acked-by as well if you want.
>>>
>>> Only keep in mind that when you give drivers some functionality in a 
>>> common component they usually expect to keep that functionality.
>>>
>>> For example changing the dma_resv object to make sure that drivers 
>>> can't cause use after free errors any more was an extremely annoying 
>>> experience since every user of those interface had to change at once.
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>>>>
>>>>>> Just my 2 cents.
>>>>>>
>>>>>> /Thomas
>>>>>>
>>>>>>
>>>>>
>>>>
>>>
>

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10  9:39                                 ` Thomas Hellström
  (?)
@ 2023-11-10 16:43                                   ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:43 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

On 11/10/23 10:39, Thomas Hellström wrote:
> 
> On 11/10/23 09:50, Christian König wrote:
>> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>>> On 11/9/23 17:03, Christian König wrote:
>>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>>> [SNIP]
>>>>>>>
>>>>> Did we get any resolution on this?
>>>>>
>>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>>
>>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>>
>>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>>
>>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>>
>>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>>
>>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>>> the reference count ensures that. This simply ensures memory safety.
>>>
>>>>
>>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>>
>>> That should be possible.
>>>
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
> 
> But to be clear this is nothing I see needing urgent attention.
> 
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>> really avoid that.
> 
> I don't think we need to worry much about this particular locking hell because if we hold, for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
> 
> Also, don't we already sort of have a mandatory vm_destroy callback?

Sure, I just wanted to say that we'd then have a mandatory callback where drivers *must* ensure
to remove *all* mappings before returning from this callback. I could imagine that there could
be some pitfalls with that.

So I'm not worried about the callback itself being mandatory, but about enforcing this semantics
on it. Maybe I didn't phrase this very well.

> 
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;> 
> 
> 
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this series.
> 
> +1.
> 
> /Thomas
> 
> 
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:43                                   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:43 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

On 11/10/23 10:39, Thomas Hellström wrote:
> 
> On 11/10/23 09:50, Christian König wrote:
>> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>>> On 11/9/23 17:03, Christian König wrote:
>>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>>> [SNIP]
>>>>>>>
>>>>> Did we get any resolution on this?
>>>>>
>>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>>
>>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>>
>>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>>
>>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>>
>>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>>
>>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>>> the reference count ensures that. This simply ensures memory safety.
>>>
>>>>
>>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>>
>>> That should be possible.
>>>
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
> 
> But to be clear this is nothing I see needing urgent attention.
> 
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>> really avoid that.
> 
> I don't think we need to worry much about this particular locking hell because if we hold, for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
> 
> Also, don't we already sort of have a mandatory vm_destroy callback?

Sure, I just wanted to say that we'd then have a mandatory callback where drivers *must* ensure
to remove *all* mappings before returning from this callback. I could imagine that there could
be some pitfalls with that.

So I'm not worried about the callback itself being mandatory, but about enforcing this semantics
on it. Maybe I didn't phrase this very well.

> 
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;> 
> 
> 
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this series.
> 
> +1.
> 
> /Thomas
> 
> 
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:43                                   ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:43 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

On 11/10/23 10:39, Thomas Hellström wrote:
> 
> On 11/10/23 09:50, Christian König wrote:
>> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>>> On 11/9/23 17:03, Christian König wrote:
>>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>>> [SNIP]
>>>>>>>
>>>>> Did we get any resolution on this?
>>>>>
>>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>>
>>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>>
>>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>>
>>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>>
>>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>>
>>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>>> the reference count ensures that. This simply ensures memory safety.
>>>
>>>>
>>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>>
>>> That should be possible.
>>>
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
> 
> But to be clear this is nothing I see needing urgent attention.
> 
>>
>>>
>>>>
>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>
>>>>>
>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>
>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>
>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>
>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>
>>> Certainly, that would be possible. However, thinking about it, this might call for
>>> huge trouble.
>>>
>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>> reference count drops to zero. Maybe something like vm_destroy().
>>>
>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>> other stuff.
>>>
>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>> be called with those locks being held as well.
>>>
>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>> really avoid that.
> 
> I don't think we need to worry much about this particular locking hell because if we hold, for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
> 
> Also, don't we already sort of have a mandatory vm_destroy callback?

Sure, I just wanted to say that we'd then have a mandatory callback where drivers *must* ensure
to remove *all* mappings before returning from this callback. I could imagine that there could
be some pitfalls with that.

So I'm not worried about the callback itself being mandatory, but about enforcing this semantics
on it. Maybe I didn't phrase this very well.

> 
> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
> +        return;> 
> 
> 
>>
>> That's a really good point, but I fear exactly that's the use case.
>>
>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>
>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>
>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>
>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>
>>>
>>>>
>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>
>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>
>>> Yeah, that was a good hint and we've covered that.
>>>
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>
>>> Fully agree, I think at this point we should go ahead and land this series.
> 
> +1.
> 
> /Thomas
> 
> 
>>>
>>
>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>
>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>
>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>
>> Regards,
>> Christian.
>>
>>>
>>>>>
>>>>> Just my 2 cents.
>>>>>
>>>>> /Thomas
>>>>>
>>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10 10:52                                     ` Thomas Hellström
  (?)
@ 2023-11-10 16:49                                       ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:49 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

On 11/10/23 11:52, Thomas Hellström wrote:
> 
> On 11/10/23 11:42, Christian König wrote:
>> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>>
>>> [SNIP]
>>
>>> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
>>
>> From my experience the most common design pattern in the Linux kernel is that you either have reference counted objects which contain a private pointer (like struct file, struct inode etc..) or the lifetime is defined by the user of the object instead of reference counting and in this case you can embed it into your own object.
>>
>>>
>>> But to be clear this is nothing I see needing urgent attention.
>>>
>>>>
>>>>>
>>>>>>
>>>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>>>
>>>>>>>
>>>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>>>
>>>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>>>
>>>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>>>
>>>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>>>
>>>>> Certainly, that would be possible. However, thinking about it, this might call for
>>>>> huge trouble.
>>>>>
>>>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>>
>>>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>>>> other stuff.
>>>>>
>>>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>>>> be called with those locks being held as well.
>>>>>
>>>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>>>> really avoid that.
>>>
>>> I don't think we need to worry much about this particular locking hell because if we hold
>>
>> I have to agree with Danilo here. Especially you have cases where you usually lock BO->VM (for example eviction) as well as cases where you need to lock VM->BO (command submission).
>>
>> Because of this in amdgpu we used (or abused?) the dma_resv of the root BO as lock for the VM. Since this is a ww_mutex locking it in both VM, BO as well as BO, VM order works.
> 
> Yes, gpuvm is doing the same. (although not necessarily using the page-table root bo, but any bo of the driver's choice). But I read it as Danilo feared the case where the VM destructor was called with a VM resv (or possibly bo resv) held. I meant the driver can easily ensure that's not happening, and in some cases it can't happen.

Right, that's what I meant. However, this also comes down to what Christian means. When the callback
is called with the resv locks held, we'd potentially have this locking inversion between
VM lock -> resv lock and resv lock -> VM lock.

> 
> Thanks,
> 
> Thomas
> 
> 
> 
>>
>> Regards,
>> Christian.
>>
>>> , for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
>>>
>>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>>
>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>> +        return;
>>>
>>>
>>>
>>>>
>>>> That's a really good point, but I fear exactly that's the use case.
>>>>
>>>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>>>
>>>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>>>
>>>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>>>
>>>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>>>
>>>>>
>>>>>>
>>>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>>>
>>>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>>>
>>>>> Yeah, that was a good hint and we've covered that.
>>>>>
>>>>>>
>>>>>> Regards,
>>>>>> Christian.
>>>>>>
>>>>>>>
>>>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>>>
>>>>> Fully agree, I think at this point we should go ahead and land this series.
>>>
>>> +1.
>>>
>>> /Thomas
>>>
>>>
>>>>>
>>>>
>>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>>>
>>>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>>>
>>>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>>>>
>>>>>>> Just my 2 cents.
>>>>>>>
>>>>>>> /Thomas
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:49                                       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:49 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

On 11/10/23 11:52, Thomas Hellström wrote:
> 
> On 11/10/23 11:42, Christian König wrote:
>> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>>
>>> [SNIP]
>>
>>> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
>>
>> From my experience the most common design pattern in the Linux kernel is that you either have reference counted objects which contain a private pointer (like struct file, struct inode etc..) or the lifetime is defined by the user of the object instead of reference counting and in this case you can embed it into your own object.
>>
>>>
>>> But to be clear this is nothing I see needing urgent attention.
>>>
>>>>
>>>>>
>>>>>>
>>>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>>>
>>>>>>>
>>>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>>>
>>>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>>>
>>>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>>>
>>>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>>>
>>>>> Certainly, that would be possible. However, thinking about it, this might call for
>>>>> huge trouble.
>>>>>
>>>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>>
>>>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>>>> other stuff.
>>>>>
>>>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>>>> be called with those locks being held as well.
>>>>>
>>>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>>>> really avoid that.
>>>
>>> I don't think we need to worry much about this particular locking hell because if we hold
>>
>> I have to agree with Danilo here. Especially you have cases where you usually lock BO->VM (for example eviction) as well as cases where you need to lock VM->BO (command submission).
>>
>> Because of this in amdgpu we used (or abused?) the dma_resv of the root BO as lock for the VM. Since this is a ww_mutex locking it in both VM, BO as well as BO, VM order works.
> 
> Yes, gpuvm is doing the same. (although not necessarily using the page-table root bo, but any bo of the driver's choice). But I read it as Danilo feared the case where the VM destructor was called with a VM resv (or possibly bo resv) held. I meant the driver can easily ensure that's not happening, and in some cases it can't happen.

Right, that's what I meant. However, this also comes down to what Christian means. When the callback
is called with the resv locks held, we'd potentially have this locking inversion between
VM lock -> resv lock and resv lock -> VM lock.

> 
> Thanks,
> 
> Thomas
> 
> 
> 
>>
>> Regards,
>> Christian.
>>
>>> , for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
>>>
>>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>>
>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>> +        return;
>>>
>>>
>>>
>>>>
>>>> That's a really good point, but I fear exactly that's the use case.
>>>>
>>>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>>>
>>>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>>>
>>>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>>>
>>>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>>>
>>>>>
>>>>>>
>>>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>>>
>>>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>>>
>>>>> Yeah, that was a good hint and we've covered that.
>>>>>
>>>>>>
>>>>>> Regards,
>>>>>> Christian.
>>>>>>
>>>>>>>
>>>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>>>
>>>>> Fully agree, I think at this point we should go ahead and land this series.
>>>
>>> +1.
>>>
>>> /Thomas
>>>
>>>
>>>>>
>>>>
>>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>>>
>>>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>>>
>>>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>>>>
>>>>>>> Just my 2 cents.
>>>>>>>
>>>>>>> /Thomas
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:49                                       ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:49 UTC (permalink / raw)
  To: Thomas Hellström, Christian König
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

On 11/10/23 11:52, Thomas Hellström wrote:
> 
> On 11/10/23 11:42, Christian König wrote:
>> Am 10.11.23 um 10:39 schrieb Thomas Hellström:
>>>
>>> [SNIP]
>>
>>> I was thinking more of the general design of a base-class that needs to be refcounted. Say a driver vm that inherits from gpu-vm, gem_object and yet another base-class that supplies its own refcount. What's the best-practice way to do refcounting? All base-classes supplying a refcount of its own, or the subclass supplying a refcount and the base-classes supply destroy helpers.
>>
>> From my experience the most common design pattern in the Linux kernel is that you either have reference counted objects which contain a private pointer (like struct file, struct inode etc..) or the lifetime is defined by the user of the object instead of reference counting and in this case you can embed it into your own object.
>>
>>>
>>> But to be clear this is nothing I see needing urgent attention.
>>>
>>>>
>>>>>
>>>>>>
>>>>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>>>>
>>>>>>>
>>>>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>>>>
>>>>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>>>>
>>>>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>>>>
>>>>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>>>>
>>>>> Certainly, that would be possible. However, thinking about it, this might call for
>>>>> huge trouble.
>>>>>
>>>>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>>>>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>>>>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>>>>> reference count drops to zero. Maybe something like vm_destroy().
>>>>>
>>>>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>>>>> by itself, since drivers might use them for tracking their allocated page tables and/or
>>>>> other stuff.
>>>>>
>>>>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>>>>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>>>>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>>>>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>>>>> be called with those locks being held as well.
>>>>>
>>>>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>>>>> really avoid that.
>>>
>>> I don't think we need to worry much about this particular locking hell because if we hold
>>
>> I have to agree with Danilo here. Especially you have cases where you usually lock BO->VM (for example eviction) as well as cases where you need to lock VM->BO (command submission).
>>
>> Because of this in amdgpu we used (or abused?) the dma_resv of the root BO as lock for the VM. Since this is a ww_mutex locking it in both VM, BO as well as BO, VM order works.
> 
> Yes, gpuvm is doing the same. (although not necessarily using the page-table root bo, but any bo of the driver's choice). But I read it as Danilo feared the case where the VM destructor was called with a VM resv (or possibly bo resv) held. I meant the driver can easily ensure that's not happening, and in some cases it can't happen.

Right, that's what I meant. However, this also comes down to what Christian means. When the callback
is called with the resv locks held, we'd potentially have this locking inversion between
VM lock -> resv lock and resv lock -> VM lock.

> 
> Thanks,
> 
> Thomas
> 
> 
> 
>>
>> Regards,
>> Christian.
>>
>>> , for example a vm and bo resv when putting the vm_bo, we need to keep additional strong references for the bo / vm pointer we use for unlocking. Hence putting the vm_bo under those locks can never lead to the vm getting destroyed.
>>>
>>> Also, don't we already sort of have a mandatory vm_destroy callback?
>>>
>>> +    if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
>>> +        return;
>>>
>>>
>>>
>>>>
>>>> That's a really good point, but I fear exactly that's the use case.
>>>>
>>>> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
>>>>
>>>> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.
>>>>
>>>> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
>>>>
>>>> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
>>>>
>>>>>
>>>>>>
>>>>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>>>>
>>>>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>>>>
>>>>> Yeah, that was a good hint and we've covered that.
>>>>>
>>>>>>
>>>>>> Regards,
>>>>>> Christian.
>>>>>>
>>>>>>>
>>>>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>>>>
>>>>> Fully agree, I think at this point we should go ahead and land this series.
>>>
>>> +1.
>>>
>>> /Thomas
>>>
>>>
>>>>>
>>>>
>>>> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
>>>>
>>>> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
>>>>
>>>> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
>>>>
>>>> Regards,
>>>> Christian.
>>>>
>>>>>
>>>>>>>
>>>>>>> Just my 2 cents.
>>>>>>>
>>>>>>> /Thomas
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10  8:50                               ` Christian König
  (?)
@ 2023-11-10 16:57                                 ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>
>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
> 
> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?

Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)

> 
> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>> by itself, since drivers might use them for tracking their allocated page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>> really avoid that.
> 
> That's a really good point, but I fear exactly that's the use case.
> 
> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
> 
> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.

Yes, you can do that. You can ask GPUVM to create a set of operations for you to shut down all
mappings of a given VM_BO. But until this is done the VM_BO structure is kept alive. Semantically,
it's exactly the same though.

> 
> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
> 
> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
> 
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this series.
> 
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
> 
> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
> 
> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
> 
> Regards,
> Christian.
> 
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:57                                 ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>
>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
> 
> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?

Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)

> 
> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>> by itself, since drivers might use them for tracking their allocated page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>> really avoid that.
> 
> That's a really good point, but I fear exactly that's the use case.
> 
> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
> 
> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.

Yes, you can do that. You can ask GPUVM to create a set of operations for you to shut down all
mappings of a given VM_BO. But until this is done the VM_BO structure is kept alive. Semantically,
it's exactly the same though.

> 
> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
> 
> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
> 
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this series.
> 
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
> 
> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
> 
> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
> 
> Regards,
> Christian.
> 
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-10 16:57                                 ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-10 16:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

On 11/10/23 09:50, Christian König wrote:
> Am 09.11.23 um 19:34 schrieb Danilo Krummrich:
>> On 11/9/23 17:03, Christian König wrote:
>>> Am 09.11.23 um 16:50 schrieb Thomas Hellström:
>>>> [SNIP]
>>>>>>
>>>> Did we get any resolution on this?
>>>>
>>>> FWIW, my take on this is that it would be possible to get GPUVM to work both with and without internal refcounting; If with, the driver needs a vm close to resolve cyclic references, if without that's not necessary. If GPUVM is allowed to refcount in mappings and vm_bos, that comes with a slight performance drop but as Danilo pointed out, the VM lifetime problem iterating over a vm_bo's mapping becomes much easier and the code thus becomes easier to maintain moving forward. That convinced me it's a good thing.
>>>
>>> I strongly believe you guys stumbled over one of the core problems with the VM here and I think that reference counting is the right answer to solving this.
>>>
>>> The big question is that what is reference counted and in which direction does the dependency points, e.g. we have here VM, BO, BO_VM and Mapping objects.
>>>
>>> Those patches here suggest a counted Mapping -> VM reference and I'm pretty sure that this isn't a good idea. What we should rather really have is a BO -> VM or BO_VM ->VM reference. In other words that each BO which is part of the VM keeps a reference to the VM.
>>
>> We have both. Please see the subsequent patch introducing VM_BO structures for that.
>>
>> As I explained, mappings (struct drm_gpuva) keep a pointer to their VM they're mapped
>> in and besides that it doesn't make sense to free a VM that still contains mappings,
>> the reference count ensures that. This simply ensures memory safety.
>>
>>>
>>> BTW: At least in amdgpu we can have BOs which (temporary) doesn't have any mappings, but are still considered part of the VM.
>>
>> That should be possible.
>>
>>>
>>>>
>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>
>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
> 
> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?

Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)

> 
> As far as I can see a driver would want to embed that into your fpriv structure which is allocated during drm_driver.open callback.
> 
>>
>>>
>>> Well, I have never seen stuff like that in the kernel. Might be that this works, but I would rather not try if avoidable.
>>>
>>>>
>>>> That would also make it possible for the driver to decide the context for the put() call: If the driver needs to be able to call put() from irq / atomic context but the base-class'es destructor doesn't allow atomic context, the driver can push freeing out to a work item if needed.
>>>>
>>>> Finally, the refcount overflow Christian pointed out. Limiting the number of mapping sounds like a reasonable remedy to me.
>>>
>>> Well that depends, I would rather avoid having a dependency for mappings.
>>>
>>> Taking the CPU VM handling as example as far as I know vm_area_structs doesn't grab a reference to their mm_struct either. Instead they get automatically destroyed when the mm_struct is destroyed.
>>
>> Certainly, that would be possible. However, thinking about it, this might call for
>> huge trouble.
>>
>> First of all, we'd still need to reference count a GPUVM and take a reference for each
>> VM_BO, as we do already. Now instead of simply increasing the reference count for each
>> mapping as well, we'd need a *mandatory* driver callback that is called when the GPUVM
>> reference count drops to zero. Maybe something like vm_destroy().
>>
>> The reason is that GPUVM can't just remove all mappings from the tree nor can it free them
>> by itself, since drivers might use them for tracking their allocated page tables and/or
>> other stuff.
>>
>> Now, let's think about the scope this callback might be called from. When a VM_BO is destroyed
>> the driver might hold a couple of locks (for Xe it would be the VM's shared dma-resv lock and
>> potentially the corresponding object's dma-resv lock if they're not the same already). If
>> destroying this VM_BO leads to the VM being destroyed, the drivers vm_destroy() callback would
>> be called with those locks being held as well.
>>
>> I feel like doing this finally opens the doors of the locking hell entirely. I think we should
>> really avoid that.
> 
> That's a really good point, but I fear exactly that's the use case.
> 
> I would expect that VM_BO structures are added in the drm_gem_object_funcs.open callback and freed in drm_gem_object_funcs.close.
> 
> Since it is perfectly legal for userspace to close a BO while there are still mappings (can trivial be that the app is killed) I would expect that the drm_gem_object_funcs.close handling is something like asking drm_gpuvm destroying the VM_BO and getting the mappings which should be cleared in the page table in return.

Yes, you can do that. You can ask GPUVM to create a set of operations for you to shut down all
mappings of a given VM_BO. But until this is done the VM_BO structure is kept alive. Semantically,
it's exactly the same though.

> 
> In amdgpu we even go a step further and the VM structure keeps track of all the mappings of deleted VM_BOs so that higher level can query those and clear them later on.
> 
> Background is that the drm_gem_object_funcs.close can't fail, but it can perfectly be that the app is killed because of an OOM situation and we can't do page tables updates in that moment because of this.
> 
>>
>>>
>>> Which makes sense in that case because when the mm_struct is gone the vm_area_struct doesn't make sense any more either.
>>>
>>> What we clearly need is a reference to prevent the VM or at least the shared resv to go away to early.
>>
>> Yeah, that was a good hint and we've covered that.
>>
>>>
>>> Regards,
>>> Christian.
>>>
>>>>
>>>> But I think all of this is fixable as follow-ups if needed, unless I'm missing something crucial.
>>
>> Fully agree, I think at this point we should go ahead and land this series.
> 
> Yeah, agree this is not UAPI so not nailed in stone. Feel free to add my acked-by as well if you want.
> 
> Only keep in mind that when you give drivers some functionality in a common component they usually expect to keep that functionality.
> 
> For example changing the dma_resv object to make sure that drivers can't cause use after free errors any more was an extremely annoying experience since every user of those interface had to change at once.
> 
> Regards,
> Christian.
> 
>>
>>>>
>>>> Just my 2 cents.
>>>>
>>>> /Thomas
>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-10 16:57                                 ` Danilo Krummrich
  (?)
@ 2023-11-13  7:22                                   ` Christian König
  -1 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-13  7:22 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
> On 11/10/23 09:50, Christian König wrote:
>> [SNIP]
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to 
>>>>> be embeddable (a base class) shouldn't really have its own 
>>>>> refcount. I think that's a valid point. If you at some point need 
>>>>> to derive from multiple such structs each having its own refcount, 
>>>>> things will start to get weird. One way to resolve that would be 
>>>>> to have the driver's subclass provide get() and put() ops, and 
>>>>> export a destructor for the base-class, rather than to have the 
>>>>> base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>>> And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>>> driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using 
>> the driver? Or am I missing something?
>
> Right, *one*, but not more than one. Wasn't that the concern? Maybe I 
> misunderstood something. :)

Well, there is the use case of native context with XEN/KVM. In that 
situation QEMU opens tons of driver file descriptors on behalves of the 
virtual environment clients.

In this use case you have many drm_gpuvm instances for a single 
application. So you can't assume that you only have one VM per PID/TGID 
or something like that.

AMD already made that mistake with KFD and I strongly suggest not to 
repeat it :)

Regards,
Christian.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-13  7:22                                   ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-13  7:22 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
> On 11/10/23 09:50, Christian König wrote:
>> [SNIP]
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to 
>>>>> be embeddable (a base class) shouldn't really have its own 
>>>>> refcount. I think that's a valid point. If you at some point need 
>>>>> to derive from multiple such structs each having its own refcount, 
>>>>> things will start to get weird. One way to resolve that would be 
>>>>> to have the driver's subclass provide get() and put() ops, and 
>>>>> export a destructor for the base-class, rather than to have the 
>>>>> base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>>> And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>>> driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using 
>> the driver? Or am I missing something?
>
> Right, *one*, but not more than one. Wasn't that the concern? Maybe I 
> misunderstood something. :)

Well, there is the use case of native context with XEN/KVM. In that 
situation QEMU opens tons of driver file descriptors on behalves of the 
virtual environment clients.

In this use case you have many drm_gpuvm instances for a single 
application. So you can't assume that you only have one VM per PID/TGID 
or something like that.

AMD already made that mistake with KFD and I strongly suggest not to 
repeat it :)

Regards,
Christian.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-13  7:22                                   ` Christian König
  0 siblings, 0 replies; 141+ messages in thread
From: Christian König @ 2023-11-13  7:22 UTC (permalink / raw)
  To: Danilo Krummrich, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
> On 11/10/23 09:50, Christian König wrote:
>> [SNIP]
>>>>
>>>>>
>>>>> Another issue Christian brought up is that something intended to 
>>>>> be embeddable (a base class) shouldn't really have its own 
>>>>> refcount. I think that's a valid point. If you at some point need 
>>>>> to derive from multiple such structs each having its own refcount, 
>>>>> things will start to get weird. One way to resolve that would be 
>>>>> to have the driver's subclass provide get() and put() ops, and 
>>>>> export a destructor for the base-class, rather than to have the 
>>>>> base-class provide the refcount and a destructor  ops.
>>>
>>> GPUVM simply follows the same pattern we have with drm_gem_objects. 
>>> And I think it makes
>>> sense. Why would we want to embed two struct drm_gpuvm in a single 
>>> driver structure?
>>
>> Because you need one drm_gpuvm structure for each application using 
>> the driver? Or am I missing something?
>
> Right, *one*, but not more than one. Wasn't that the concern? Maybe I 
> misunderstood something. :)

Well, there is the use case of native context with XEN/KVM. In that 
situation QEMU opens tons of driver file descriptors on behalves of the 
virtual environment clients.

In this use case you have many drm_gpuvm instances for a single 
application. So you can't assume that you only have one VM per PID/TGID 
or something like that.

AMD already made that mistake with KFD and I strongly suggest not to 
repeat it :)

Regards,
Christian.

^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
  2023-11-13  7:22                                   ` [Nouveau] " Christian König
  (?)
@ 2023-11-13 12:57                                     ` Danilo Krummrich
  -1 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-13 12:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson, daniel

On 11/13/23 08:22, Christian König wrote:
> Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
>> On 11/10/23 09:50, Christian König wrote:
>>> [SNIP]
>>>>>
>>>>>>
>>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>>
>>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>>
>>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)
> 
> Well, there is the use case of native context with XEN/KVM. In that situation QEMU opens tons of driver file descriptors on behalves of the virtual environment clients.
> 
> In this use case you have many drm_gpuvm instances for a single application. So you can't assume that you only have one VM per PID/TGID or something like that.

Well, that's fine. I think Xe can have multiple VMs per PID as well. In this case you'd keep creating driver VM structures with a single GPUVM as base class. But not multiple GPUVMs serving as base class for a single driver structure, which I thought was the concern here. For the latter I can't see a use case.

> 
> AMD already made that mistake with KFD and I strongly suggest not to repeat it :)
> 
> Regards,
> Christian.
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-13 12:57                                     ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-13 12:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: matthew.brost, sarah.walker, nouveau, linux-kernel, dri-devel,
	faith, boris.brezillon, donald.robson

On 11/13/23 08:22, Christian König wrote:
> Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
>> On 11/10/23 09:50, Christian König wrote:
>>> [SNIP]
>>>>>
>>>>>>
>>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>>
>>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>>
>>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)
> 
> Well, there is the use case of native context with XEN/KVM. In that situation QEMU opens tons of driver file descriptors on behalves of the virtual environment clients.
> 
> In this use case you have many drm_gpuvm instances for a single application. So you can't assume that you only have one VM per PID/TGID or something like that.

Well, that's fine. I think Xe can have multiple VMs per PID as well. In this case you'd keep creating driver VM structures with a single GPUVM as base class. But not multiple GPUVMs serving as base class for a single driver structure, which I thought was the concern here. For the latter I can't see a use case.

> 
> AMD already made that mistake with KFD and I strongly suggest not to repeat it :)
> 
> Regards,
> Christian.
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

* Re: [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures
@ 2023-11-13 12:57                                     ` Danilo Krummrich
  0 siblings, 0 replies; 141+ messages in thread
From: Danilo Krummrich @ 2023-11-13 12:57 UTC (permalink / raw)
  To: Christian König, Thomas Hellström
  Cc: airlied, daniel, matthew.brost, sarah.walker, donald.robson,
	boris.brezillon, faith, dri-devel, nouveau, linux-kernel

On 11/13/23 08:22, Christian König wrote:
> Am 10.11.23 um 17:57 schrieb Danilo Krummrich:
>> On 11/10/23 09:50, Christian König wrote:
>>> [SNIP]
>>>>>
>>>>>>
>>>>>> Another issue Christian brought up is that something intended to be embeddable (a base class) shouldn't really have its own refcount. I think that's a valid point. If you at some point need to derive from multiple such structs each having its own refcount, things will start to get weird. One way to resolve that would be to have the driver's subclass provide get() and put() ops, and export a destructor for the base-class, rather than to have the base-class provide the refcount and a destructor  ops.
>>>>
>>>> GPUVM simply follows the same pattern we have with drm_gem_objects. And I think it makes
>>>> sense. Why would we want to embed two struct drm_gpuvm in a single driver structure?
>>>
>>> Because you need one drm_gpuvm structure for each application using the driver? Or am I missing something?
>>
>> Right, *one*, but not more than one. Wasn't that the concern? Maybe I misunderstood something. :)
> 
> Well, there is the use case of native context with XEN/KVM. In that situation QEMU opens tons of driver file descriptors on behalves of the virtual environment clients.
> 
> In this use case you have many drm_gpuvm instances for a single application. So you can't assume that you only have one VM per PID/TGID or something like that.

Well, that's fine. I think Xe can have multiple VMs per PID as well. In this case you'd keep creating driver VM structures with a single GPUVM as base class. But not multiple GPUVMs serving as base class for a single driver structure, which I thought was the concern here. For the latter I can't see a use case.

> 
> AMD already made that mistake with KFD and I strongly suggest not to repeat it :)
> 
> Regards,
> Christian.
> 


^ permalink raw reply	[flat|nested] 141+ messages in thread

end of thread, other threads:[~2023-11-13 12:58 UTC | newest]

Thread overview: 141+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-01 23:30 [Nouveau] [PATCH drm-misc-next v8 00/12] [RFC] DRM GPUVM features Danilo Krummrich
2023-11-01 23:30 ` Danilo Krummrich
2023-11-01 23:30 ` Danilo Krummrich
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 01/12] drm/gpuvm: convert WARN() to drm_WARN() variants Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 02/12] drm/gpuvm: don't always WARN in drm_gpuvm_check_overflow() Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-02 13:19   ` [Nouveau] " Thomas Hellström
2023-11-02 13:19     ` Thomas Hellström
2023-11-02 13:19     ` Thomas Hellström
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 03/12] drm/gpuvm: export drm_gpuvm_range_valid() Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-02 13:20   ` Thomas Hellström
2023-11-02 13:20     ` Thomas Hellström
2023-11-02 13:20     ` [Nouveau] " Thomas Hellström
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 04/12] drm/nouveau: make use of drm_gpuvm_range_valid() Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-02  4:46   ` Dave Airlie
2023-11-02  4:46     ` Dave Airlie
2023-11-02  4:46     ` [Nouveau] " Dave Airlie
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 05/12] drm/gpuvm: add common dma-resv per struct drm_gpuvm Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 06/12] drm/nouveau: make use of the GPUVM's shared dma-resv Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30 ` [Nouveau] [PATCH drm-misc-next v8 07/12] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:30   ` Danilo Krummrich
2023-11-01 23:31 ` [Nouveau] [PATCH drm-misc-next v8 08/12] drm/nouveau: separately allocate struct nouveau_uvmm Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-02  4:44   ` Dave Airlie
2023-11-02  4:44     ` Dave Airlie
2023-11-02  4:44     ` [Nouveau] " Dave Airlie
2023-11-01 23:31 ` [Nouveau] [PATCH drm-misc-next v8 09/12] drm/gpuvm: reference count drm_gpuvm structures Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-02 10:46   ` kernel test robot
2023-11-02 10:46     ` kernel test robot
2023-11-02 10:46     ` [Nouveau] " kernel test robot
2023-11-02 13:21   ` Thomas Hellström
2023-11-02 13:21     ` Thomas Hellström
2023-11-02 13:21     ` [Nouveau] " Thomas Hellström
2023-11-02 17:09   ` Thomas Hellström
2023-11-02 17:09     ` Thomas Hellström
2023-11-02 17:09     ` [Nouveau] " Thomas Hellström
2023-11-02 17:32     ` Danilo Krummrich
2023-11-02 17:32       ` Danilo Krummrich
2023-11-02 17:32       ` Danilo Krummrich
2023-11-02 18:04       ` [Nouveau] " Thomas Hellström
2023-11-02 18:04         ` Thomas Hellström
2023-11-02 18:04         ` Thomas Hellström
2023-11-03  7:18   ` Christian König
2023-11-03  7:18     ` Christian König
2023-11-03  7:18     ` [Nouveau] " Christian König
2023-11-03 13:14     ` Danilo Krummrich
2023-11-03 13:14       ` Danilo Krummrich
2023-11-03 13:14       ` Danilo Krummrich
2023-11-03 14:04       ` [Nouveau] " Christian König
2023-11-03 14:04         ` Christian König
2023-11-03 15:34         ` [Nouveau] " Danilo Krummrich
2023-11-03 15:34           ` Danilo Krummrich
2023-11-03 15:34           ` Danilo Krummrich
2023-11-06  9:14           ` Christian König
2023-11-06  9:14             ` Christian König
2023-11-06  9:14             ` [Nouveau] " Christian König
2023-11-06 12:16             ` Danilo Krummrich
2023-11-06 12:16               ` [Nouveau] " Danilo Krummrich
2023-11-06 12:16               ` Danilo Krummrich
2023-11-06 13:05               ` [Nouveau] " Christian König
2023-11-06 13:05                 ` Christian König
2023-11-06 14:11                 ` Danilo Krummrich
2023-11-06 14:11                   ` Danilo Krummrich
2023-11-06 14:11                   ` [Nouveau] " Danilo Krummrich
2023-11-06 15:10                   ` Christian König
2023-11-06 15:10                     ` Christian König
2023-11-06 16:42                     ` [Nouveau] " Danilo Krummrich
2023-11-06 16:42                       ` Danilo Krummrich
2023-11-06 16:42                       ` Danilo Krummrich
2023-11-09 15:50                       ` Thomas Hellström
2023-11-09 15:50                         ` [Nouveau] " Thomas Hellström
2023-11-09 15:50                         ` Thomas Hellström
2023-11-09 16:03                         ` Christian König
2023-11-09 16:03                           ` Christian König
2023-11-09 16:03                           ` [Nouveau] " Christian König
2023-11-09 18:34                           ` Danilo Krummrich
2023-11-09 18:34                             ` Danilo Krummrich
2023-11-09 18:34                             ` Danilo Krummrich
2023-11-10  8:50                             ` [Nouveau] " Christian König
2023-11-10  8:50                               ` Christian König
2023-11-10  8:50                               ` Christian König
2023-11-10  9:39                               ` [Nouveau] " Thomas Hellström
2023-11-10  9:39                                 ` Thomas Hellström
2023-11-10  9:39                                 ` Thomas Hellström
2023-11-10 10:42                                 ` [Nouveau] " Christian König
2023-11-10 10:42                                   ` Christian König
2023-11-10 10:42                                   ` Christian König
2023-11-10 10:52                                   ` [Nouveau] " Thomas Hellström
2023-11-10 10:52                                     ` Thomas Hellström
2023-11-10 10:52                                     ` Thomas Hellström
2023-11-10 16:49                                     ` [Nouveau] " Danilo Krummrich
2023-11-10 16:49                                       ` Danilo Krummrich
2023-11-10 16:49                                       ` Danilo Krummrich
2023-11-10 16:43                                 ` [Nouveau] " Danilo Krummrich
2023-11-10 16:43                                   ` Danilo Krummrich
2023-11-10 16:43                                   ` Danilo Krummrich
2023-11-10 16:57                               ` [Nouveau] " Danilo Krummrich
2023-11-10 16:57                                 ` Danilo Krummrich
2023-11-10 16:57                                 ` Danilo Krummrich
2023-11-13  7:22                                 ` Christian König
2023-11-13  7:22                                   ` Christian König
2023-11-13  7:22                                   ` [Nouveau] " Christian König
2023-11-13 12:57                                   ` Danilo Krummrich
2023-11-13 12:57                                     ` Danilo Krummrich
2023-11-13 12:57                                     ` Danilo Krummrich
2023-11-01 23:31 ` [Nouveau] [PATCH drm-misc-next v8 10/12] drm/gpuvm: add an abstraction for a VM / BO combination Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-02 13:25   ` Thomas Hellström
2023-11-02 13:25     ` Thomas Hellström
2023-11-02 13:25     ` [Nouveau] " Thomas Hellström
2023-11-02 17:16   ` Thomas Hellström
2023-11-02 17:16     ` Thomas Hellström
2023-11-02 17:16     ` [Nouveau] " Thomas Hellström
2023-11-01 23:31 ` [Nouveau] [PATCH drm-misc-next v8 11/12] drm/gpuvm: track/lock/validate external/evicted objects Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31 ` [Nouveau] [PATCH drm-misc-next v8 12/12] drm/nouveau: use GPUVM common infrastructure Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-01 23:31   ` Danilo Krummrich
2023-11-02  4:47   ` Dave Airlie
2023-11-02  4:47     ` Dave Airlie
2023-11-02  4:47     ` [Nouveau] " Dave Airlie
2023-11-02 13:18   ` Thomas Hellström
2023-11-02 13:18     ` Thomas Hellström
2023-11-02 13:18     ` [Nouveau] " Thomas Hellström

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.