/*
 * DF-4009 userspace replication — NVMe PRP arena over-walk arithmetic
 *
 * mpr_build_nvme_prp() (sys/dev/raid/mpr/mpr.c:2680-2864, entered from
 * mpr_user.c:970 with fully user-controlled DataSize/DataOutSize) converts a
 * byte length into a linear walk of 8-byte PRP entries through ONE
 * contiguous arena:
 *
 *   arena = PAGE_SIZE * pages_required * NVME_QDEPTH          (mpr.c:1595-98)
 *   pages_required = (maxio/PAGE_SIZE / 511) + 1              (mpr.c:1594)
 *   NVME_QDEPTH = MPR_REQ_FRAMES = 2048                       (mprvar.h:59,40)
 *
 * The walk (while(length) at mpr.c:2757) writes one 8-byte entry per 4096
 * bytes of length, plus one extra list-pointer entry every 511 entries
 * (mpr.c:2765-2783), and NEVER checks the arena end — it relies on
 * contiguity.  This program computes exactly how far past the arena the
 * walk runs for a given DataSize and starting chunk index, replicating the
 * loop's arithmetic verbatim.
 *
 * Trigger reachability (no FW cooperation needed): the branch at
 * mpr_user.c:945 only tests the Function byte from the user header
 * (MPI2_FUNCTION_NVME_ENCAPSULATED = 0x33); mpr_build_nvme_prp() runs
 * BEFORE mpr_wait_command(), so the over-walk happens even if the IOC
 * later rejects the command — the only requirement is the mpr driver
 * attached (PRP arena allocated) and ioctl access to /dev/mprN.
 */
#include <stdio.h>
#include <stdint.h>

#define	PAGE_SIZE_	4096UL
#define	PRP_ENTRY_SIZE	8UL
#define	PRPS_PER_PAGE	((PAGE_SIZE_ / PRP_ENTRY_SIZE) - 1)	/* 511 */
#define	NVME_QDEPTH	2048UL			/* MPR_REQ_FRAMES */

int
main(void)
{
	unsigned long maxio_cases[] = { 128*1024, 256*1024, 1024*1024 };
	unsigned long lengths[] = { 1UL<<20, 64UL<<20, 1UL<<30,
	    0xFFFFFFFFUL };
	unsigned long chunks[] = { 0, 512, 1024, 1536, 2000 };
	unsigned long maxio, arena, L, chunk;

	for (int m = 0; m < 3; m++) {
		maxio = maxio_cases[m];
		/* mpr.c:1593-1598, verbatim arithmetic */
		unsigned long prps_required = maxio / PAGE_SIZE_;
		unsigned long pages_required =
		    (prps_required / PRPS_PER_PAGE) + 1;
		unsigned long prp_buffer_size = PAGE_SIZE_ * pages_required;
		arena = prp_buffer_size * NVME_QDEPTH;
		printf("maxio=%luKB -> pages_required=%lu arena=%lu bytes "
		    "(%lu entries)\n", maxio/1024, pages_required, arena,
		    arena / PRP_ENTRY_SIZE);

		for (int l = 0; l < 4; l++) {
			L = lengths[l];
			/* verbatim loop accounting: one entry per page of
			 * length, plus a list pointer per full PRP page */
			unsigned long pages = (L + PAGE_SIZE_ - 1) / PAGE_SIZE_;
			unsigned long entries = pages + pages / PRPS_PER_PAGE;
			unsigned long walk_bytes = entries * PRP_ENTRY_SIZE;
			for (int c = 0; c < 5; c++) {
				chunk = chunks[c];
				unsigned long in_arena =
				    arena - chunk * prp_buffer_size;
				long oob = (long)walk_bytes - (long)in_arena;
				printf("  DataSize=%-10lu start_chunk=%-4lu "
				    "walk=%-9lu in_arena=%-9lu OOB=%ld%s\n",
				    L, chunk, walk_bytes, in_arena, oob,
				    oob > 0 ? "  <== OOB WRITE" : "");
			}
		}
		printf("\n");
	}
	printf("NOTE: kmalloc(DataSize) failure does NOT gate the walk: a\n"
	    "NULL cm->cm_data only changes the written values (paddr=0),\n"
	    "mpr_user.c:908 result is never checked.\n");
	return (0);
}
