#!/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.
""")
