/*
 * DF-3662 -- wpi(4) wpi_get_power_index() interpolate() divide-by-zero
 *           from EEPROM TX-power samples (sys/dev/netif/wpi/if_wpi.c).
 *
 * Verbatim-arithmetic harness: the sample walk, the fdivround/interpolate
 * macros and the surrounding arithmetic are copied 1:1 from
 * sys/dev/netif/wpi/if_wpi.c:3773-3835 (wpi_get_power_index) and the
 * EEPROM bulk-load from if_wpi.c:1574-1610 (wpi_read_eeprom_group,
 * which performs NO monotonicity/distinctness validation of sample
 * powers before they are used as interpolation divisors).
 *
 * In the kernel the same integer division by zero raises #DE and panics
 * DragonFly. In this userland harness it raises SIGFPE (exit 136 via
 * shell). The kernel call path that reaches this arithmetic is:
 *
 *   attach:  wpi_attach -> wpi_read_eeprom -> wpi_read_eeprom_group (x5)
 *   bring-up: ifconfig wpi0 up -> wpi_init -> wpi_config ->
 *             wpi_send_rxon -> wpi_set_txpower -> wpi_get_power_index
 *   runtime: every 60s wpi_calib_timeout -> wpi_power_calibration ->
 *             wpi_set_txpower -> wpi_get_power_index
 *             (and on every RXON / channel change)
 *
 * Cases:
 *   A (control): realistic monotonic EEPROM group -> prints idx, exit 0
 *   B (corrupt flash): all-zero EEPROM group -> 0/0 SIGFPE
 *   C (crafted device): adjacent equal sample powers with pwr above them
 *                       -> x2-x1 == 0 SIGFPE
 *
 * Build: cc -O2 -o wpi_pwr_div0 wpi_pwr_div0.c
 * Run:   ./wpi_pwr_div0 <case>    (case = A|B|C)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* --- verbatim from sys/dev/netif/wpi/if_wpivar.h --- */
struct wpi_power_sample {
	uint8_t	index;
	int8_t	power;
};
struct wpi_power_group {
#define WPI_SAMPLES_COUNT	5
	struct wpi_power_sample samples[WPI_SAMPLES_COUNT];
	uint8_t	chan;
	int8_t	maxpwr;
	int16_t	temp;
};

/* --- verbatim from sys/dev/netif/wpi/if_wpi.c:3777-3783 --- */
#define fdivround(a, b, n)	\
	((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n))

#define interpolate(x, x1, y1, x2, y2, n)	\
	((y1) + fdivround(((x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n))

#define WPI_MAX_PWR_INDEX	77
#define	min(a, b)	((a) < (b) ? (a) : (b))

static int sc_maxpwr_chan;	/* sc->maxpwr[chan] (int8_t in kernel) */
static int sc_temp;		/* sc->temp */

/*
 * Verbatim control flow of wpi_get_power_index(),
 * sys/dev/netif/wpi/if_wpi.c:3773-3835 (only sc/ring plumbing elided).
 */
static int
wpi_get_power_index(struct wpi_power_group *group, uint8_t chan,
    int is_chan_5ghz, int ridx)
{
	struct wpi_power_sample *sample;
	int pwr, idx;

	/* Default TX power is group maximum TX power minus 3dB. */
	pwr = group->maxpwr / 2;

	/* Decrease TX power for highest OFDM rates to reduce distortion. */
	switch (ridx) {
	case 5:		/* WPI_RIDX_OFDM36 */
		pwr -= is_chan_5ghz ?  5 : 0;
		break;
	case 6:		/* WPI_RIDX_OFDM48 */
		pwr -= is_chan_5ghz ? 10 : 7;
		break;
	case 7:		/* WPI_RIDX_OFDM54 */
		pwr -= is_chan_5ghz ? 12 : 9;
		break;
	}

	/* Never exceed the channel maximum allowed TX power. */
	pwr = min(pwr, sc_maxpwr_chan);

	/* Retrieve TX power index into gain tables from samples. */
	for (sample = group->samples; sample < &group->samples[3]; sample++)
		if (pwr > sample[1].power)
			break;
	/* DF-3662 fix: degenerate samples must not reach interpolate() */
	if (sample[1].power == sample[0].power)
		idx = sample[0].index;
	else
	/* Fixed-point linear interpolation using a 19-bit fractional part. */
		idx = interpolate(pwr, sample[0].power, sample[0].index,
	    sample[1].power, sample[1].index, 19);

	idx -= (sc_temp - group->temp) * 11 / 100;

	/* Decrease TX power for CCK rates (-5dB). */
	if (ridx >= 8)		/* WPI_RIDX_CCK1 */
		idx += 10;

	/* Make sure idx stays in a valid range. */
	if (idx < 0)
		return 0;
	if (idx > WPI_MAX_PWR_INDEX)
		return WPI_MAX_PWR_INDEX;
	return idx;
}

/*
 * Verbatim semantics of wpi_read_eeprom_group(),
 * sys/dev/netif/wpi/if_wpi.c:1574-1610: raw EEPROM bytes are copied into
 * the group structure with no validation of sample powers. We emulate
 * the bulk load by letting the caller hand us "EEPROM bytes".
 */
static void
wpi_read_eeprom_group(struct wpi_power_group *group, const uint8_t eeprom[64],
    int temp)
{
	int i;

	memset(group, 0, sizeof(*group));
	group->chan   = eeprom[62];
	group->maxpwr = (int8_t)eeprom[60];
	group->temp   = (int16_t)temp;
	for (i = 0; i < WPI_SAMPLES_COUNT; i++) {
		group->samples[i].index = eeprom[i * 4];
		group->samples[i].power = (int8_t)eeprom[i * 4 + 1];
	}
}

int
main(int argc, char *argv[])
{
	struct wpi_power_group group;
	uint8_t eeprom[64];
	char c;

	if (argc != 2 || strlen(argv[1]) != 1) {
		fprintf(stderr, "usage: %s A|B|C\n", argv[0]);
		return 2;
	}
	c = argv[1][0];

	/* Common ambient state (as a just-booted adapter would have). */
	sc_temp = 20;		/* degrees, sane silicon value */
	sc_maxpwr_chan = 40;	/* int8_t EEPROM channel cap, sane value */

	switch (c) {
	case 'A':
		/*
		 * Control: realistic 3945 EEPROM group -- strictly
		 * decreasing sample powers (33,27,21,15,9) with rising
		 * gain indices. Reference-driver-like content.
		 */
		memset(eeprom, 0, sizeof(eeprom));
		eeprom[0] = 0x5a; eeprom[1]  = 33;	/* s0 */
		eeprom[4] = 0x44; eeprom[5]  = 27;	/* s1 */
		eeprom[8] = 0x33; eeprom[9]  = 21;	/* s2 */
		eeprom[12]= 0x18; eeprom[13] = 15;	/* s3 */
		eeprom[16]= 0x0a; eeprom[17] =  9;	/* s4 */
		eeprom[60] = 56;			/* group maxpwr */
		eeprom[62] = 36;			/* group top chan */
		wpi_read_eeprom_group(&group, eeprom, 253 /* Kelvin */);
		break;
	case 'B':
		/*
		 * Corrupt/zeroed EEPROM (flash corruption on a genuine
		 * card, or a crafted device): every sample power is 0.
		 * pwr = 0 never exceeds sample[1].power == 0, so the walk
		 * falls through to samples[3]; interpolate() then divides
		 * by sample[4].power - sample[3].power == 0  ->  SIGFPE.
		 */
		memset(eeprom, 0, sizeof(eeprom));
		wpi_read_eeprom_group(&group, eeprom, 0);
		break;
	case 'C':
		/*
		 * Crafted device: adjacent samples share the same power
		 * (20, 20, 12, 8, 4) and the computed pwr (28) sits above
		 * them, so the walk breaks at samples[0] and interpolate()
		 * divides by sample[1].power - sample[0].power == 0.
		 */
		memset(eeprom, 0, sizeof(eeprom));
		eeprom[0] = 0x40; eeprom[1]  = 20;	/* s0 */
		eeprom[4] = 0x50; eeprom[5]  = 20;	/* s1 == s0 */
		eeprom[8] = 0x60; eeprom[9]  = 12;
		eeprom[12]= 0x70; eeprom[13] =  8;
		eeprom[16]= 0x78; eeprom[17] =  4;
		eeprom[60] = 60;			/* maxpwr -> pwr = 30 */
		eeprom[62] = 36;
		wpi_read_eeprom_group(&group, eeprom, 253);
		break;
	default:
		fprintf(stderr, "usage: %s A|B|C\n", argv[0]);
		return 2;
	}

	printf("case %c: group maxpwr=%d temp=%d samples pow={%d,%d,%d,%d,%d}"
	    " idx={%d,%d,%d,%d,%d} pwr-walk -> ",
	    c, group.maxpwr, group.temp,
	    group.samples[0].power, group.samples[1].power,
	    group.samples[2].power, group.samples[3].power,
	    group.samples[4].power,
	    group.samples[0].index, group.samples[1].index,
	    group.samples[2].index, group.samples[3].index,
	    group.samples[4].index);
	fflush(stdout);

	/* Exactly the call wpi_set_txpower() makes 12x per invocation. */
	printf("idx=%d\n", wpi_get_power_index(&group, 36 /* chan */, 0, 0));
	printf("case %c: SURVIVED (no SIGFPE)\n", c);
	return 0;
}
