/*
 * DF-5497 userspace model: the PRODUCTION fwcrom.c parser (compiled
 * unmodified for userland, exactly as fwcontrol(8) links it) is driven
 * on a 1024-byte Config ROM placed against a PROT_NONE guard page.
 *
 * A CSRTYPE_D entry with val=V makes crom_next() load 4 bytes at
 * rom + 24 + 4*V (fwcrom.c:122 + :130) BEFORE the CROM_END bound check
 * on the next operand of the && -- so:
 *   subtest A  V=300       -> read at rom+1224, 200 bytes into the guard
 *                             page (past the 1024-byte ROM).  CROM_END
 *                             == rom+1023 would have rejected this
 *                             directory had the check run first.
 *   subtest B  V=0xffffff  -> read at rom+24+0x3fffffc (+64 MB): the
 *                             full 24-bit reach, unmapped in userland.
 *   subtest C  V=100       -> read at rom+424, INSIDE the ROM (control:
 *                             walk descends normally, no fault).
 *
 * Each subtest runs in a fork()ed child; the SIGSEGV handler prints the
 * fault address; the child exits 42 iff it faulted exactly at the
 * predicted OOB address, 0 for a clean walk.
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

#include <bus/firewire/firewire.h>
#include <bus/firewire/iec13213.h>

static char *rom;
static unsigned long expect_fault;

static void
segv(int sig, siginfo_t *si, void *uc)
{
	int hit;

	(void)sig; (void)uc;
	/* the crc_len bitfield is the high halfword of the quadlet at
	 * 'dir', so the faulting load lands at dir+0..dir+3 (observed
	 * dir+2: movzwl for the 16-bit field). */
	hit = ((unsigned long)si->si_addr >= expect_fault &&
	       (unsigned long)si->si_addr <= expect_fault + 3);
	printf("  SIGSEGV: fault address %p, predicted OOB load %p..%p "
	    "(rom+%ld, +%ld past the 1024-byte ROM)\n",
	    si->si_addr, (void *)expect_fault, (void *)(expect_fault + 3),
	    (long)expect_fault - (long)rom,
	    (long)((char *)si->si_addr - (rom + 1024)));
	fflush(stdout);
	_exit(hit ? 42 : 43);
}

static void
run_rom(int val, long off)
{
	struct sigaction sa;

	/* fresh mapping per child: ROM = last 1024 bytes of page 1,
	 * PROT_NONE guard page at rom+1024 .. rom+2048. */
	rom = mmap(NULL, 3 * 4096, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (rom == MAP_FAILED)
		_exit(44);
	mprotect(rom + 4096, 4096, PROT_NONE);
	rom += 4096 - 1024;

	*(u_int32_t *)(rom + 0) = 4 << 24;	/* csrhdr: info_len = 4 */
	/* Q1..Q4 bus info: zero (MAP_ANON) */
	*(u_int32_t *)(rom + 5 * 4) = 1 << 16;	/* root dir: crc_len = 1 */
	*(u_int32_t *)(rom + 6 * 4) =		/* entry 0xd1, value val */
	    (0xd1u << 24) | (val & 0xffffff);

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = segv;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	expect_fault = (unsigned long)(rom + off);

	{
		struct crom_context cc;
		crom_init_context(&cc, (u_int32_t *)rom);
		printf("  walk start (depth=%d), dir read expected at rom+%ld\n",
		    cc.depth, off);
		fflush(stdout);
		while (cc.depth >= 0)
			crom_next(&cc);
		printf("  walk finished cleanly, depth=%d (no fault)\n",
		    cc.depth);
		fflush(stdout);
	}
	_exit(0);
}

int
main(void)
{
	pid_t p;
	int i, st;
	static const struct {
		const char *name;
		int val, off, want;
	} t[3] = {
		{ "A: V=300, dir read at rom+1224 (guard page, +200 past ROM)",
		  300, 24 + 4 * 300, 42 },
		{ "B: V=0xffffff, dir read at rom+0x4000014 (+64 MB reach)",
		  0xffffff, 24 + 4 * 0xffffff, 42 },
		{ "C: V=100, dir read at rom+424 (in-ROM control)",
		  100, 24 + 4 * 100, 0 },
	};

	puts("DF-5497 userspace model: production fwcrom.c vs guard-page ROM");
	for (i = 0; i < 3; i++) {
		printf("subtest %s\n", t[i].name);
		fflush(stdout);
		p = fork();
		if (p == 0)
			run_rom(t[i].val, t[i].off);
		waitpid(p, &st, 0);
		if (WIFEXITED(st))
			printf("  -> child exit %d %s\n", WEXITSTATUS(st),
			    WEXITSTATUS(st) == t[i].want ?
			    "(EXPECTED)" : "(UNEXPECTED)");
		else
			printf("  -> child died by signal %d (UNEXPECTED)\n",
			    WTERMSIG(st));
		fflush(stdout);
	}
	return (0);
}
