/*
 * DF-5342 — u_int truncation of the HT-fixup APIC-id mask for ids 32..63
 * (mptable.c:641,658-659,784) makes mptable_hyperthread_fixup() decide on
 * partial data and enumerate phantom CPUs.
 *
 * pass1 builds:   u_int ht_apicid_mask |= 1UL << ent->apic_id;   (id < 64)
 * For 32 <= id < 64 the 64-bit 1UL<<id truncates to 0 when stored in the
 * u_int — the id silently vanishes from the mask while ht_fixup stays
 * ENABLED (disable only happens for id >= 64, mptable.c:660-663).
 * mask.ary[0] = ht_apicid_mask (mptable.c:784) then feeds
 * mptable_hyperthread_fixup() whose distance/boundary checks run over the
 * partial set.  arg1.cpu_count *= logical_cpus (mptable.c:787) inflates
 * naps and pass2 synthesizes fake PROC entries with APIC ids that do not
 * exist -> start_all_aps() SIPIs phantom ids and the boot waits forever
 * on smp_startup_mask (mp_machdep.c:595).
 *
 * Demonstrated with a table listing ids {0, 2, 33} on an HTT CPU with
 * lcpus_max = 2:
 *   - with the in-tree u_int mask: ids {0,2} -> dist=2 -> fixup returns 2
 *     -> cpu_count 3*2=6, naps=5, fakes 1,3,34 enumerated (phantoms).
 *   - with a non-truncating 64-bit mask: distances 2 and 31 differ ->
 *     fixup returns 0 (the intended "kill the fixup" decision).
 *
 * mptable_hyperthread_fixup() body is verbatim (mptable.c:400-498);
 * CPUMASK helpers mirror sys/cpu/x86_64/include/cpumask.h (MAXCPU=256,
 * ary[4], word index masked & 3).  cpu_feature/vendor plumbing is
 * pre-applied (HTT present, lcpus_max already power-of-2) as noted.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

typedef unsigned char u_char;
typedef unsigned int u_int;

#define MAXCPU		256
#define CPUMASK_TESTBIT(m, i)	((((m).ary[((i) >> 6) & 3]) >> ((i) & 63)) & 1)

struct cpumask { uint64_t ary[4]; };

static void
kassert(int ok)
{
	if (!ok) {
		fprintf(stderr, "KKASSERT fired\n");
		exit(86);
	}
}

/* ---- verbatim: mptable_hyperthread_fixup (mptable.c:400-498) ----
 * (HTT-present and Intel pow2-roundup pre-applied; lcpus_max passed in) */
static int
mptable_hyperthread_fixup(struct cpumask id_mask, int cpu_count, int lcpus_max)
{
	int i, id, logical_cpus;

	kassert(cpu_count != 0);			/* mptable.c:429 */
	if (cpu_count == lcpus_max)
		return 0;
	else if (cpu_count == 1)
		logical_cpus = lcpus_max;
	else {
		int cur, prev, dist;

		dist = cur = prev = -1;
		for (id = 0; id < MAXCPU; ++id) {	/* mptable.c:446 */
			if (CPUMASK_TESTBIT(id_mask, id) == 0)
				continue;
			cur = id;
			if (prev >= 0) {
				int new_dist = cur - prev;
				if (dist < 0)
					dist = new_dist;
				if (dist != new_dist)	/* mptable.c:462 */
					return 0;
			}
			prev = cur;
		}
		if (dist == 1)				/* mptable.c:467 */
			return 0;
		if (dist & (dist - 1))			/* mptable.c:471: must be pow2 */
			return 0;
		if (dist > lcpus_max)
			logical_cpus = lcpus_max;
		else
			logical_cpus = dist;
	}

	for (id = 0; id < MAXCPU; id++) {		/* mptable.c:487 */
		if (CPUMASK_TESTBIT(id_mask, id) == 0)
			continue;
		if (id % logical_cpus != 0)
			return 0;
		for (i = id + 1; i < id + logical_cpus; i++)
			if (CPUMASK_TESTBIT(id_mask, i) != 0)
				return 0;
	}
	return logical_cpus;
}

/* ---- verbatim: pass1 mask build (mptable.c:654-664) ---- */
static u_int
build_mask_u_int(const u_char ids[], int n, int *cpu_count, int *ht_fixup)
{
	u_int ht_apicid_mask = 0;
	int i;

	*cpu_count = 0;
	*ht_fixup = 1;
	for (i = 0; i < n; i++) {
		(*cpu_count)++;
		if (ids[i] < 64)
			ht_apicid_mask |= 1UL << ids[i];	/* mptable.c:659 — truncates for id >= 32 */
		else if (*ht_fixup)
			*ht_fixup = 0;
	}
	return ht_apicid_mask;
}

static uint64_t
build_mask_true(const u_char ids[], int n)
{
	uint64_t m = 0;
	int i;

	for (i = 0; i < n; i++)
		if (ids[i] < 64)
			m |= 1ULL << ids[i];
	return m;
}

static void
run(const char *name, const u_char ids[], int n, int lcpus_max)
{
	struct cpumask mask;
	u_int m_ui;
	uint64_t m_true;
	int cpu_count, ht_fixup, fix, i;
	m_ui = build_mask_u_int(ids, n, &cpu_count, &ht_fixup);
	m_true = build_mask_true(ids, n);

	/* in-tree path: mask.ary[0] = (u_int)ht_apicid_mask (mptable.c:784) */
	memset(&mask, 0, sizeof(mask));
	mask.ary[0] = m_ui;
	fix = ht_fixup ? mptable_hyperthread_fixup(mask, cpu_count, lcpus_max) : 0;
	printf("== %s ==\n", name);
	printf("  ids present      : {");
	for (i = 0; i < n; i++) printf(" %u", ids[i]);
	printf(" }  cpu_count=%d lcpus_max=%d\n", cpu_count, lcpus_max);
	printf("  in-tree u_int mask=0x%x ", m_ui);
	if (fix) {
		printf("-> fixup ENGAGES: logical_cpus=%d\n"
		    "     cpu_count *= logical_cpus -> %d, naps = %d\n"
		    "     pass2 fakes enumerated for EVERY entry: apic ids {",
		    fix, cpu_count * fix, cpu_count * fix - 1);
		for (i = 0; i < n; i++) {
			int j;
			for (j = 1; j < fix; j++)
				printf(" %u", (u_char)(ids[i] + j));
		}
		printf(" } (phantom ids)\n");
	} else {
		printf("-> fixup killed (return 0)\n");
	}

	/* true 64-bit mask, same inputs */
	memset(&mask, 0, sizeof(mask));
	mask.ary[0] = m_true;
	fix = mptable_hyperthread_fixup(mask, cpu_count, lcpus_max);
	printf("  true 64-bit mask=0x%llx -> fixup returns %d%s\n",
	    (unsigned long long)m_true, fix,
	    fix ? " (ENGAGES)" : " (killed — the intended decision)");
}

int
main(void)
{
	const u_char ids1[] = { 0, 2, 33 };
	const u_char ids2[] = { 0, 2 };		/* control: truncation irrelevant */
	const u_char ids3[] = { 0, 33, 34 };	/* id>=32 group spaced */

	run("trigger table: ids {0,2,33}, lcpus_max=2", ids1, 3, 2);
	run("control table: ids {0,2}, lcpus_max=2", ids2, 2, 2);
	run("trigger table: ids {0,33,34}, lcpus_max=2", ids3, 3, 2);
	return 0;
}
