⬢ DragonFlyBSD Kernel Audit
DF-4450 / psr2_reg_demo.c
← back to finding ↓ download raw
/*
 * DF-4450 demo: EDP_PSR2_CTL field spill in hsw_activate_psr2()
 * Mirrors the kernel math from sys/dev/drm/i915/intel_psr.c:410-443 and the
 * register field definitions from sys/dev/drm/i915/i915_reg.h:4206-4222.
 * Build: cc -O2 -o psr2_reg_demo psr2_reg_demo.c
 */
#include <stdio.h>
#include <stdint.h>

/* i915_reg.h:4206-4222 (verbatim values) */
#define EDP_PSR2_ENABLE            (1u << 31)
#define EDP_SU_TRACK_ENABLE        (1u << 30)
#define EDP_PSR2_TP2_TIME_500us    (0u << 8)
#define EDP_PSR2_TP2_TIME_100us    (1u << 8)
#define EDP_PSR2_TP2_TIME_2500us   (2u << 8)
#define EDP_PSR2_TP2_TIME_MASK     (3u << 8)
#define EDP_PSR2_FRAME_BEFORE_SU_SHIFT 4
#define EDP_PSR2_FRAME_BEFORE_SU_MASK  (0xfu << 4)
#define EDP_PSR2_FRAME_BEFORE_SU(a)    ((a) << 4)
#define EDP_PSR2_IDLE_FRAME_MASK   0xfu
#define EDP_PSR2_IDLE_FRAME_SHIFT  0
/* include/drm/drm_dp_helper.h:800 (verbatim) */
#define DP_MAX_RESYNC_FRAME_COUNT_MASK 0xfu

/* int max(a,b) as in the kernel */
static int kmax(int a, int b) { return a > b ? a : b; }

/*
 * Exact reproduction of hsw_activate_psr2() value construction
 * (intel_psr.c:418-441) for vbt.psr.idle_frames = 0 (common) and
 * vbt.psr.tp2_tp3_wakeup_time_us > 500 ("else" -> 2500us branch).
 * sink_sync_latency is the masked DPCD byte from
 * intel_dp_get_sink_sync_latency() (intel_psr.c:205-215).
 */
static uint32_t encode(unsigned sink_sync_latency)
{
	int idle_frames = kmax(6, 0 /* vbt.psr.idle_frames */);
	uint32_t val;

	idle_frames = kmax(idle_frames, sink_sync_latency + 1);   /* :420 */
	val = idle_frames << EDP_PSR2_IDLE_FRAME_SHIFT;           /* :421 */

	val |= EDP_PSR2_ENABLE | EDP_SU_TRACK_ENABLE;             /* :426 */
	val |= EDP_PSR2_FRAME_BEFORE_SU(sink_sync_latency + 1);   /* :430 */
	val |= EDP_PSR2_TP2_TIME_2500us;                          /* :440 */
	return val;
}

static const char *tp2_name(unsigned tp2)
{
	switch (tp2) {
	case 0: return "500us";
	case 1: return "100us";
	case 2: return "2500us";
	case 3: return "50us";
	}
	return "?";
}

static void decode(const char *tag, uint32_t val)
{
	unsigned idle = val & EDP_PSR2_IDLE_FRAME_MASK;
	unsigned fbsu = (val & EDP_PSR2_FRAME_BEFORE_SU_MASK)
			>> EDP_PSR2_FRAME_BEFORE_SU_SHIFT;
	unsigned tp2  = (val & EDP_PSR2_TP2_TIME_MASK) >> 8;

	printf("%-34s EDP_PSR2_CTL=0x%08x -> IDLE_FRAME=%u  FRAME_BEFORE_SU=%u  TP2_TIME=%s\n",
	       tag, val, idle, fbsu, tp2_name(tp2));
}

int main(void)
{
	printf("DF-4450: hsw_activate_psr2() encoding, sink_sync_latency from DPCD\n");
	printf("(DP_MAX_RESYNC_FRAME_COUNT_MASK=0x%x; HW fields are 4 bits wide)\n\n",
	       DP_MAX_RESYNC_FRAME_COUNT_MASK);

	decode("latency= 7 (typical)",  encode(7));
	decode("latency=11",             encode(11));
	decode("latency=14 (max legal)", encode(14));
	decode("latency=15 (SPILLS!)",   encode(15));

	printf("\nWith sink_sync_latency=15 the kernel computes idle_frames=16 and\n"
	       "FRAME_BEFORE_SU(16):\n"
	       "  * 16 << 0 spills bit 4 into FRAME_BEFORE_SU (idle reads back 0,\n"
	       "    below the HW minimum of 6 the code tries to enforce)\n"
	       "  * 16 << 4 = 0x100 spills bit 8 into TP2_TIME; OR'ed with the\n"
	       "    requested 2500us encoding (0x200) the field becomes 0x300 = 50us\n"
	       "  -> three independent PSR2 timing fields corrupted in one write\n");
	return 0;
}