Thanks Mikhail ! Merged.
Vitaly
On 2026-10-01 12:13, Mikhail Gavrilov wrote:
> Reading or writing a register through the amdgpu_regs2 debugfs file
> with GRBM bank selection, which umr does for every banked access (a
> single "umr -b x x x -r <reg>" is enough, as is the wave dump with
> "-O halt_waves"), makes lockdep report a circular locking dependency,
> after which lockdep stays off until reboot:
>
> WARNING: possible circular locking dependency detected
> umr/63306 is trying to acquire lock:
> ffff8882cf4b8350 (&mm->mmap_lock){++++}-{4:4}, at: __might_fault+0x9b/0x140
> but task is already holding lock:
> ffff8881aec010d0 (&amdgpu_grbm_lock_key){+.+.}-{4:4}, at:
> amdgpu_debugfs_regs2_op.isra.0+0x20d/0xd00 [amdgpu]
> ...
> Chain exists of:
> &mm->mmap_lock --> &amdgpu_srbm_lock_key --> &amdgpu_grbm_lock_key
> ...
> __might_fault+0xc5/0x140
> amdgpu_debugfs_regs2_op.isra.0+0x441/0xd00 [amdgpu]
> full_proxy_write+0x10e/0x1d0
> vfs_write+0x1d0/0xf50
> ksys_write+0x116/0x250
>
> amdgpu_debugfs_regs2_op() and amdgpu_debugfs_process_reg_op() call
> get_user()/put_user() for every dword while holding grbm_idx_mutex or
> srbm_mutex, and pm.mutex if requested, so a fault there takes mmap_lock
> inside those locks. The lock hierarchy in amdgpu_lockdep.c puts
> srbm_mutex and grbm_idx_mutex below notifier_lock, which
> amdgpu_cs_submit() takes with the BO reservations held, and
> dma_resv_lockdep() orders the reservations inside mmap_lock. So
> mmap_lock is outside grbm_idx_mutex and srbm_mutex, and these two
> debugfs paths nest it the other way round. The copies under the locks
> are as old as the bank selection in both files; commit 1d0f5838b126
> ("drm/amdgpu: Add lockdep annotations for lock ordering validation")
> only made them visible to lockdep.
>
> Copy the user buffer in before any of the locks is taken and the result
> out after all of them are dropped, as amdgpu_debugfs_wave_read(),
> amdgpu_debugfs_gpr_read() and amdgpu_debugfs_gprwave_read() already do.
> The buffer is sized by the count passed to read() or write(), so an
> access larger than PAGE_SIZE is now rejected with -EINVAL; umr reads and
> writes one register at a time. A fault on the user buffer of a write now
> fails with -EFAULT before any register is written, instead of after a
> partial write.
>
> Fixes: 566281595037 ("drm/amd/amdgpu: Add bank selection for MMIO debugfs
> (v3)")
> Fixes: 37df9560cd3e ("drm/amd/amdgpu: New debugfs interface for MMIO
> registers (v5)")
> Acked-by: Christian König <[email protected]>
> Acked-by: Tom St Denis <[email protected]>
> Reviewed-by: Vitaly Prosyak <[email protected]>
> Signed-off-by: Mikhail Gavrilov <[email protected]>
> ---
>
> Notes:
> v2:
> - Fixes: point at the commits that introduced the user copies under the
> locks, not at the lockdep annotation that made them visible
> (Christian).
> - Reject an access larger than PAGE_SIZE before allocating the buffer
> (Vitaly); umr only does 4-byte accesses (Tom).
> - Allocate the buffer with kmalloc() instead of kvmalloc() (Christian).
> - amdgpu_debugfs_gprwave_read() needs no change: it already calls
> put_user() after dropping grbm_idx_mutex.
> - Christian's and Tom's Acked-by were given on v1; the size check and
> kmalloc() are the only code changes since.
> - The test notes went out as a separate reply to v1; they are part of
> the patch mail now.
>
> v1:
> https://lore.kernel.org/amd-gfx/[email protected]/
>
> Tested v2 on Navi 31 (gfx1100), 7.3.0-rc5 at 551c722f4080, with lockdep.
>
> Without this patch the first GRBM-banked access through amdgpu_regs2
> produces the report above: from the write branch when umr halts
> waves, and from the read branch for a single
> "umr -b x x x -r '*.gfx1100.regGRBM_STATUS'" on a fresh boot.
>
> With this patch, and with the chain from reservation_ww_class_mutex
> through &amdgpu_notifier_lock_key down to &amdgpu_grbm_lock_key present
> in /proc/lockdep, none of the following produced a report, and
> debug_locks stayed 1:
> - the banked read of GRBM_STATUS* above;
> - "umr -O halt_waves -wa gfx_0.0.0" (banked SQ_CMD writes);
> - writing SCRATCH_REG0 and reading it back through amdgpu_regs2 with
> GRBM broadcast, and through amdgpu_regs with the bank bits in the
> offset; both reads returned the value written.
> A 4100-byte read of amdgpu_regs and of amdgpu_regs2 fails with EINVAL.
>
> drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 77 ++++++++++++---------
> 1 file changed, 46 insertions(+), 31 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> index aca1a8045a..ab7ccfaea2 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> @@ -121,6 +121,20 @@ static int amdgpu_debugfs_process_reg_op(bool read,
> struct file *f,
>
> *pos &= (1UL << 22) - 1;
>
> + if (size > PAGE_SIZE)
> + return -EINVAL;
> +
> + /*
> + * Access the user buffer only while none of the locks below are held:
> + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest
> + * inside it.
> + */
> + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> + if (!read && copy_from_user(data, buf, size))
> + return -EFAULT;
> +
> r = pm_runtime_get_sync(adev_to_drm(adev)->dev);
> if (r < 0) {
> pm_runtime_put_autosuspend(adev_to_drm(adev)->dev);
> @@ -152,28 +166,16 @@ static int amdgpu_debugfs_process_reg_op(bool read,
> struct file *f,
> mutex_lock(&adev->pm.mutex);
>
> while (size) {
> - uint32_t value;
> -
> - if (read) {
> - value = RREG32(*pos >> 2);
> - r = put_user(value, (uint32_t *)buf);
> - } else {
> - r = get_user(value, (uint32_t *)buf);
> - if (!r)
> - amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, value,
> 0);
> - }
> - if (r) {
> - result = r;
> - goto end;
> - }
> + if (read)
> + data[result >> 2] = RREG32(*pos >> 2);
> + else
> + amdgpu_mm_wreg_mmio_rlc(adev, *pos >> 2, data[result >>
> 2], 0);
>
> result += 4;
> - buf += 4;
> *pos += 4;
> size -= 4;
> }
>
> -end:
> if (use_bank) {
> amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff,
> 0xffffffff, 0);
> mutex_unlock(&adev->grbm_idx_mutex);
> @@ -188,6 +190,10 @@ static int amdgpu_debugfs_process_reg_op(bool read,
> struct file *f,
> pm_runtime_put_autosuspend(adev_to_drm(adev)->dev);
>
> amdgpu_virt_disable_access_debugfs(adev);
> +
> + if (read && copy_to_user(buf, data, result))
> + return -EFAULT;
> +
> return result;
> }
>
> @@ -238,11 +244,24 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f,
> char __user *buf, u32 off
> struct amdgpu_device *adev = rd->adev;
> ssize_t result = 0;
> int r;
> - uint32_t value;
>
> if (size & 0x3 || offset & 0x3)
> return -EINVAL;
>
> + if (size > PAGE_SIZE)
> + return -EINVAL;
> +
> + /*
> + * Access the user buffer only while none of the locks below are held:
> + * a fault takes mmap_lock, and grbm_idx_mutex and srbm_mutex nest
> + * inside it.
> + */
> + u32 *data __free(kfree) = kmalloc(size, GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> + if (write_en && copy_from_user(data, buf, size))
> + return -EFAULT;
> +
> r = pm_runtime_get_sync(adev_to_drm(adev)->dev);
> if (r < 0) {
> pm_runtime_put_autosuspend(adev_to_drm(adev)->dev);
> @@ -281,24 +300,16 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f,
> char __user *buf, u32 off
> mutex_lock(&adev->pm.mutex);
>
> while (size) {
> - if (!write_en) {
> - value = RREG32(offset >> 2);
> - r = put_user(value, (uint32_t *)buf);
> - } else {
> - r = get_user(value, (uint32_t *)buf);
> - if (!r)
> - amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2,
> value, rd->id.xcc_id);
> - }
> - if (r) {
> - result = r;
> - goto end;
> - }
> + if (!write_en)
> + data[result >> 2] = RREG32(offset >> 2);
> + else
> + amdgpu_mm_wreg_mmio_rlc(adev, offset >> 2, data[result
> >> 2],
> + rd->id.xcc_id);
> offset += 4;
> size -= 4;
> result += 4;
> - buf += 4;
> }
> -end:
> +
> if (rd->id.use_grbm) {
> amdgpu_gfx_select_se_sh(adev, 0xffffffff, 0xffffffff,
> 0xffffffff, rd->id.xcc_id);
> mutex_unlock(&adev->grbm_idx_mutex);
> @@ -317,6 +328,10 @@ static ssize_t amdgpu_debugfs_regs2_op(struct file *f,
> char __user *buf, u32 off
> pm_runtime_put_autosuspend(adev_to_drm(adev)->dev);
>
> amdgpu_virt_disable_access_debugfs(adev);
> +
> + if (!write_en && copy_to_user(buf, data, result))
> + return -EFAULT;
> +
> return result;
> }
>
>
> base-commit: 551c722f40809618230001baccf219193e22fc5a