/*
 * DF-5339 — probe/enumerate BSP-validation asymmetry in mptable.c
 *
 * mptable_lapic_probe_callback() (mptable.c:818-845) skips DISABLED
 * processor entries before its duplicate-BSP check, so a table whose
 * second BP-flagged entry is disabled passes probe().
 * mptable_lapic_pass2_callback() (mptable.c:674-686) does the BP
 * bookkeeping with NO enabled check and KKASSERTs on the second
 * BP-flagged entry regardless of EN  ->  boot-time kernel panic on
 * INVARIANTS kernels (X86_64_GENERIC has INVARIANTS, sys/config/X86_64_GENERIC:56).
 *
 * This harness copies both callbacks' control flow verbatim (u_char flag
 * semantics preserved) and demonstrates: probe() accepts the table,
 * enumerate pass2 then trips KKASSERT(!arg->found_bsp).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>

typedef unsigned char u_char;

#define PROCENTRY_FLAG_EN	0x01
#define PROCENTRY_FLAG_BP	0x02
#define APICID_MAX		255

static sigjmp_buf panic_jmp;
static int in_panic_scope;
static char panic_msg[256];

static void kpanic(const char *exp)
{
	snprintf(panic_msg, sizeof(panic_msg),
	    "assertion \"%s\" failed in mptable_lapic_pass2_callback "
	    "at mptable.c:684", exp);
	if (in_panic_scope) {
		in_panic_scope = 0;
		siglongjmp(panic_jmp, 1);
	}
	fprintf(stderr, "KERNEL PANIC: %s\n", panic_msg);
	exit(86);
}
#define KKASSERT(x) do { if (!(x)) kpanic(#x); } while (0)

struct PROCENTRY {
	u_char  type;
	u_char  apic_id;
	u_char  apic_version;
	u_char  cpu_flags;
	unsigned int cpu_signature, feature_flags, reserved1, reserved2;
};

/* ---- verbatim control flow: mptable_lapic_probe_callback (mptable.c:818-845) ---- */
struct mptable_lapic_probe_cbarg {
	int	cpu_count;
	int	found_bsp;
};

static int
probe_callback(struct mptable_lapic_probe_cbarg *arg,
    const struct PROCENTRY *ent)
{
	if ((ent->cpu_flags & PROCENTRY_FLAG_EN) == 0)
		return 0;			/* mptable.c:827-829: disabled skipped BEFORE BP check */
	arg->cpu_count++;

	if (ent->apic_id == APICID_MAX)
		return 1;			/* EINVAL */

	if (ent->cpu_flags & PROCENTRY_FLAG_BP) {
		if (arg->found_bsp)
			return 1;		/* EINVAL: "more than one BSP" — but only ENABLED ones ever reach here */
		arg->found_bsp = 1;
	}
	return 0;
}

/* ---- verbatim control flow: BP bookkeeping of mptable_lapic_pass2_callback (mptable.c:674-686) ---- */
struct mptable_lapic_cbarg2 {
	int	found_bsp;
};

static int
pass2_bp_check(struct mptable_lapic_cbarg2 *arg, const struct PROCENTRY *ent)
{
	if (ent->cpu_flags & PROCENTRY_FLAG_BP) {	/* NO EN check */
		KKASSERT(!arg->found_bsp);		/* mptable.c:684 */
		arg->found_bsp = 1;
	}
	return 0;
}

static void
run_table(const char *name, const struct PROCENTRY tbl[], int n)
{
	struct mptable_lapic_probe_cbarg pa;
	struct mptable_lapic_cbarg2 p2;
	int i, probe_err;

	memset(&pa, 0, sizeof(pa));
	memset(&p2, 0, sizeof(p2));
	probe_err = 0;

	printf("== %s ==\n", name);
	for (i = 0; i < n; i++) {
		if (probe_callback(&pa, tbl + i)) {
			probe_err = 1;
			break;
		}
	}
	printf("  probe : cpu_count=%d found_bsp=%d -> %s\n",
	    pa.cpu_count, pa.found_bsp,
	    probe_err ? "EINVAL (mptable enumerator rejected, ACPI path used) "
	      : "PASS (mptable enumerator selected)");

	if (probe_err)
		return;		/* enumerate never runs: no bug */

	in_panic_scope = 1;
	if (sigsetjmp(panic_jmp, 1) == 0) {
		for (i = 0; i < n; i++)
			pass2_bp_check(&p2, tbl + i);
		in_panic_scope = 0;
		printf("  pass2 : completed, found_bsp=%d (no panic)\n", p2.found_bsp);
	} else {
		printf("  pass2 : KERNEL PANIC -> %s\n", panic_msg);
	}
}

#define ENT(_id, _fl) { 0, (_id), 0x14, (_fl), 0, 0, 0, 0 }

int
main(void)
{
	/* Trigger: enabled BSP + second BP-flagged but DISABLED entry.
	 * Probe sees only the enabled one -> found_bsp=1, accepts.
	 * Pass2 asserts on the raw BP bit of entry #2 -> panic. */
	const struct PROCENTRY trig[2] = {
		ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP),
		ENT(1, PROCENTRY_FLAG_BP),	/* disabled: EN clear, BP set */
	};
	/* Control 1: two enabled BSP entries — correctly caught by probe. */
	const struct PROCENTRY ctrl1[2] = {
		ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP),
		ENT(1, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP),
	};
	/* Control 2: healthy table — no panic anywhere. */
	const struct PROCENTRY ctrl2[2] = {
		ENT(0, PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP),
		ENT(2, PROCENTRY_FLAG_EN),
	};

	run_table("trigger: [EN|BP] [BP] (2nd disabled BSP)", trig, 2);
	run_table("control: [EN|BP] [EN|BP] (both enabled)", ctrl1, 2);
	run_table("control: [EN|BP] [EN] (healthy table)", ctrl2, 2);
	return 0;
}
