/*
 * mtwprobe.c - structural proof for DF-5258:
 * DragonFly sys/bus/u4b/wlan/if_mtwvar.h declares
 *     struct mtw_endpoint_queue sc_epq[MTW_BULK_RX];
 * where MTW_BULK_RX is the FIRST constant of the xfer enum == 0.
 * GCC treats this as a zero-length array: sizeof(struct mtw_softc)
 * therefore does NOT include the 6 endpoint queues, yet
 * mtw_init_locked() (if_mtw.c:4466-4467) calls mtw_setup_tx_list()
 * on sc->sc_epq[0..5] on every interface UP, stamping
 * 6 * sizeof(struct mtw_endpoint_queue) bytes starting at the
 * sc_epq offset - far past the end of the softc allocation.
 *
 * This module compiles the REAL if_mtwvar.h from the guest kernel
 * source, prints the real sizes/offsets, then re-runs the exact
 * mtw_setup_tx_list() stamping loop (copied verbatim from
 * if_mtw.c:949-965) against a kmalloc'd buffer the size of the
 * softc followed by a 0xAA guard region, and counts how many guard
 * bytes get clobbered - i.e. how far past the softc allocation the
 * writes land.  No MT7601U hardware is needed for this proof.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/mbuf.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_radiotap.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>

#include "opt_wlan.h"
#include "if_mtwreg.h"
#include "if_mtwvar.h"	/* the real thing: /usr/src/sys/bus/u4b/wlan/if_mtwvar.h */

/* verbatim copy of mtw_setup_tx_list() - sys/bus/u4b/wlan/if_mtw.c:949-965 */
static void
mtw_setup_tx_list(struct mtw_softc *sc, struct mtw_endpoint_queue *pq)
{
	struct mtw_tx_data *data;

	memset(pq, 0, sizeof(*pq));

	STAILQ_INIT(&pq->tx_qh);
	STAILQ_INIT(&pq->tx_fh);

	for (data = &pq->tx_data[0]; data < &pq->tx_data[MTW_TX_RING_COUNT];
	     data++) {
		data->sc = sc;
		STAILQ_INSERT_TAIL(&pq->tx_fh, data, next);
	}
	pq->tx_nfree = MTW_TX_RING_COUNT;
}

#define	GUARD	(256 * 1024)

static void
report(void)
{
	size_t scsz = sizeof(struct mtw_softc);
	size_t pqsz = sizeof(struct mtw_endpoint_queue);
	size_t epq_off = __offsetof(struct mtw_softc, sc_epq);
	uint8_t *buf;
	struct mtw_softc *sc;
	size_t i, clobber = 0, firstbad = 0;
	int q;

	kprintf("mtwprobe: MTW_BULK_RX=%d MTW_EP_QUEUES=%d MTW_TX_RING_COUNT=%d\n",
	    MTW_BULK_RX, MTW_EP_QUEUES, MTW_TX_RING_COUNT);
	kprintf("mtwprobe: sizeof(struct mtw_softc)            = %zu\n", scsz);
	kprintf("mtwprobe: sizeof(struct mtw_endpoint_queue)   = %zu\n", pqsz);
	kprintf("mtwprobe: sizeof(sc->sc_epq) as declared      = %zu  <-- zero-length array!\n",
	    sizeof(((struct mtw_softc *)0)->sc_epq));
	kprintf("mtwprobe: offsetof(struct mtw_softc, sc_epq)  = %zu\n", epq_off);
	kprintf("mtwprobe: offsetof(rfswitch) [next member]    = %zu (== sc_epq base)\n",
	    __offsetof(struct mtw_softc, rfswitch));
	kprintf("mtwprobe: 6 queues need %zu bytes from offset %zu -> end %zu; softc is only %zu bytes -> %zd bytes PAST the allocation\n",
	    pqsz * MTW_EP_QUEUES, epq_off, epq_off + pqsz * MTW_EP_QUEUES, scsz,
	    (ssize_t)(epq_off + pqsz * MTW_EP_QUEUES - scsz));

	buf = kmalloc(scsz + GUARD, M_TEMP, M_WAITOK | M_ZERO);
	memset(buf + scsz, 0xAA, GUARD);
	sc = (struct mtw_softc *)buf;

	/* exactly what mtw_init_locked() does at if_mtw.c:4466-4467 */
	for (q = 0; q != MTW_EP_QUEUES; q++)
		mtw_setup_tx_list(sc, &sc->sc_epq[q]);

	for (i = 0; i < GUARD; i++) {
		if (buf[scsz + i] != 0xAA) {
			if (clobber == 0)
				firstbad = i;
			clobber++;
		}
	}
	kprintf("mtwprobe: simulated ifconfig-up stamping clobbered %zu bytes past sizeof(struct mtw_softc) (first at +%zu)\n",
	    clobber, firstbad);


	/* ---- alias mapping: which live softc fields fall inside the epq region? ---- */
	struct { const char *n; size_t o; } fields[] = {
		{"rfswitch", __offsetof(struct mtw_softc, rfswitch)},
		{"txpow1", __offsetof(struct mtw_softc, txpow1)},
		{"lna", __offsetof(struct mtw_softc, lna)},
		{"txpow20mhz", __offsetof(struct mtw_softc, txpow20mhz)},
		{"sc_chan_group", __offsetof(struct mtw_softc, sc_chan_group)},
		{"cmd_seq", __offsetof(struct mtw_softc, cmd_seq)},
		{"sc_detached", __offsetof(struct mtw_softc, sc_detached)},
		{"sc_mcu", __offsetof(struct mtw_softc, sc_mcu)},
		{"rxq", __offsetof(struct mtw_softc, rxq)},
		{"txq", __offsetof(struct mtw_softc, txq)},
		{"ratectl_task", __offsetof(struct mtw_softc, ratectl_task)},
		{"ratectl_ch", __offsetof(struct mtw_softc, ratectl_ch)},
		{"sc_bssid", __offsetof(struct mtw_softc, sc_bssid)},
		{"sc_rxtapu", __offsetof(struct mtw_softc, sc_rxtapu)},
		{"sc_txtapu", __offsetof(struct mtw_softc, sc_txtapu)},
	};
	size_t epq_end = epq_off + pqsz * MTW_EP_QUEUES;
	for (i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) {
		const char *where;
		if (fields[i].o < epq_off)
			where = "BEFORE epq region";
		else if (fields[i].o < epq_end) {
			size_t rel = fields[i].o - epq_off;
			size_t q = rel / pqsz;
			size_t in = rel % pqsz;
			size_t ent = in / sizeof(struct mtw_tx_data);
			size_t off = in % sizeof(struct mtw_tx_data);
			kprintf("mtwprobe: LIVE FIELD %-14s @+%6zu INSIDE epq[%zu] tx_data[%zu] + %zu (%s)\n",
			    fields[i].n, fields[i].o, q, ent, off,
			    off < 16 ? "STAILQ next ptr" :
			    off < 24 ? "m (mbuf*)" :
			    off < 32 ? "sc ptr" :
			    off < 40 ? "xfer ptr" :
			    off < 44 ? "qid/ridx" :
			    off < 48 ? "buflen" :
			    off < 56 ? "ni (node*) !" : "desc[]");
			continue;
		} else
			where = "after epq region (safe)";
		kprintf("mtwprobe: live field %-14s @+%6zu %s\n", fields[i].n, fields[i].o, where);
	}

	kfree(buf, M_TEMP);

	/* ---- forge simulation with RAW byte addresses (no member access) ---- */
	{
		uint8_t *p = (uint8_t *)sc;
		void **ni_p = (void **)(p + __offsetof(struct mtw_softc, sc_epq) +
		    3 * sizeof(struct mtw_tx_data) + 48);	/* epq[0].tx_data[3].ni */

		kprintf("mtwprobe: raw check: &sc_chan_group=%p  epq[0].tx_data[3].ni at %p (same? %d)\n",
		    (void *)(p + __offsetof(struct mtw_softc, sc_chan_group)),
		    (void *)ni_p,
		    (size_t)ni_p + 4 == (size_t)(p + __offsetof(struct mtw_softc, sc_chan_group)));

		/* idle case: post-stamp ni == NULL; mtw_set_chan() writes group */
		kprintf("mtwprobe: idle: ni before = %p\n", *ni_p);
		sc->sc_chan_group = 2;			/* mtw_set_chan(), if_mtw.c:3758 */
		kprintf("mtwprobe: idle: ni after sc_chan_group=2 -> %p  <-- forged node ptr\n", *ni_p);

		/* in-flight case: entry holds a real node pointer */
		*ni_p = (void *)0xfffff800deadbeefULL;
		sc->sc_chan_group = 3;
		kprintf("mtwprobe: in-flight: ni was 0xfffff800deadbeef, after sc_chan_group=3 -> %p\n", *ni_p);
		kprintf("mtwprobe: this pointer is passed to ieee80211_tx_complete()/ieee80211_free_node() on TX completion.\n");
	}
	kfree(buf, M_TEMP);
}

static int
mtwprobe_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		report();
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t mtwprobe_mod = { "mtwprobe", mtwprobe_modevent, NULL };
DECLARE_MODULE(mtwprobe, mtwprobe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
