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
--
2.43.0