/*
 * DF-5531 — measurement rig: usbpf_xfertap() builds and copies the full
 * USB transfer record even when NO bpf listener exists.
 *
 * DragonFly disabled FreeBSD's early-out:
 *   sys/bus/u4b/usb_pf.c:364-367
 *   #if 0 // XXX this is not needed on dragonfly
 *   if (!bpf_peers_present(bus->ifp->if_bpf)) return;
 *   #endif
 *
 * and DragonFly's bpf only sets ifp->if_bpf non-NULL while a /dev/bpfN
 * listener is actually attached to the interface (net/bpf.c:297 sets
 * *driverp = bp in bpf_attachd; bpf.c:324 NULLs it again in
 * bpf_detachd).  bpf_tap() itself NULL-checks and returns immediately
 * (net/bpf.c:1245-1248) — so with a created-but-unwatched usbus clone,
 * EVERY USB transfer on that bus still pays:
 *   precompute + kmalloc(totlen) + full payload copy + kfree
 * under the USB BUS lock on the SUBMIT paths (usb_transfer.c:2630/2813).
 *
 * This module calls the production usbpf_xfertap() (global symbol,
 * usb_pf.c:345) with a synthetic 8x512-byte BULK-OUT xfer against the
 * real usbus0 bus, and times:
 *   (A) bus->ifp set (clone created, NO listener)  -> full work, bpf no-op
 *   (B) bus->ifp NULLed (control)                  -> early return :362
 *
 * A >> B proves the copy machinery runs with no consumer.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
#include <sys/condvar.h>
#include <machine/cpufunc.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_clone.h>
#include <net/if_types.h>
#include <net/bpf.h>

/* mirror usb_pf.c include order (usb_bus.h depends on these) */
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_busdma.h>

#include <bus/u4b/usb_controller.h>
#include <bus/u4b/usb_core.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/usb_device.h>
#include <bus/u4b/usb_bus.h>
#include <bus/u4b/usb_pf.h>
#include <bus/u4b/usb_transfer.h>

#define	NFRAMES		8
#define	FRLEN		512
#define	ITERS		20000

static int
df5531_run(void)
{
	struct usb_bus *ubus;
	devclass_t dc;
	device_t dev;
	struct ifnet *saved_ifp;
	struct usb_xfer *xfer;
	struct usb_xfer_root *xroot;
	struct usb_device *udev;
	struct usb_endpoint *ep;
	struct usb_endpoint_descriptor *edesc;
	struct usb_page_cache *frb;
	usb_frlength_t *frl;
	uint8_t *payload;
	uint64_t t0, t1, tA, tB;
	int i, error = 0;

	dc = devclass_find("usbus");
	if (dc == NULL || (dev = devclass_get_device(dc, 0)) == NULL ||
	    (ubus = device_get_softc(dev)) == NULL) {
		kprintf("DF5531: no usbus0\n");
		return (ENOENT);
	}
	if (ubus->ifp == NULL) {
		kprintf("DF5531: usbus0 clone not created "
		    "(run: ifconfig usbus0 create)\n");
		return (ENOENT);
	}
	kprintf("DF5531: bus ifp=%p if_bpf=%p (expect NULL: no listener)\n",
	    ubus->ifp, ubus->ifp->if_bpf);

	/* ---- fabricate a minimal 8x512 BULK-OUT host xfer ---- */
	payload = kmalloc(NFRAMES * FRLEN, M_TEMP, M_WAITOK | M_ZERO);
	memset(payload, 0xA5, NFRAMES * FRLEN);
	frb = kmalloc(sizeof(*frb) * NFRAMES, M_TEMP, M_WAITOK | M_ZERO);
	for (i = 0; i < NFRAMES; i++)
		frb[i].buffer = payload + i * FRLEN;	/* PIO, page_start NULL */
	frl = kmalloc(sizeof(*frl) * NFRAMES, M_TEMP, M_WAITOK | M_ZERO);
	for (i = 0; i < NFRAMES; i++)
		frl[i] = FRLEN;

	edesc = kmalloc(sizeof(*edesc), M_TEMP, M_WAITOK | M_ZERO);
	edesc->bmAttributes = UE_BULK;			/* xfer type for header */
	ep = kmalloc(sizeof(*ep), M_TEMP, M_WAITOK | M_ZERO);
	ep->edesc = edesc;

	udev = kmalloc(sizeof(*udev), M_TEMP, M_WAITOK | M_ZERO);
	udev->device_index = 1;
	udev->speed = USB_SPEED_FULL;

	xroot = kmalloc(sizeof(*xroot), M_TEMP, M_WAITOK | M_ZERO);
	xroot->bus = ubus;				/* REAL bus: bdev,parent */
	xroot->udev = udev;

	xfer = kmalloc(sizeof(*xfer), M_TEMP, M_WAITOK | M_ZERO);
	xfer->xroot = xroot;
	xfer->endpoint = ep;
	xfer->frlengths = frl;
	xfer->frbuffers = frb;
	xfer->nframes = NFRAMES;
	xfer->aframes = NFRAMES;
	xfer->max_frame_count = NFRAMES;
	xfer->max_data_length = NFRAMES * FRLEN;
	xfer->endpointno = 0;				/* host -> device (OUT) */
	/* flags_int zeroed: usb_mode = USB_MODE_HOST, not control/isoc */

	/* ---- (A) clone exists, no listener: tap does ALL the work ---- */
	tA = 0;
	for (i = 0; i < ITERS; i++) {
		t0 = rdtsc();
		usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT);
		t1 = rdtsc();
		tA += t1 - t0;
	}

	/* ---- (B) control: bus->ifp == NULL -> early return at :362 ---- */
	saved_ifp = ubus->ifp;
	ubus->ifp = NULL;
	tB = 0;
	for (i = 0; i < ITERS; i++) {
		t0 = rdtsc();
		usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT);
		t1 = rdtsc();
		tB += t1 - t0;
	}
	ubus->ifp = saved_ifp;

	kprintf("DF5531: payload=%d bytes, %d iters\n",
	    NFRAMES * FRLEN, ITERS);
	kprintf("DF5531: (A) ifp set,   if_bpf==NULL : %ju cycles/call\n",
	    (uintmax_t)(tA / ITERS));
	kprintf("DF5531: (B) ifp NULL (early-return) : %ju cycles/call\n",
	    (uintmax_t)(tB / ITERS));
	kprintf("DF5531: overhead ratio A/B          : %ju.%ju x\n",
	    (uintmax_t)(tB ? tA / tB : 0),
	    (uintmax_t)(tB ? ((uint64_t)((unsigned long long)tA * 10ULL /
	        (unsigned long long)tB)) % 10ULL : 0));
	kprintf("TAP_RESULT=done\n");

	kfree(xfer, M_TEMP);
	kfree(xroot, M_TEMP);
	kfree(udev, M_TEMP);
	kfree(ep, M_TEMP);
	kfree(edesc, M_TEMP);
	kfree(frl, M_TEMP);
	kfree(frb, M_TEMP);
	kfree(payload, M_TEMP);
	return (error);
}

static int
df5531_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		df5531_run();
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df5531_tapcost_mod = {
	"df5531_tapcost",
	df5531_load,
	NULL
};

DECLARE_MODULE(df5531_tapcost, df5531_tapcost_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(df5531_tapcost, 1);
