/* SPDX-License-Identifier: BSD-2-Clause
 * DF-1048 harness: proves the divide-by-zero primitive in
 * umcs7840_calc_baudrate() at the arithmetic level.
 *
 * The guest VM has NO USB host controller and no MCS7820/MCS7840 adapter,
 * so the live tcsetattr(B0) trigger path cannot be exercised at runtime.
 * This harness extracts the EXACT buggy function and its data table
 * verbatim from sys/bus/u4b/serial/umcs.c and replays the arithmetic
 * that the kernel would perform when called with rate==0 (B0).
 *
 * On x86-64, integer division by zero raises #DE.  In kernel mode that is
 * a fatal trap (panic, "trap type 17").  In userspace the same #DE fault
 * is delivered as SIGFPE (Floating point exception / Arithmetic exception).
 * So a SIGFPE here is the direct userspace analog of the kernel panic the
 * finding describes.
 *
 * `rate` is passed through `volatile` so the optimizer cannot exploit the
 * undefined-behaviour of "divide by zero" to elide the div instruction
 * (it does so at -O2 otherwise).  The kernel has no such luxury: gcc
 * emits a real `div`/`idiv` and the CPU raises #DE.
 *
 * Build:  cc -O0 -o harness harness.c   (or -O2, volatile defeats the elision)
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>

/* ---- verbatim copy from sys/bus/u4b/serial/umcs.c:1052-1054 ---- */
#define NELEM(a) (sizeof(a) / sizeof(a[0]))
static const uint32_t umcs7840_baudrate_divisors[] =
    {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,};
static const uint8_t umcs7840_baudrate_divisors_len = NELEM(umcs7840_baudrate_divisors);

/*
 * volatile sink so the optimizer cannot constant-fold / dead-store-eliminate
 * the outputs (and thereby drop the division).
 */
static volatile uint16_t sink_div;
static volatile uint8_t  sink_clk;

/* ---- verbatim copy of the BUGGY function, umcs.c:1056-1070 ----
 * `rate` is read through a volatile pointer so the compiler must emit a
 * genuine hardware divide and cannot prove rate!=0. */
static int
umcs7840_calc_baudrate_BUGGY(volatile const uint32_t *ratep, uint16_t *divisor, uint8_t *clk)
{
	volatile const uint32_t rate = *ratep;
	uint8_t i = 0;

	if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1])
		return (-1);

	for (i = 0; i < umcs7840_baudrate_divisors_len - 1 &&
	    !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i);
	*divisor = umcs7840_baudrate_divisors[i + 1] / rate;	/* <-- trap when rate==0 */
	*clk = i << 4; /* MCS7840_DEV_SPx_CLOCK_SHIFT is 4 */
	return (0);
}

/* ---- the FIXED function (mirrors fix.diff) ---- */
static int
umcs7840_calc_baudrate_FIXED(volatile const uint32_t *ratep, uint16_t *divisor, uint8_t *clk)
{
	volatile const uint32_t rate = *ratep;
	uint8_t i = 0;

	if (rate == 0 ||
	    rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1])
		return (-1);

	for (i = 0; i < umcs7840_baudrate_divisors_len - 1 &&
	    !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i);
	*divisor = umcs7840_baudrate_divisors[i + 1] / rate;
	*clk = i << 4;
	return (0);
}

static sigjmp_buf jb;
static volatile sig_atomic_t got_fpe;
static void
fpe_handler(int sig)
{
	(void)sig;
	got_fpe = 1;
	siglongjmp(jb, 1);
}

static const char *
test(const char *which, int (*fn)(volatile const uint32_t *, uint16_t *, uint8_t *), uint32_t rate_in)
{
	uint32_t rate = rate_in;	/* addressable copy */
	uint16_t divisor = 0xDEAD;
	uint8_t  clk = 0xFF;

	got_fpe = 0;
	struct sigaction sa, old;
	sa.sa_handler = fpe_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGFPE, &sa, &old);
	if (sigsetjmp(jb, 1) == 0) {
		int rc = fn(&rate, &divisor, &clk);
		sigaction(SIGFPE, &old, NULL);
		sink_div = divisor;
		sink_clk = clk;
		static char buf[160];
		snprintf(buf, sizeof(buf), "returned rc=%d divisor=0x%04x clk=0x%02x",
		    rc, divisor, clk);
		(void)which;
		return buf;
	}
	sigaction(SIGFPE, &old, NULL);
	return "*** DIVIDE BY ZERO (SIGFPE / #DE trap) ***";
}

int
main(void)
{
	printf("DF-1048 harness: umcs7840_calc_baudrate divide-by-zero proof\n");
	printf("array umcs7840_baudrate_divisors has %u elements (len-1 = %u)\n",
	    umcs7840_baudrate_divisors_len, (uint8_t)(umcs7840_baudrate_divisors_len - 1));
	printf("On x86-64, integer /0 raises #DE -> kernel panic (trap 17); "
	       "userspace analog is SIGFPE.\n\n");

	printf("== BUGGY version (mirrors umcs.c:1056-1070 as shipped) ==\n");
	printf("  rate=%-10u -> %s\n", 0u,      test("BUGGY", umcs7840_calc_baudrate_BUGGY, 0u));
	printf("  rate=%-10u -> %s\n", 115200u, test("BUGGY", umcs7840_calc_baudrate_BUGGY, 115200u));
	printf("  rate=%-10u -> %s\n", 921600u, test("BUGGY", umcs7840_calc_baudrate_BUGGY, 921600u));
	printf("\n");

	printf("== FIXED version (mirrors fix.diff: rate==0 guard) ==\n");
	printf("  rate=%-10u -> %s\n", 0u,      test("FIXED", umcs7840_calc_baudrate_FIXED, 0u));
	printf("  rate=%-10u -> %s\n", 115200u, test("FIXED", umcs7840_calc_baudrate_FIXED, 115200u));
	printf("  rate=%-10u -> %s\n", 921600u, test("FIXED", umcs7840_calc_baudrate_FIXED, 921600u));
	printf("\n");

	printf("CONCLUSION: buggy version faults on rate==0 (kernel #DE panic, trap 17);\n");
	printf("            fixed version returns -1 cleanly (-> EINVAL propagates to tcsetattr).\n");
	return 0;
}
