On 9/30/26 17:49, 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 declared in amdgpu_lockdep.c since
> commit 1d0f5838b126
> ("drm/amdgpu: Add lockdep annotations for lock ordering validation") 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.
>
> 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.
> 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: 1d0f5838b126 ("drm/amdgpu: Add lockdep annotations for lock ordering
> validation")
Well that's not really correct since that patch only makes the problem visible
and not introduce it :)
But apart from that rather good catch, Vitalies work to add lockdep annotations
pays of.
> Signed-off-by: Mikhail Gavrilov <[email protected]>
I don't have time to review this in detail. Vitaly and Tom please take a look
at this.
But feel free to add Acked-by: Christian König <[email protected]>
Thanks in advance,
Christian.
> ---
> drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c | 71 ++++++++++++---------
> 1 file changed, 40 insertions(+), 31 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> index aca1a8045afa..5618bd3e2610 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> @@ -121,6 +121,17 @@ static int amdgpu_debugfs_process_reg_op(bool read,
> struct file *f,
>
> *pos &= (1UL << 22) - 1;
>
> + /*
> + * 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(kvfree) = kvmalloc(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 +163,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 +187,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 +241,21 @@ 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;
>
> + /*
> + * 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(kvfree) = kvmalloc(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 +294,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 +322,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;
> }
>