/*
 * DF-4007 model harness — i915_gem_execbuffer.c eb_move_to_gpu() error path
 * double-unreserve via eb_release_vmas().
 *
 * Replicates, with mock refcounts, the exact control flow of:
 *   eb_move_to_gpu()  second loop   (sys/dev/drm/i915/i915_gem_execbuffer.c:1860-1876)
 *   eb_release_vmas()               (sys/dev/drm/i915/i915_gem_execbuffer.c:857-879)
 *   __eb_unreserve_vma()            (:427-435 -> __i915_vma_unpin = vma->flags--; i915_vma.h:323-326)
 *   i915_vma_put()                  (i915_vma.h:222 -> i915_gem_object_put)
 *
 * Trigger: i915_vma_move_to_active() (:1864) returns -ENOMEM mid-loop when
 * active_instance()'s rbtree kmalloc fails (i915_vma.c:981-989) — reachable
 * when the vma is active from a second context (last_active cache miss) under
 * DRM-slab exhaustion.  For every entry already processed by the loop,
 * eb->flags[i] still has __EXEC_OBJECT_HAS_PIN/HAS_REF set (they are never
 * cleared — the unreserve took `flags` BY VALUE), so the ioctl error path
 * (i915_gem_do_execbuffer :2442-2444 -> eb_release_vmas) unreserves and
 * i915_vma_put()s those vmas A SECOND TIME.
 */
#include <stdio.h>
#include <stdint.h>

#define __EXEC_OBJECT_HAS_REF   (1u<<31)
#define __EXEC_OBJECT_HAS_PIN   (1u<<30)
#define __EXEC_OBJECT_HAS_FENCE (1u<<29)
#define __EXEC_OBJECT_RESERVED  (__EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_FENCE)

struct mock_vma {
    const char *name;
    int pin_count;      /* vma->flags pin bits           */
    int fence_pin;      /* vma->fence->pin_count         */
    int obj_refcount;   /* drm_i915_gem_object refcount  */
    int obj_floor;      /* refs that must remain after correct unwind */
    unsigned int *exec_flags; /* vma->exec_flags -> &eb.flags[i] */
};

static void __eb_unreserve_vma(struct mock_vma *vma, unsigned int flags)
{
    if (flags & __EXEC_OBJECT_HAS_FENCE)
        vma->fence_pin--;       /* __i915_vma_unpin_fence: vma->fence->pin_count-- */
    vma->pin_count--;           /* __i915_vma_unpin: vma->flags-- */
}

static void eb_unreserve_vma_fixed(struct mock_vma *vma, unsigned int *flags)
{
    if (!(*flags & __EXEC_OBJECT_HAS_PIN))
        return;
    __eb_unreserve_vma(vma, *flags);
    *flags &= ~__EXEC_OBJECT_RESERVED;   /* the missing step in the kernel code */
}

static void i915_vma_put(struct mock_vma *vma) { vma->obj_refcount--; }

/* eb_move_to_gpu second loop; fail_at = index where move_to_active returns -ENOMEM (<0 = never) */
static int eb_move_to_gpu(struct mock_vma **vma, unsigned int *flags, int count,
                          int fail_at, int fixed)
{
    for (int i = 0; i < count; i++) {
        if (i == fail_at)
            return -12;         /* -ENOMEM from active_instance() */

        if (fixed) {
            /* fix.diff: by-ref unreserve clears PIN|FENCE; HAS_REF cleared after the put */
            eb_unreserve_vma_fixed(vma[i], &flags[i]);
            vma[i]->exec_flags = NULL;
            if (flags[i] & __EXEC_OBJECT_HAS_REF) {
                i915_vma_put(vma[i]);
                flags[i] &= ~__EXEC_OBJECT_HAS_REF;
            }
        } else {
            /* kernel as shipped: flags taken BY VALUE, eb->flags[i] never cleared */
            __eb_unreserve_vma(vma[i], flags[i]);
            vma[i]->exec_flags = NULL;
            if (flags[i] & __EXEC_OBJECT_HAS_REF)
                i915_vma_put(vma[i]);   /* shadow batch only */
        }
    }
    return 0;
}

/* eb_release_vmas — verbatim semantics of i915_gem_execbuffer.c:857-879 */
static void eb_release_vmas(struct mock_vma **vma, unsigned int *flags, int count)
{
    for (int i = 0; i < count; i++) {
        if (!vma[i]) break;
        vma[i]->exec_flags = NULL;

        if (flags[i] & __EXEC_OBJECT_HAS_PIN)
            __eb_unreserve_vma(vma[i], flags[i]);

        if (flags[i] & __EXEC_OBJECT_HAS_REF)
            i915_vma_put(vma[i]);
    }
}

static void run(const char *tag, int fail_at, int fixed)
{
    /* a/c: 1 pin, 1 fence pin, 2 obj refs (create+ctx) that must survive.
       b (cmdparser shadow, eb_parse :1963): 1 pin, no fence, 3 obj refs
         (pool + i915_vma_get); execbuf must return exactly its 1 -> floor 2 */
    struct mock_vma a = {"vma[0] objA", 1, 1, 2, 2, NULL};
    struct mock_vma b = {"vma[1] shadow", 1, 0, 3, 2, NULL};
    struct mock_vma c = {"vma[2] objC", 1, 1, 2, 2, NULL};
    struct mock_vma *vmas[3] = { &a, &b, &c };
    unsigned int flags[3];
    int count = 3;

    for (int i = 0; i < count; i++)
        flags[i] = __EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_FENCE;
    flags[1] = __EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_REF; /* eb_parse :1964-1965: shadow has NO fence */
    for (int i = 0; i < count; i++) vmas[i]->exec_flags = &flags[i];

    int err = eb_move_to_gpu(vmas, flags, count, fail_at, fixed);
    if (err) eb_release_vmas(vmas, flags, count);         /* err_vma: :2442-2444 */

    printf("[%s] move_to_active fails at index %d%s\n", tag, fail_at, fixed ? " (PATCHED)" : " (BASELINE)");
    for (int i = 0; i < count; i++) {
        const char *bad = "";
        if (vmas[i]->pin_count < 0)  bad = "  <-- PIN UNDERFLOW (vma->flags borrow: vma unbinds while execbuf believes it pinned)";
        if (vmas[i]->obj_refcount < vmas[i]->obj_floor) bad = "  <-- OBJ REF UNDERFLOW (premature GEM object free -> UAF)";
        if (vmas[i]->fence_pin < 0) bad = "  <-- FENCE PIN UNDERFLOW (fence freed while vma still fenced)";
        printf("    %-12s pin=%d fence=%d objrefs=%d%s\n",
               vmas[i]->name, vmas[i]->pin_count, vmas[i]->fence_pin,
               vmas[i]->obj_refcount, bad);
    }
    printf("\n");
}

int main(void)
{
    run("DF-4007 BASELINE", 1, 0);   /* bug as shipped: entry 1 ENOMEM, entry 0 double-released (pin+fence) */
    run("DF-4007 BASELINE", 2, 0);   /* bug as shipped: entry 2 ENOMEM, shadow batch double-put (obj refcount) */
    run("DF-4007 control", -1, 0);   /* no failure: perfectly balanced */
    run("DF-4007 PATCHED ", 2, 1);   /* fix.diff applied: balanced even on failure */
    return 0;
}
