⬢ DragonFlyBSD Kernel Audit
DF-4857 / sclkdiv0.c
← back to finding ↓ download raw
/*
 * 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;
}