DF-4857 / sclkdiv0.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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | /* * DF-4857 — verbatim userland replica of rv730_populate_sclk_value() * (sys/dev/drm/radeon/rv730_dpm.c:40-117). * * The kernel function computes: * tmp = (u64)engine_clock * reference_divider * post_divider * 16384; * do_div(tmp, reference_clock); <-- rv730_dpm.c:71 * where reference_clock = rdev->clock.spll.reference_freq is copied verbatim * from the VBIOS FirmwareInfo table (radeon_atombios.c:1189-1193) with no * zero check. usReferenceClock==0 -> divide-by-zero -> kernel #DE panic. * * Register macros copied verbatim from sys/dev/drm/radeon/rv730d.h:26-43. * radeon_atom_get_clock_dividers() (firmware ATOM call) is out of replica * scope; dividers are caller-supplied. The div at :71 is unconditional and * precedes all SS logic. * * Modes: * ok realistic HD46xx values, usReferenceClock=2700 -> clean run * zero identical inputs but usReferenceClock=0 -> SIGFPE (kernel: #DE) * Build with -DFIX_DF4857 to compile in the proposed guard (returns -EINVAL). */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef uint32_t u32; typedef uint64_t u64; typedef uint8_t u8; typedef uint16_t u16; #define EINVAL 22 #define cpu_to_be32(x) __builtin_bswap32(x) /* ---- rv730d.h:26-43 (verbatim) ---- */ #define SPLL_DIVEN (1 << 2) #define SPLL_REF_DIV(x) ((x) << 4) #define SPLL_REF_DIV_MASK (0x3f << 4) #define SPLL_HILEN(x) ((x) << 12) #define SPLL_HILEN_MASK (0xf << 12) #define SPLL_LOLEN(x) ((x) << 16) #define SPLL_LOLEN_MASK (0xf << 16) #define SCLK_MUX_SEL(x) ((x) << 0) #define SCLK_MUX_SEL_MASK (0x1ff << 0) #define SPLL_FB_DIV(x) ((x) << 0) #define SPLL_FB_DIV_MASK (0x3ffffff << 0) #define SPLL_DITHEN (1 << 28) /* kernel-style do_div (both the % and the / trap on base==0, exactly like * the in-kernel do_div on x86-64) */ #define do_div(n, base) ({ \ u32 __r; \ __r = (u64)(n) % (u32)(base); \ (n) = (u64)(n) / (u32)(base); \ __r; \ }) struct atom_clock_dividers { u32 fb_div, ref_div, post_div; int enable_post_div, enable_dithen; }; struct radeon_atom_ss { u16 percentage, percentage_divider, step, rate, amount; u8 type, delay, range, refdiv; }; /* pi->clk_regs.rv730 boot snapshot (read by rv730_read_clock_registers) */ struct pi_stub { u32 cg_spll_func_cntl, cg_spll_func_cntl_2, cg_spll_func_cntl_3; u32 cg_spll_spread_spectrum, cg_spll_spread_spectrum_2; int sclk_ss; }; struct radeon_stub { u32 spll_reference_freq; struct pi_stub pi; }; struct smc_sclk { u32 sclk_value, vCG_SPLL_FUNC_CNTL, vCG_SPLL_FUNC_CNTL_2, vCG_SPLL_FUNC_CNTL_3, vCG_SPLL_SPREAD_SPECTRUM, vCG_SPLL_SPREAD_SPECTRUM_2; }; /* ==== replica of rv730_populate_sclk_value (rv730_dpm.c:40-117) ==== */ static int replica_sclk_value(struct radeon_stub *rdev, u32 engine_clock, struct smc_sclk *sclk, struct atom_clock_dividers *dividers, struct radeon_atom_ss *ss_tab) { struct pi_stub *pi = &rdev->pi; u32 spll_func_cntl = pi->cg_spll_func_cntl; u32 spll_func_cntl_2 = pi->cg_spll_func_cntl_2; u32 spll_func_cntl_3 = pi->cg_spll_func_cntl_3; u32 cg_spll_spread_spectrum = pi->cg_spll_spread_spectrum; u32 cg_spll_spread_spectrum_2 = pi->cg_spll_spread_spectrum_2; u64 tmp; u32 reference_clock = rdev->spll_reference_freq; /* :52 — VBIOS usReferenceClock */ u32 reference_divider, post_divider; u32 fbdiv; /* radeon_atom_get_clock_dividers() stubbed: caller-provided dividers */ reference_divider = 1 + dividers->ref_div; /* :62 */ if (dividers->enable_post_div) /* :64-68 */ post_divider = ((dividers->post_div >> 4) & 0xf) + (dividers->post_div & 0xf) + 2; else post_divider = 1; tmp = (u64) engine_clock * reference_divider * post_divider * 16384; /* :70 */ #ifdef FIX_DF4857 if (reference_clock == 0) { /* proposed fix */ printf("FIX_GUARD: reference_clock==0 rejected (rv=-EINVAL), no division\n"); return -EINVAL; } #endif do_div(tmp, reference_clock); /* :71 <-- DIV0 */ fbdiv = (u32) tmp; /* :72 */ if (dividers->enable_post_div) /* :75-82 */ spll_func_cntl |= SPLL_DIVEN; else spll_func_cntl &= ~SPLL_DIVEN; spll_func_cntl &= ~(SPLL_HILEN_MASK | SPLL_LOLEN_MASK | SPLL_REF_DIV_MASK); spll_func_cntl |= SPLL_REF_DIV(dividers->ref_div); spll_func_cntl |= SPLL_HILEN((dividers->post_div >> 4) & 0xf); spll_func_cntl |= SPLL_LOLEN(dividers->post_div & 0xf); spll_func_cntl_2 &= ~SCLK_MUX_SEL_MASK; /* :84-85 */ spll_func_cntl_2 |= SCLK_MUX_SEL(2); spll_func_cntl_3 &= ~SPLL_FB_DIV_MASK; /* :87-89 */ spll_func_cntl_3 |= SPLL_FB_DIV(fbdiv); spll_func_cntl_3 |= SPLL_DITHEN; if (pi->sclk_ss) { /* :91-107 (SS math is DF-1937) */ u32 vco_freq = engine_clock * post_divider; (void)vco_freq; if (ss_tab) { u32 clk_s = reference_clock * 5 / (reference_divider * ss_tab->rate); u32 clk_v = ss_tab->percentage * fbdiv / (clk_s * 10000); cg_spll_spread_spectrum &= ~((0xfff << 4)); cg_spll_spread_spectrum |= ((clk_s) << 4); cg_spll_spread_spectrum |= (1 << 0); cg_spll_spread_spectrum_2 &= ~(0x3ffffff << 0); cg_spll_spread_spectrum_2 |= ((clk_v) << 0); } } sclk->sclk_value = cpu_to_be32(engine_clock); /* :109-114 */ sclk->vCG_SPLL_FUNC_CNTL = cpu_to_be32(spll_func_cntl); sclk->vCG_SPLL_FUNC_CNTL_2 = cpu_to_be32(spll_func_cntl_2); sclk->vCG_SPLL_FUNC_CNTL_3 = cpu_to_be32(spll_func_cntl_3); sclk->vCG_SPLL_SPREAD_SPECTRUM = cpu_to_be32(cg_spll_spread_spectrum); sclk->vCG_SPLL_SPREAD_SPECTRUM_2 = cpu_to_be32(cg_spll_spread_spectrum_2); printf(" engine_clock=%u reference_clock=%u ref_div=%u post_div=0x%x -> fbdiv=%u (0x%x)%s\n", engine_clock, reference_clock, dividers->ref_div, dividers->post_div, fbdiv, fbdiv, fbdiv > 0x3ffffff ? " [EXCEEDS SPLL_FB_DIV_MASK]" : ""); printf(" vCG_SPLL_FUNC_CNTL_3=0x%08x\n", spll_func_cntl_3); return 0; } int main(int argc, char **argv) { struct radeon_stub rdev; struct atom_clock_dividers div; struct smc_sclk sclk; const char *mode = argc > 1 ? argv[1] : "ok"; u32 usReferenceClock; memset(&rdev, 0, sizeof(rdev)); memset(&div, 0, sizeof(div)); memset(&sclk, 0, sizeof(sclk)); /* realistic HD46xx snapshot */ rdev.pi.cg_spll_func_cntl = 0x00000115; /* boot regs */ rdev.pi.cg_spll_func_cntl_2 = 0x00000102; rdev.pi.cg_spll_func_cntl_3 = 0x00030010; rdev.pi.cg_spll_spread_spectrum = 0x000000a0; rdev.pi.cg_spll_spread_spectrum_2 = 0x00000abc; rdev.pi.sclk_ss = 0; /* SS off: div at :71 runs regardless */ div.ref_div = 1; div.post_div = 0x00; div.enable_post_div = 1; div.fb_div = 400; if (strcmp(mode, "ok") == 0) usReferenceClock = 2700; /* normal VBIOS: 27.00 MHz */ else if (strcmp(mode, "zero") == 0) usReferenceClock = 0; /* crafted/corrupt VBIOS */ else { fprintf(stderr, "usage: %s ok|zero\n", argv[0]); return 2; } /* radeon_atombios.c:1192-1193: spll->reference_freq = le16(fw.usReferenceClock); */ rdev.spll_reference_freq = usReferenceClock; printf("DF-4857 mode=%s: FirmwareInfo.usReferenceClock=%u -> spll.reference_freq=%u\n", mode, usReferenceClock, rdev.spll_reference_freq); replica_sclk_value(&rdev, 750000, &sclk, &div, NULL); printf("completed without trap\n"); return 0; } |