[PATCH AUTOSEL 6.19] drm/amd/display: Fix wrong x_pos and y_pos for cursor offload

Sasha Levin
2026-02-14T01:03:29Z

From: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>

[ Upstream commit c02288724b98cbc018231200891d66578f83f848 ]

[Why]
The hubp401_cursor_set_position function programs a different value
than it stores for use with cursor offload.

This can cause a desync when switching between cursor programming paths.

[How]
We do the translation to destination space currently twice: once in the
HWSS layer, and then again in the HUBP layer since we never store the
translated result.

HUBP expects to program the pos->x and pos->y directly for other ASIC,
so follow that pattern here as well.

Reviewed-by: Alvin Lee <alvin.lee2@amd.com>
Signed-off-by: Nicholas Kazlauskas <nicholas.kazlauskas@amd.com>
Signed-off-by: Roman Li <roman.li@amd.com>
Tested-by: Dan Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

The DCN10 HWSS passes `pos_cpy.x` and `pos_cpy.y` directly (which were
set to `(uint32_t)x_pos` at line 3744-3745), and the DCN10/DCN20 HUBP
uses `pos->x` directly for the register programming. This is the
consistent pattern across all ASICs.

DCN401 was broken because:
1. HWSS computes `x_pos = pos_cpy.x - param.recout.x` (line 1200 in the
current code)
2. But doesn't update `pos_cpy.x` with this new x_pos before calling
HUBP
3. HUBP then does its own `x_pos = pos->x - param->recout.x` —
subtracting recout.x from a value that already had recout.x
subtracted once (at the HWSS line 1200)... Wait, let me re-read.

Actually, looking more carefully at the HWSS code flow:

```
// Line 1194-1195: x_pos/y_pos are already in dst space here
pos_cpy.x = (uint32_t)x_pos; // stores DST-space value
pos_cpy.y = (uint32_t)y_pos;

// Line 1200-1201: Now computes recout-relative for visibility checks
x_pos = pos_cpy.x - param.recout.x; // recout-relative value
y_pos = pos_cpy.y - param.recout.y;

// ... visibility checks use recout-relative x_pos, y_pos ...

// BUG: pos_cpy.x still has DST-space value, NOT recout-relative
// HUBP receives dst-space pos_cpy.x/y
hubp->funcs->set_cursor_position(hubp, &pos_cpy, &param);
```

And in HUBP:
```
// HUBP receives pos->x in dst-space
int x_pos = pos->x - param->recout.x; // converts to recout-relative
// Programs register with recout-relative x_pos
REG_SET_2(CURSOR_POSITION, 0, CURSOR_X_POSITION, x_pos, ...);
// But stores dst-space pos->x in cache
hubp->pos.position.bits.x_pos = pos->x; // DST-space!
```

The **desync** is: register gets recout-relative, but `hubp->pos` cache
gets dst-space. When cursor offload reads the cache, it gets the wrong
(dst-space) values and programs them directly, causing cursor position
to jump.

The **fix**:
1. HWSS now updates `pos_cpy.x = x_pos` (recout-relative) before calling
HUBP
2. HUBP no longer does `pos->x - param->recout.x` — it uses `pos->x`
directly (already recout-relative)
3. Register and cache both get the same recout-relative value — NO
desync

---

Here is my complete analysis:

## 1. COMMIT MESSAGE ANALYSIS

The commit message clearly describes a **bug fix** with the standard AMD
display `[Why]` / `[How]` format. The bug is:
`hubp401_cursor_set_position` programs different values to hardware
registers than it stores in the cursor position cache, causing a
**desync when switching between cursor programming paths** (direct vs.
DMU offload). This is a user-visible display bug — the cursor position
jumps/glitches when the display driver switches between direct cursor
programming and DMU-offloaded cursor programming.

The commit has strong trust indicators:
- **Reviewed-by:** Alvin Lee (AMD DC team member)
- **Tested-by:** Dan Wheeler (AMD test engineer)
- **Author:** Nicholas Kazlauskas (the same person who wrote the cursor
offload feature)
- Multiple sign-offs from AMD display team

## 2. CODE CHANGE ANALYSIS

The fix is small and surgical: **2 files changed, 9 insertions, 8
deletions** (net +1 line).

**In `dcn401_hwseq.c`:** Adds 3 lines to update `pos_cpy.x = x_pos` and
`pos_cpy.y = y_pos` before passing them to the HUBP function. This
ensures the HUBP receives already-translated (recout-relative)
coordinates.

**In `dcn401_hubp.c`:** Removes the redundant coordinate translation
(`pos->x - param->recout.x`), using `pos->x` directly instead — matching
the pattern used by all other ASICs (DCN10, DCN20, DCN32).

The fix eliminates a double-translation bug and makes register
programming and cache storage consistent. This exactly matches how
DCN20's `hubp2_cursor_set_position` works (which programs `pos->x`
directly at line 1075).

## 3. CLASSIFICATION

This is a **clear bug fix**. It fixes incorrect cursor positioning that
causes visual glitches when switching between cursor programming paths.
It fixes a desync between hardware register values and cached software
values.

## 4. SCOPE AND RISK

- **Scope:** Very small — 2 files, 17 lines changed (9 added, 8
removed), only DCN401 (AMD RDNA4 GPU) affected
- **Risk:** Low — the fix aligns DCN401 with the well-established
pattern used by all other DCN generations
- **Subsystem:** AMD display driver (drm/amd/display), specifically
DCN401 HUBP/HWSS
- **Cannot break other ASICs:** Changes are only in dcn401-specific
files

## 5. USER IMPACT

- **Who is affected:** Users with AMD RDNA4 (DCN401) GPUs running kernel
6.19+ with cursor offload enabled
- **Severity:** Cursor position desync causing visual glitches when
switching between cursor programming paths. This is a user-visible
display bug
- **Impact:** Medium — affects cursor display correctness on specific
hardware

## 6. DEPENDENCY CHECK

- The cursor offload feature (8e8691ecee82) is only in v6.19+
- All prerequisite commits are already in the 6.19.y stable branch
- The fix applies cleanly to 6.19.y (files are identical to mainline
v6.19)
- DCN401 hardware doesn't exist in kernels older than 6.11, and cursor
offload doesn't exist before 6.19
- This fix is only applicable to 6.19.y stable

## 7. CONCERNS

- The commit is only in linux-next (not yet in a mainline release). It's
queued for v6.20 merge window. Normally, stable backports come from
commits already in mainline. This is a potential concern — the commit
hasn't been battle-tested in a mainline release yet.
- However, the fix is authored by the original cursor offload developer,
reviewed and tested by AMD team, and follows the well-established
pattern from other DCN generations.

## VERDICT

This is a legitimate bug fix for a desync between cursor register
programming and cache values in DCN401's cursor offload path. It's
small, surgical, well-tested, authored by the subsystem expert, and
follows established patterns from other ASICs. It fixes a user-visible
cursor positioning bug.

However, this commit is NOT yet in any mainline release — it's only in
linux-next, queued for the v6.20 merge window. The stable kernel rules
require commits to come from mainline. While the fix itself is
technically correct and appropriate for stable, it should wait until
it's actually in a mainline release before being backported.
Additionally, the cursor offload feature itself is very new (only in
6.19), meaning the affected user base is small and the code is still
rapidly evolving (5+ cursor offload fixes already queued).

Given that the commit is only in linux-next and not yet in mainline, and
the cursor offload is brand-new code still under active development,
this is borderline. The fix IS important for 6.19.y users with DCN401
hardware, but it should ideally land in mainline first.

**YES**

.../drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c | 14 ++++++--------
.../drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c | 3 +++
2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
index f01eae50d02f7..c205500290ecd 100644
--- a/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
+++ b/drivers/gpu/drm/amd/display/dc/hubp/dcn401/dcn401_hubp.c
@@ -733,10 +733,8 @@ void hubp401_cursor_set_position(
const struct dc_cursor_mi_param *param)
{
struct dcn20_hubp *hubp2 = TO_DCN20_HUBP(hubp);
- int x_pos = pos->x - param->recout.x;
- int y_pos = pos->y - param->recout.y;
- int rec_x_offset = x_pos - pos->x_hotspot;
- int rec_y_offset = y_pos - pos->y_hotspot;
+ int rec_x_offset = pos->x - pos->x_hotspot;
+ int rec_y_offset = pos->y - pos->y_hotspot;
int dst_x_offset;
int x_pos_viewport = 0;
int x_hot_viewport = 0;
@@ -748,10 +746,10 @@ void hubp401_cursor_set_position(
* within preceeding ODM slices.
*/
if (param->recout.width) {
- x_pos_viewport = x_pos * param->viewport.width / param->recout.width;
+ x_pos_viewport = pos->x * param->viewport.width / param->recout.width;
x_hot_viewport = pos->x_hotspot * param->viewport.width / param->recout.width;
} else {
- ASSERT(!cur_en || x_pos == 0);
+ ASSERT(!cur_en || pos->x == 0);
ASSERT(!cur_en || pos->x_hotspot == 0);
}

@@ -790,8 +788,8 @@ void hubp401_cursor_set_position(

if (!hubp->cursor_offload) {
REG_SET_2(CURSOR_POSITION, 0,
- CURSOR_X_POSITION, x_pos,
- CURSOR_Y_POSITION, y_pos);
+ CURSOR_X_POSITION, pos->x,
+ CURSOR_Y_POSITION, pos->y);

REG_SET_2(CURSOR_HOT_SPOT, 0,
CURSOR_HOT_SPOT_X, pos->x_hotspot,
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
index 5eda7648d0d2b..5ffe41a96864a 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn401/dcn401_hwseq.c
@@ -1215,6 +1215,9 @@ void dcn401_set_cursor_position(struct pipe_ctx *pipe_ctx)
if (recout_y_pos + (int)hubp->curs_attr.height <= 0)
pos_cpy.enable = false; /* not visible beyond top edge*/

+ pos_cpy.x = x_pos;
+ pos_cpy.y = y_pos;
+
hubp->funcs->set_cursor_position(hubp, &pos_cpy, &param);
dpp->funcs->set_cursor_position(dpp, &pos_cpy, &param, hubp->curs_attr.width, hubp->curs_attr.height);
}
--
2.51.0