DF-3855 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | /* * DF-3855 harness - external-TMDS init parser desynchronisation: * non-IGP "case 0" record forgets "index += 4". * * Kernel path (replicated 1:1): * sys/dev/drm/radeon/radeon_legacy_encoders.c:985 * if (!radeon_combios_external_tmds_setup(encoder)) ... * sys/dev/drm/radeon/radeon_combios.c:2948-2998, non-IGP branch: * index = offset + 10; * id = RBIOS16(index); * while (id != 0xffff) { * index += 2; [skip id word] * switch (id >> 13) { * case 0: reg = (id & 0x1fff) * 4; * val = RBIOS32(index); * WREG32(reg, val); [MISSING index += 4] * break; * case 2: ... index += 4; ... index += 4; ... * * The IGP branch of the same function (radeon_combios.c:2900-2905) DOES * advance "index += 4" after a case-0 value - the non-IGP branch is * missing the advance, so after the first plain register write the parser * re-reads the low half of the just-written VALUE as the next command id. * A well-formed terminator (0xffff) placed after the value is consumed as * operand bytes instead; the stream desynchronises and subsequent words * (including data words and whatever follows the table) are executed as * commands - MMIO writes / PLL RMWs driven by data. * * Upstream parity: verified identical (missing advance) in Linux v5.10 * drivers/gpu/drm/radeon/radeon_combios.c:2965-2969 - inherited, not a * DragonFly port regression. */ #include <stdio.h> #include <stdint.h> #include <string.h> static uint8_t bios[256]; #define RBIOS8(i) (bios[i]) #define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i) + 1) << 8)) #define RBIOS32(i) (RBIOS16(i) | (RBIOS16((i) + 2) << 16)) static int writes; static void WREG32(uint32_t reg, uint32_t v) { writes++; printf(" WREG32(0x%04x, 0x%08x)\n", reg, v); } /* radeon_combios.c:2948 non-IGP walk, bug included (case 0: no advance) */ static void ext_tmds_setup_nonigp_buggy(uint16_t offset) { uint32_t index = offset + 10; uint32_t id = RBIOS16(index); printf(" [non-IGP, buggy] walking from 0x%04x:\n", (unsigned)index); while (id != 0xffff) { uint32_t reg, val, and_mask, or_mask; index += 2; switch (id >> 13) { case 0: reg = (id & 0x1fff) * 4; val = RBIOS32(index); printf(" case 0 (direct write): consumes 4-byte value but DOES NOT advance index\n"); WREG32(reg, val); break; /* <-- missing index += 4 */ case 2: reg = (id & 0x1fff) * 4; and_mask = RBIOS32(index); index += 4; or_mask = RBIOS32(index); index += 4; printf(" case 2 (RMW): reg 0x%04x and 0x%08x or 0x%08x\n", reg, and_mask, or_mask); WREG32(reg, (0 & and_mask) | or_mask); break; case 4: val = RBIOS16(index); index += 2; printf(" case 4 (udelay %u)\n", val); break; default: printf(" unknown id class %u\n", id >> 13); break; } id = RBIOS16(index); /* re-reads value bytes */ } printf(" terminated at index 0x%04x\n", (unsigned)index); } /* same walk with the fix (advance after case-0 value) */ static void ext_tmds_setup_fixed(uint16_t offset) { uint32_t index = offset + 10; uint32_t id = RBIOS16(index); printf(" [with index += 4 fix]:\n"); while (id != 0xffff) { uint32_t reg, val; index += 2; switch (id >> 13) { case 0: reg = (id & 0x1fff) * 4; val = RBIOS32(index); index += 4; /* the fix */ WREG32(reg, val); break; default: printf(" unexpected class in fixed walk\n"); return; } id = RBIOS16(index); } printf(" terminated at index 0x%04x (at the 0xffff terminator)\n", (unsigned)index); } int main(void) { uint16_t offset = 0x20; /* well-formed two-record stream + terminator: * rec1: case 0 write reg 0x0040 <- 0x55555555 * term: 0xffff */ memset(bios, 0, sizeof(bios)); bios[offset + 10] = 0x01; bios[offset + 11] = 0x00; /* id: class0, reg 1 */ bios[offset + 12] = 0x55; bios[offset + 13] = 0x55; /* value lo = 0x5555 */ bios[offset + 14] = 0x55; bios[offset + 15] = 0x55; /* value hi */ bios[offset + 16] = 0xff; bios[offset + 17] = 0xff; /* terminator */ /* following bytes (0) get executed as commands by the buggy walk: */ bios[offset + 18] = 0x01; bios[offset + 19] = 0x00; bios[offset + 20] = 0xaa; bios[offset + 21] = 0xaa; bios[offset + 22] = 0xaa; bios[offset + 23] = 0xaa; bios[offset + 24] = 0xff; bios[offset + 25] = 0xff; /* real terminator 2 */ printf("== DF-3855: well-formed ext-TMDS stream, terminator at +16 ==\n"); writes = 0; ext_tmds_setup_nonigp_buggy(offset); printf(" buggy walk executed %d MMIO write(s) and ran PAST the " "terminator (value 0x5555 re-read as id -> extra case-0 write of " "0xaaaaaa data)\n", writes); writes = 0; ext_tmds_setup_fixed(offset); printf(" fixed walk executed %d MMIO write(s) and stopped at the " "terminator\n", writes); return 0; } |