⬢ DragonFlyBSD Kernel Audit
DF-4820 / harness_env.h
← back to finding ↓ download raw
/*
 * DF-4820 — mock environment for the verbatim radeon_ring_restore() body.
 *
 * Reproduces, in userspace, the exact kernel conditions that hold when
 * radeon_ring_restore() is called from radeon_gpu_reset()
 * (sys/dev/drm/radeon/radeon_device.c:1879-1887):
 *
 *   - radeon_ring_lock() -> radeon_ring_alloc() fails with an error.
 *     Two deterministic failure modes exist at that point, both proven in
 *     the surrounding kernel source:
 *
 *     (1) -EDEADLK: the GPU was just reset, so rdev->needs_reset is still
 *         true (it is only cleared at radeon_device.c:1921-1922, AFTER the
 *         restore loop).  radeon_fence_wait_seq_timeout()
 *         (radeon_fence.c:511-517) has `|| rdev->needs_reset` in its wait
 *         condition and converts it to -EDEADLK.
 *
 *     (2) -ENOENT: no fences pending (radeon_fence.c:649-654).
 *
 *     alloc() can only reach the fence wait when the aligned request equals
 *     ring_size/4 dwords — i.e. the ring was saturated (within align_mask
 *     dwords of full) when the GPU hung.  On the fresh post-reset ring
 *     (ring->wptr = 0, cik.c:4104) free_dw maxes at ring_size/4, so the
 *     request can never be satisfied -> error return is guaranteed.
 *
 *   - `data` points to a kvmalloc_array() allocation made by
 *     radeon_ring_backup() (radeon_ring.c:317).  The caller
 *     (radeon_device.c:1881-1882) ignores radeon_ring_restore()'s return
 *     value and never frees `data` on the success-of-reset branch.
 *
 * kvmalloc_array()/kvfree() are instrumented to track live allocations.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

/* ---- kernel types / return codes (minimal mock) ---------------------- */
/* values are cosmetic for the mock; only sign matters to the body */
#define ENOENT 2
#define EDEADLK 35
#define ENOMEM 12

struct radeon_device { int dummy; };
struct radeon_ring {
	unsigned ring_size;	/* bytes, power of 2 */
	unsigned align_mask;
	unsigned ptr_mask;
	unsigned ring_free_dw;
};

/* ---- allocation accounting (all test blocks are the same size) ------- */
static unsigned long live_allocs;
static unsigned long total_allocs;
static unsigned long block_bytes;

static void *kvmalloc_array(size_t n, size_t sz, unsigned gfp)
{
	void *p = malloc(n * sz);
	if (p) {
		live_allocs++;
		total_allocs++;
	}
	return p;
}

static void kvfree(void *p)
{
	if (p) {
		if (live_allocs == 0) {
			fprintf(stderr, "kvfree of untracked pointer %p!\n", p);
			exit(3);
		}
		live_allocs--;
		free(p);
	}
}

/* ---- mocked collaborators -------------------------------------------- */

/* error injected by radeon_ring_lock(); -EDEADLK (mode 1) or -ENOENT (2) */
static int lock_err = -EDEADLK;

static int radeon_ring_lock(struct radeon_device *rdev,
			    struct radeon_ring *ring, unsigned ndw)
{
	/*
	 * Model of radeon_ring_alloc() (radeon_ring.c:104-127) reduced to
	 * its decision on the fresh (just-resumed, wptr=0) ring:
	 *
	 *   ndw    = 16383 (saturated-ring backup size: full - 1 dw)
	 *   aligned = (16383 + 0xf) & ~0xf = 16384 == ring->ring_size/4
	 *   while (16384 > 16384 - 1):
	 *       radeon_ring_free_size -> free_dw = 16384 (maximum)
	 *       ndw < free_dw ? 16384 < 16384 ? no
	 *       radeon_fence_wait_next -> -EDEADLK / -ENOENT -> return r
	 */
	unsigned aligned = (ndw + ring->align_mask) & ~ring->align_mask;

	if (aligned == ring->ring_size / 4) {
		printf("  [alloc-model] ndw=%u aligned=%u == ring_size/4=%u:"
		       " unsatisfiable on fresh ring -> fence wait -> error %d\n",
		       ndw, aligned, ring->ring_size / 4, lock_err);
		return lock_err;
	}
	return 0;
}

static void radeon_ring_write(struct radeon_ring *ring, uint32_t v) { }

static void radeon_ring_unlock_commit(struct radeon_device *rdev,
				      struct radeon_ring *ring, bool hdp_flush)
{
	/* successful path: body then calls kvfree(data) itself */
}