dce110 id_to_offset DDC_DATA/I2C_PAD translation carries the shared DDC6 DATA mask 0x100, but DC_GPIO_I2CPAD_A defines only SCL=bit0 (0x1) and SDA=bit1 (0x2) β reserved-bit MMIO write and dead SDA bit on the I2C pad
Summary
Shared-mask shortcut ignores the pad register's different layout: DDC_DATA on the I2C pad selects nonexistent bit 8 while never touching real SDA (CLOCK correct by coincidence). Deterministic constant-arithmetic PoC on guest; the dcn10 twin was DF-4947. SDA-mask override fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4966 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| mask_check.c | β | 2.5 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 116 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 433 B | view raw | |
| env.txt | β | 192 B | view raw | |
| fix.diff | β | 473 B | view raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| README.md | β | 819 B | β raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-4966 β dce110 hw_translate id_to_offset: I2C-pad DDC_DATA uses the shared DDC6 DATA mask 0x100; the pad's SDA bit is 0x2
Reproduce
./build.sh # gcc -O2 -Wall -Wextra -o mask_check mask_check.c
./run.sh
Expected: id_to_offset(DDC_DATA, I2C_PAD) mask = 0x100 -> WRONG: selects bit
8; SDA is bit 1 while the DDC1 control and the DDC_CLOCK/I2C_PAD case come
out correct (shared mask valid for real DDC lines; CLK bit == SCL bit).
Files
mask_check.cβ evaluates the translation against the realDC_GPIO_I2CPAD_Afield constants (dce_11_0_sh_mask.h:4203-4206)build.sh,run.sh,build.log,run.log,env.txtfix.diffβ override the mask in the DDC_DATA/I2C_PAD case withDC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASKVERDICT.md,manifest.json,verdict.json
DF-4966 VERDICT
Status: reproduced (the wrong-bit translation is deterministic and was executed on the DF guest against the real DCE11 register field constants). No memory-safety consequence β functional MMIO misprogramming only.
The defect
id_to_offset in sys/dev/drm/amd/display/dc/gpio/dce110/hw_translate_dce110.c
assigns the shared DDC data mask to every GPIO_ID_DDC_DATA line:
- hw_translate_dce110.c:189-190:
info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK; - hw_translate_dce110.c:213-215:
case GPIO_DDC_LINE_I2C_PAD: info->offset = mmDC_GPIO_I2CPAD_A;β the offset changes but the mask stays 0x100.
On DCE11, the real DC_GPIO_I2CPAD_A (0x4899) field layout is
(dce_11_0_sh_mask.h:4203-4206):
DC_GPIO_I2CPAD_A__DC_GPIO_SCL_A_MASK = 0x1(bit 0)DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK = 0x2(bit 1)
unlike the DDCx_A/DDCVGA_A registers where CLK=bit0 / DATA=bit8 (0x100), which is why the shared mask is correct for DDC1..6 and VGA (verified controls: DDC1DATA=0x100, DDC6DATA=0x100, VGADATA=0x100).
So id_to_offset(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_I2C_PAD) returns
offset=0x4899 with mask=0x100 β a bit that does not exist in that register
(SDA is bit 1). The DDC_CLOCK twin is correct only by coincidence
(DDC CLK bit0 == pad SCL bit0).
Manifestation (run.log, guest)
id_to_offset(DDC_DATA, I2C_PAD) mask = 0x100 -> WRONG: selects bit 8id_to_offset(DDC_CLOCK, I2C_PAD) mask = 0x1 -> correct (by coincidence)- control
id_to_offset(DDC_DATA, DDC1) = 0x100 == DDC1DATA_A_MASK -> correct
Consequence / impact ceiling
The hw_gpio/hw_ddc code then does REG_UPDATE/REG_GET on 0x4899 with
mask 0x100: read-modify-write sets/reads an undefined bit while never
touching the real SDA bit (0x2). Effects: the I2C-pad GPIO DDC line cannot
work (reads always 0; writes hit a reserved bit) β a functional defect; on
hardware where bit 8 of that register aliases something else, unintended
MMIO state change. No kernel memory-safety impact; upstream-shared code
(AMD DAL dce110 hw_translate is verbatim from the amdgpu display-core).
Reached from dal_gpio_create_ddc when the VBIOS I2C record points the DDC
data line at the I2C pad (dc_link_ddc.c:272-276, offset/mask from
get_gpio_i2c_info), i.e. crafted-VBIOS or real boards that wire the pad that
way. Severity: Info (hardening / correctness; no demonstrated security impact).
Fix
fix.diff: override the mask in the DDC_DATA I2C_PAD case with the real SDA
bit (DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK). Validated at harness level β the
wrong-bit is structurally eliminated; all other lines keep the shared mask.
Guest
DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, 2026-07-02), gcc 8.3.
No AMD hardware (amdgpu modules: 0, vendor-0x1002 PCI devices: 0), so the
register constants were verified against the tree's generated headers and the
translation executed at unit level β the defect itself is pure constant
arithmetic, deterministic on any host.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff overrides the mask in the DDC_DATA/I2C_PAD case with DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK (0x2) -- the defect is pure constant selection, structurally eliminated by the one-line override; all other lines keep the shared mask (harness controls). No kernel-build validation possible on this guest (no AMD hardware); Info-severity functional fix.
fix.diff (2-line hunk, hw_translate_dce110.c DDC_DATA/I2C_PAD case); run.log controls demonstrate shared-mask correctness for the other seven lines.
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: 'id_to_offset(DDC_DATA, I2C_PAD) mask = 0x100 -> WRONG' vs real SCL=0x1/SDA=0x2 field layout; controls: DDC_CLOCK/I2C_PAD mask 0x1 == SCL (correct by coincidence), DDC_DATA/DDC1 mask 0x100 == DDC1DATA (shared mask valid for real DDC lines).
PoC changes
fresh minimal constant-arithmetic harness; fixed a nested-comment gcc error from the seed draft.
Verified recommended fix
Set info->mask = DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK in the GPIO_ID_DDC_DATA / GPIO_DDC_LINE_I2C_PAD case.
Verdict
id_to_offset in hw_translate_dce110.c assigns the shared DDC data mask DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK (0x100) to every GPIO_ID_DDC_DATA line (line 190) and merely swaps the offset to mmDC_GPIO_I2CPAD_A (0x4899) for GPIO_DDC_LINE_I2C_PAD (lines 213-215). The DCE11 register DC_GPIO_I2CPAD_A defines only SCL=bit0 (0x1) and SDA=bit1 (0x2) (dce_11_0_sh_mask.h:4203-4206), so the DDC_DATA(SDA) translation carries a mask for a bit that does not exist in the target register. Deterministic (constant arithmetic), executed on the DF guest: mask_check run.log shows 'id_to_offset(DDC_DATA, I2C_PAD) mask = 0x100 -> WRONG: selects bit 8; SDA is bit 1', with the DDC1 control and the DDC_CLOCK/I2C_PAD case (0x1 == SCL by coincidence) correct. Consequence: REG_UPDATE/REG_GET on 0x4899 read-modify-write an undefined bit and never touch the real SDA bit -- functional defect (I2C-pad DDC line dead / reserved-bit MMIO write), upstream-shared AMD DAL code, no kernel memory-safety impact. Info severity.
No comments yet.