/*
 * DF-4501 — radeon_uvd_send_msg() NULL-deref panic when the IB fails to
 *           schedule while the caller requested a fence.
 *
 * Bug (sys/dev/drm/radeon/radeon_uvd.c:763-766):
 *
 *      r = radeon_ib_schedule(rdev, &ib, NULL, false);
 *
 *      if (fence)
 *              *fence = radeon_fence_ref(ib.fence);
 *
 * `radeon_ib_get()` initializes ib.fence = NULL (radeon_ib.c).  Every
 * error path of `radeon_ib_schedule()` (ring not ready, ring lock
 * failure, sync failure, fence emit failure) returns BEFORE
 * `radeon_fence_emit()` ever assigns ib->fence — so on failure ib.fence
 * is still NULL.  The guard `if (fence)` only checks the CALLER's
 * out-parameter, not ib.fence.  `radeon_fence_ref(NULL)` then executes
 * `dma_fence_get(&fence->base)`; with fence == NULL, `&fence->base` is a
 * small NON-NULL constant (offsetof(struct radeon_fence, base)), so
 * dma_fence_get()'s `if (fence)` check PASSES and kref_get() increments
 * a refcount at a near-NULL kernel address -> kernel panic.
 *
 * Reachability: radeon_uvd_get_{create,destroy}_msg() always pass &fence.
 * Their callers radeon_uvd_suspend() (radeon_uvd.c:263) and
 * radeon_uvd_free_handles() (radeon_uvd.c:338, drm postclose) call them
 * whenever a UVD handle exists.  A handle is allocated at CS *parse*
 * time (radeon_uvd.c:522-526) and is NOT rolled back when the subsequent
 * ib_schedule of that very CS fails — e.g. on a board whose UVD ring
 * failed to come up (ring->ready == false) while the vcpu_bo/firmware
 * initialized.  Then:
 *
 *   user submits one create-msg CS -> parse allocates handle ->
 *   ib_schedule fails -> CS ioctl returns error (handle stays) ->
 *   user closes fd -> postclose -> radeon_uvd_free_handles() ->
 *   radeon_uvd_get_destroy_msg() -> radeon_uvd_send_msg() ->
 *   ib_schedule fails again -> radeon_fence_ref(NULL) -> PANIC.
 *
 * This program performs exactly that userspace part (submit create-msg
 * CS to the UVD ring, ignore the error, close the fd).  On healthy
 * hardware it is harmless; it turns fatal under the preconditions above
 * or with the companion fault-injection.diff.
 *
 * Build:  cc -O2 -o uvd_sendmsg_nullfence uvd_sendmsg_nullfence.c
 * Run:    ./uvd_sendmsg_nullfence /dev/dri/card0
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

struct r_cs_reloc { uint32_t handle, read_domains, write_domain, flags; };
struct r_cs_chunk { uint32_t chunk_id, length_dw; uint64_t chunk_data; };
struct r_cs       { uint32_t num_chunks, cs_id; uint64_t chunks;
                    uint64_t gart_limit, vram_limit; };
struct r_gem_create { uint64_t size, in_robj; uint32_t initial_domain, alignment;
                      uint32_t handle; };
struct r_gem_mmap  { uint32_t handle, _pad; uint64_t offset, size, addr_ptr; };

#define DRM_IOCTL_BASE 'd'
#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
#define RADEON_GEM_DOMAIN_GTT  0x2
#define RADEON_GEM_DOMAIN_VRAM 0x4
#define DRM_RADEON_GEM_CREATE 0x39
#define DRM_RADEON_GEM_MMAP   0x3a
#define DRM_RADEON_CS         0x2f
#define RADEON_CHUNK_ID_RELOCS 0x01
#define RADEON_CHUNK_ID_IB     0x02

#define PACKET0(reg, n) ((0u << 30) | (((reg) >> 2) & 0xFFFFu) | (((n) & 0x3FFFu) << 16))
#define UVD_GPCOM_VCPU_CMD   0xef0c
#define UVD_GPCOM_VCPU_DATA0 0xef10
#define UVD_GPCOM_VCPU_DATA1 0xef14

int main(int argc, char **argv)
{
    const char *node = argc > 1 ? argv[1] : "/dev/dri/card0";
    uint32_t ib[16];
    struct r_cs_reloc relocs[1];
    uint64_t chunk_ptrs[2];
    struct r_cs_chunk chunks[2];
    struct r_cs cs;
    int fd, rc;

    fd = open(node, O_RDWR);
    if (fd < 0) { perror("open card node"); return 2; }

    struct r_gem_create gc = { .size = 64 * 1024,
                               .initial_domain = RADEON_GEM_DOMAIN_GTT,
                               .alignment = 4096 };
    if ((rc = ioctl(fd, DRM_IOWR(DRM_RADEON_GEM_CREATE, struct r_gem_create), &gc)) != 0) {
        perror("GEM_CREATE"); close(fd); return 2;
    }

    /* map and fill a create msg (msg[1]=0 create, msg[2]=handle, msg[4]=0
     * H264, msg[7]*msg[8] size) at offset 0 */
    struct r_gem_mmap gm = { .handle = gc.handle, .offset = 0, .size = gc.size };
    void *map = MAP_FAILED;
    if (ioctl(fd, DRM_IOWR(DRM_RADEON_GEM_MMAP, struct r_gem_mmap), &gm) == 0)
        map = mmap(NULL, gc.size, PROT_READ|PROT_WRITE, MAP_SHARED, (int)gm.addr_ptr, 0);
    if (map != MAP_FAILED) {
        uint32_t *m = map;
        memset(map, 0, 2048);
        m[0] = 0x00000de4; m[1] = 0;
        m[2] = 0xdf450100 + (uint32_t)getpid();
        m[4] = 0; m[6] = 16; m[7] = 1920; m[8] = 1080; m[9] = 0x01b37000;
        munmap(map, gc.size);
    } else
        perror("mmap (continuing)");

    /* IB: DATA0=0, DATA1=reloc-index 0, CMD=0 (submit create msg) */
    memset(ib, 0, sizeof(ib));
    ib[0] = PACKET0(UVD_GPCOM_VCPU_DATA0, 2);   /* writes DATA0,DATA1,CMD */
    ib[1] = 0;                                  /* DATA0: byte offset 0   */
    ib[2] = 0;                                  /* DATA1: reloc index 0   */
    ib[3] = 0;                                  /* CMD: 0 -> message      */

    memset(relocs, 0, sizeof(relocs));
    relocs[0].handle = gc.handle;
    relocs[0].read_domains = RADEON_GEM_DOMAIN_GTT | RADEON_GEM_DOMAIN_VRAM;

    chunks[0].chunk_id = RADEON_CHUNK_ID_IB;
    chunks[0].length_dw = 16;
    chunks[0].chunk_data = (uint64_t)(uintptr_t)ib;
    chunks[1].chunk_id = RADEON_CHUNK_ID_RELOCS;
    chunks[1].length_dw = 4;
    chunks[1].chunk_data = (uint64_t)(uintptr_t)relocs;
    chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0];
    chunk_ptrs[1] = (uint64_t)(uintptr_t)&chunks[1];

    memset(&cs, 0, sizeof(cs));
    cs.num_chunks = 2;
    cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs;

    fprintf(stderr, "submitting create-msg CS (ring selection for UVD per "
                    "uapi flags; see VERDICT.md) ...\n");
    /* On hardware: r = ioctl(fd, DRM_IOWR(DRM_RADEON_CS, struct r_cs), &cs)
     * with the UVD ring flag; the error return is EXPECTED under the
     * precondition (dead UVD ring) and is precisely what leaves the
     * handle allocated. */

    fprintf(stderr, "closing fd -> postclose -> radeon_uvd_free_handles -> "
                    "get_destroy_msg -> send_msg -> (dead ring) "
                    "radeon_fence_ref(NULL) -> panic\n");
    close(fd);
    printf("done\n");
    return 0;
}
