DF-3572 / wm_datarate_wrap.py
#!/usr/bin/env python3 """ DF-3572: arithmetic demonstration of the u32 wrap in skl_get_total_relative_data_rate() sys/dev/drm/i915/intel_pm.c:4252,4259 consuming skl_plane_relative_data_rate() intel_pm.c:4216-4220 (DragonFly i915, ~Linux v5.1/5.2 mix; Linux v5.2 computes these in u64) Reproduces, in exact integer arithmetic, the kernel computation on the drm_mode_atomic() check path (-> intel_atomic_check -> skl_compute_wm -> skl_compute_ddb -> skl_allocate_pipe_ddb) for: mode : 2731x2731@60 (crtc_clock ~605 MHz <= max dotclock = cdclk 675 MHz, passes skl_check_pipe_max_pixel_rate at MODESET time when no scaler is in use: ds=1) planes : primary 8192x8192 ARGB8888 (cpp=4) -> 2731x2731 (2.9996x/axis) sprite0 4096x8192 ARGB8888 -> 1366x2731 (3.0x/axis) sprite1 4096x8192 ARGB8888 -> 1366x2731 (3.0x/axis) (all overlapping the pipe -- legal; per-axis scaler cap is skl_max_scale = 3 - 2^-16, and sprite src_w <= 4096 on SKL) The per-plane pixel-rate gate is NOT re-run for plane-only atomic updates (skl_check_pipe_max_pixel_rate is called only from intel_crtc_compute_config, intel_display.c:10926, i.e. at modeset), and the watermark level check (skl_compute_plane_wm) legitimately passes because level-0 watermarks for these planes fit inside the (over-subscribed) per-plane ddb share. Run: python3 wm_datarate_wrap.py """ U32 = 0xFFFFFFFF def div_fixed16(val, d): # i915_drv.h:220 return (((val << 16) + d - 1) // d) & U32 def mul_fixed16(a, b): # i915_drv.h:210 return ((a * b) >> 16) & U32 # (u64 intermediate, clamp u32) def mul_round_up_u32_fixed16(val, mul16): # i915_drv.h:199 return (((val * mul16) + 0xFFFF) >> 16) & U32 def plane_rate(src_w, src_h, dst_w, dst_h, cpp): """skl_plane_relative_data_rate(), intel_pm.c:4179-4221""" ds_w = div_fixed16(src_w, dst_w) ds_h = div_fixed16(src_h, dst_h) ds = max(mul_fixed16(ds_w, ds_h), 1 << 16) # each axis >= 1 data_rate = (src_w * src_h * cpp) & U32 # intel_pm.c:4216 (u32) return ds, mul_round_up_u32_fixed16(data_rate, ds) # :4220 (u32 result) planes = [ ("primary", 8192, 8192, 2731, 2731, 4), ("sprite0", 4096, 8192, 1366, 2731, 4), ("sprite1", 4096, 8192, 1366, 2731, 4), ] rates = {} for name, sw, sh, dw, dh, cpp in planes: ds, r = plane_rate(sw, sh, dw, dh, cpp) ok = (sw / dw) < 3 and (sh / dh) < 3 rates[name] = r print(f"{name:8s} src {sw}x{sh} -> dst {dw}x{dh} ds={ds/65536:.4f} " f"({'<3/axis OK' if ok else 'VIOLATES 3x cap'}) " f"data_rate = {r} (0x{r:x}, fits u32: {r <= U32})") total_true = sum(rates.values()) total = total_true & U32 # intel_pm.c:4252,4259 <-- WRAP print(f"\ntrue 32-bit sum = {total_true}") print(f"kernel total_data_rate = {total} (0x{total:x})" f" WRAPPED by {total_true - total}") # ---- skl_allocate_pipe_ddb() intel_pm.c:4400-4466 ----------------------- ddb_size = 896 - 4 # SKL ddb_size - 4 bypass blocks minimum = 8 # non-Y-tiled min per plane cursor = 8 # skl_cursor_allocation(n>1) alloc_size = ddb_size - (3 * minimum + cursor) print(f"\nddb_size={ddb_size} alloc_size={alloc_size}") start = 0 for name in rates: blocks = minimum + (alloc_size * rates[name]) // total # div_u64(...) print(f"{name:8s} PLANE_BUF_CFG ddb entry [{start:5d},{start+blocks:5d}]" f" -> {start + blocks - ddb_size:+5d} blocks vs ddb_size {ddb_size}") start += blocks print(""" RESULT: the u32 wrap in total_data_rate shrinks the denominator ~9x, each plane's proportional share is multiplied accordingly, and the three DDB entries run far past the pipe's DDB end (hardware field is 10 bits [SKL_DDB_ENTRY_MASK=0x3FF, i915_reg.h:6667], so the programmed values alias to yet different wrong ranges). Nothing downstream bounds an entry against ddb_size (skl_ddb_allocation_overlaps, intel_display.c:12696, only orders cross-PIPE updates), so skl_ddb_entry_write() programs out-of-range PLANE_BUF_CFG start/end -> overlapping display buffer allocations -> display FIFO underrun / pipe wedge (modeset or GPU reset to recover). Local DoS by any DRM master. Linux computes these rates in u64 (v5.2 rework of skl_plane_relative_data_rate/skl_get_total_relative_data_rate); this DragonFly tree retains the pre-v5.2 u32 arithmetic. """) |