/* DF-3608 trigger — ncr(4) programs 32-bit-only DMA descriptors from
 * unrestricted 64-bit vtophys() results.
 *
 * Target: sys/dev/disk/ncr/ncr.c. Every address the driver hands the
 * 53C8xx script processor lives in a 32-bit field:
 *   - struct link { ncrcmd l_cmd, l_paddr; }        (ncrreg.h, ncrcmd=u32)
 *   - struct scr_tblmove { u_int32_t size, addr; }  (ncrreg.h:306)
 *   - np->squeue[MAX_START] is u_int32_t            (ncr.c:1081)
 * but the sources are full-width, unconstrained vtophys() results:
 *   - ncr.c:6577  cp->p_nccb          = vtophys(cp)   (kmalloc'd nccb)
 *   - ncr.c:4149  np->squeue[..]      = CCB_PHYS(cp, phys)  -> u32 store
 *   - ncr.c:4094  cp->phys.cmd.addr   = vtophys(cdb_bytes)
 *   - ncr.c:4110  cp->phys.sense.addr = vtophys(&csio->sense_data)
 *   - ncr.c:6701  phys->data[i].addr  = segaddr (vtophys of buffer)
 * Nothing below 4 GB is guaranteed except the script images
 * (kmem_alloc_contig(.., 0, 0xffffffff), ncr.c:3588-3605).  On amd64
 * with RAM above 4 GB the low 32 bits are programmed silently and the
 * chip DMAs to the WRONG physical address.
 *
 * Test: raw write+read+FNV-1a compare over an ncr-attached da(4) disk.
 *  A: guest RAM <= 4GB  -> checksums match (baseline)
 *  B: guest RAM >  4GB  -> mismatch / wedge / panic
 *
 * Build (root, in guest):  cc -O2 -o ncr_dma32 ncr_dma32.c
 * Run   (root, in guest):  ./ncr_dma32 /dev/daN 8
 */
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define CHUNK (64 * 1024)

static u_int64_t fnv;

static void
fnv_init(void) { fnv = 0xcbf29ce484222325ull; }

static void
fnv_update(const unsigned char *d, size_t n)
{
	for (size_t i = 0; i < n; i++) {
		fnv ^= d[i];
		fnv *= 0x100000001b3ull;
	}
}

int
main(int argc, char **argv)
{
	unsigned char *buf, *rb;
	u_int64_t w_sum, r_sum;
	off_t off, size;
	int fd;

	if (argc < 3) {
		fprintf(stderr, "usage: %s /dev/daN <size_mb>\n", argv[0]);
		return (2);
	}
	size = (off_t)atoi(argv[2]) * 1024 * 1024;
	if (size < CHUNK) size = CHUNK;

	buf = malloc(CHUNK);
	rb  = malloc(CHUNK);
	if (!buf || !rb) { perror("malloc"); return (1); }

	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror(argv[1]); return (1); }

	srandom(0xdfdf);
	fnv_init();
	for (off = 0; off < size; off += CHUNK) {
		u_int32_t *p = (u_int32_t *)buf;
		for (size_t i = 0; i < CHUNK / sizeof(*p); i++)
			p[i] = (u_int32_t)random();
		if (pwrite(fd, buf, CHUNK, off) != CHUNK) {
			perror("pwrite"); close(fd); return (1);
		}
		fnv_update(buf, CHUNK);
	}
	w_sum = fnv;

	fnv_init();
	for (off = 0; off < size; off += CHUNK) {
		if (pread(fd, rb, CHUNK, off) != CHUNK) {
			perror("pread"); close(fd); return (1);
		}
		fnv_update(rb, CHUNK);
	}
	r_sum = fnv;
	close(fd);

	printf("write fnv1a64 = %016llx\nread  fnv1a64 = %016llx\n%s\n",
	    (unsigned long long)w_sum, (unsigned long long)r_sum,
	    w_sum == r_sum ? "match" : "MISMATCH — data corrupted in flight");
	return (w_sum == r_sum ? 0 : 1);
}
