/*
 * harness.c -- KLD harness for the pass-2 audit of sys/netbt/hci_socket.c
 * (finding DF-5740: hci_drop() TX-completion callback races socket
 * teardown / the socket send buffer across protocol ports).
 *
 * The production files hci_socket.c / hci_event.c / hci_link.c /
 * hci_misc.c / hci_unit.c / bt_input.c / bt_proto.c are compiled into
 * this module VERBATIM (copied from /usr/src/sys/netbt by build.sh,
 * md5s recorded).  Compiling bt_proto.c registers the real AF_BLUETOOTH
 * domain, so an ordinary userland program can
 *
 *	socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)
 *
 * and reach the VERBATIM production hci_sattach/hci_ssend/hci_sdetach.
 *
 * This harness plays the role the controller driver (ubt(4)) plays in
 * production: it attaches a unit via production hci_attach() with
 * hci_num_cmd_pkts = 0 (so every raw-socket command lands on
 * unit->hci_cmdwait, hci_socket.c:531) and injects Command_Complete
 * events through the production entry point hci_input_event() from a
 * kernel thread PINNED TO CPU 3:
 *
 *	events:   hci_input_event() -> netisr_queue() on CPU 3
 *		-> btintr() on CPU3's netisr port
 *		-> hci_intr() -> hci_event_command_compl()
 *		-> IF_DEQUEUE(&unit->hci_cmdwait)      [hci_event.c:354]
 *		-> hci_output_cmd()                    [hci_unit.c:490]
 *		-> hci_mtap() ... hci_drop(so)         [hci_unit.c:505-507]
 *
 * while the socket operations of the userland trigger run (as for every
 * HCI socket, uipc_socket.c:259) on CPU0's netisr port:
 *
 *	send(2):  hci_ssend -> hci_send -> sbappendrecord(&so->so_snd) +
 *	          IF_ENQUEUE(&unit->hci_cmdwait) + M_SETCTX(m, so)
 *	close(2): hci_sdetach -> hci_cmdwait_flush(so) [walks cmdwait
 *	          clearing M_SETCTX] -> so_pcb = NULL -> sofree(so)
 *	          [-> ssb_release -> sbflush FREES so_snd records ->
 *	          sodealloc FREES the socket]
 *
 * The race: any command mbuf dequeued by CPU3 *before* the flush walk
 * reaches it is invisible to hci_cmdwait_flush; CPU3 then executes
 * hci_drop(so) -- sbdroprecord(&so->so_snd.sb) + sowwakeup(so) --
 * against a socket that CPU0 has already freed (or against a live
 * socket whose sockbuf it mutates with no common lock).
 *
 * Triggers (sysctl, as root):
 *
 *   hw.hsh_setup = 1     attach fake unit (BTF_UP, bdaddr
 *                         11:22:33:44:55:66, num_cmd_pkts = 0) and
 *                         start the CPU3 injector thread (idle).
 *
 *   hw.hsh_fire = N      injector: N rounds, each queueing BURST
 *                         Command_Complete events (each event drains
 *                         exactly one cmdwait entry -> one hci_drop()).
 *
 *   hw.hsh_stop = 1      ask the injector to stop after the current
 *                         round.
 *
 *   hw.hsh_state = 1     dump unit state (cmdwait len, eventq len,
 *                         commands handed to the fake controller).
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/types.h>
#include <sys/endian.h>

#include <net/netisr.h>

#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/rfcomm.h>
#include <netbt/sco.h>

#include "stub.h"

/* ------------------------------------------------------------------ */
/* fake controller driver: the role ubt(4) plays in production        */

static int	 fake_enable(device_t);
static void	 fake_disable(device_t);
static void	 fake_output_cmd(device_t, struct mbuf *);
static void	 fake_output_acl(device_t, struct mbuf *);
static void	 fake_output_sco(device_t, struct mbuf *);
static void	 fake_get_stats(device_t, struct bt_stats *, int);

static const struct hci_if fake_hci_if = {
	fake_enable,
	fake_disable,
	fake_output_cmd,
	fake_output_acl,
	fake_output_sco,
	fake_get_stats,
};

static volatile long fake_cmds_sent;

/*
 * Fake device_t: production paths call device_get_nameunit(hci_dev)
 * inside kprintf("%s"...) -- the blob must be MAPPED memory with a
 * valid string pointer at +0x58 (same trick as the DF-5739 rig).
 */
static char *fake_dev;
static const char fake_dev_name[] = "hcih0";

#define FAKE_DEV_NAMEUNIT_OFF	0x58

static device_t
fake_dev_alloc(void)
{
	fake_dev = kmalloc(1024, M_BLUETOOTH, M_NOWAIT | M_ZERO);
	KKASSERT(fake_dev != NULL);
	*(const char **)(fake_dev + FAKE_DEV_NAMEUNIT_OFF) = fake_dev_name;
	return (device_t)fake_dev;
}

static int
fake_enable(device_t dev)
{
	return 0;
}

static void
fake_disable(device_t dev)
{
}

/*
 * Called from hci_output_cmd() AFTER hci_drop() has already run -- by
 * the time we get the mbuf, the racy callback already happened (or is
 * about to, on the other CPU).
 */
static void
fake_output_cmd(device_t dev, struct mbuf *m)
{
	atomic_add_long(&fake_cmds_sent, 1);
	m_freem(m);
}

static void
fake_output_acl(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_output_sco(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_get_stats(device_t dev, struct bt_stats *s, int arg)
{
	memset(s, 0, sizeof(*s));
}

/* ------------------------------------------------------------------ */

static struct hci_unit *g_unit;
static struct thread *inj_td;

/* bdaddr of the fake unit: 66:55:44:33:22:11 (byte order as stored) */
static const bdaddr_t test_bdaddr = { .b = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 } };

#define INJ_CPU		3	/* != CPU0 where all HCI socket ops run */

static volatile int inj_run;	/* 1 = keep injecting */
static volatile long inj_events;
static volatile long inj_rounds;

/*
 * Command_Complete event:
 *   [0] 0x04  HCI_EVENT_PKT
 *   [1] 0x0E  HCI_EVENT_COMMAND_COMPL
 *   [2] len   = 4
 *   [3] num_cmd_pkts = 1   (drains exactly ONE queued command)
 *   [4..5] opcode = 0x0000 (unknown -> default: no side effects)
 *   [6] status = 0x00      (read by the m_copydata(rp) in the handler)
 */
static const uint8_t compl_pkt[] = { 0x04, 0x0e, 0x04, 0x01, 0x00, 0x00, 0x00 };

#define INJ_BURST	8

static void
inject_compl(void)
{
	struct mbuf *m;

	m = m_gethdr(M_NOWAIT, MT_DATA);
	if (m == NULL)
		return;		/* allocation failure: skip, do not pass NULL */
	if (m_copyback2(m, 0, sizeof(compl_pkt), compl_pkt, M_NOWAIT) != 0) {
		m_freem(m);
		return;
	}

	hci_input_event(g_unit, m);	/* production entry point */
	atomic_add_long(&inj_events, 1);
}

/*
 * Injector thread body -- pinned to INJ_CPU.  Every hci_input_event()
 * here does netisr_queue(NETISR_BLUETOOTH) which lands on THIS cpu's
 * netisr port, so btintr() (and hci_drop()) run on CPU3 while the
 * socket ops of the trigger run on CPU0's netisr port.
 *
 * PACED: only inject while commands are pending on cmdwait, so the
 * completion rate tracks the socket send rate 1:1 (a real controller
 * behaves the same way) and the tap copies stay bounded.
 */
static void
inj_thread(void *arg)
{
	int i;

	kprintf("[harness] injector thread %p running on cpu %d\n",
	    curthread, mycpuid);

	while (!inj_run)
		tsleep(&inj_run, 0, "hshid", hz);

	for (;;) {
		if (!inj_run)
			break;
		if (IF_QEMPTY(&g_unit->hci_cmdwait)) {
			tsleep(&inj_rounds, 0, "hshw", 1);
			continue;
		}
		for (i = 0; i < INJ_BURST; i++)
			inject_compl();
		atomic_add_long(&inj_rounds, 1);
		tsleep(&inj_rounds, 0, "hshij", 1);
	}

	kprintf("[harness] injector stopping: %ld rounds, %ld events\n",
	    inj_rounds, inj_events);
	wakeup(&inj_td);
	lwkt_exit();
}

/* ------------------------------------------------------------------ */
/* sysctls                                                             */

static int
hsh_setup(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	if (g_unit != NULL) {
		kprintf("[harness] unit already attached\n");
		return 0;
	}

	kprintf("[harness] === setup: attach unit bdaddr "
	    "66:55:44:33:22:11, num_cmd_pkts=0 ===\n");

	g_unit = hci_attach(&fake_hci_if, fake_dev_alloc(), 0);
	KKASSERT(g_unit != NULL);

	/* the state a real controller reaches after RESET + READ_BDADDR */
	g_unit->hci_flags |= BTF_UP | BTF_RUNNING;
	g_unit->hci_num_cmd_pkts = 0;	/* every socket cmd queues to cmdwait */
	g_unit->hci_num_acl_pkts = 8;
	bdaddr_copy(&g_unit->hci_bdaddr, &test_bdaddr);

	/* vm_zones, in case the domain's dom_init did not run at kldload */
	if (l2cap_pdu_pool == NULL)
		l2cap_pdu_pool = zinit("l2cap_pdu",
		    sizeof(struct l2cap_pdu), 1, ZONE_DESTROYABLE);
	if (l2cap_req_pool == NULL)
		l2cap_req_pool = zinit("l2cap_req",
		    sizeof(struct l2cap_req), 1, ZONE_DESTROYABLE);
	if (rfcomm_credit_pool == NULL)
		rfcomm_credit_pool = zinit("rfcomm_credit",
		    sizeof(struct rfcomm_credit), 1, ZONE_DESTROYABLE);

	/* SCO listener so production hci_sco_newconn() has its list */
	stub_sco_init();

	/* CPU3 injector thread */
	inj_run = 0;
	lwkt_create(inj_thread, NULL, &inj_td, NULL, 0, INJ_CPU, "hsh_inj");

	kprintf("[harness] unit %p attached; injector on cpu %d; "
	    "AF_BLUETOOTH sockets live on cpu0's netisr port\n",
	    g_unit, INJ_CPU);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hsh_setup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hsh_setup, "I", "attach fake HCI unit + CPU3 injector thread");

static int
hsh_fire(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v <= 0)
		return err;

	KKASSERT(g_unit != NULL);

	kprintf("[harness] === fire: %d injector rounds "
	    "(%d events each) from cpu %d ===\n",
	    v, INJ_BURST, INJ_CPU);

	inj_run = 1;
	wakeup(&inj_run);
	tsleep(&inj_rounds, 0, "hshfd", 1);	/* let it start */

	while (v-- > 0) {
		if (inj_run == 0)
			break;
		tsleep(&g_unit, 0, "hshf", hz / 20);
	}

	kprintf("[harness] fire done: rounds=%ld events=%ld "
	    "cmds_to_driver=%ld\n", inj_rounds, inj_events, fake_cmds_sent);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hsh_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hsh_fire, "I", "N injector rounds of Command_Complete events");

static int
hsh_stop(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	inj_run = 0;
	wakeup(&inj_run);
	kprintf("[harness] stop requested\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hsh_stop, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hsh_stop, "I", "stop the injector thread");

static int
hsh_state(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);
	kprintf("[harness] state: cmdwait len=%d maxlen=%d eventqlen=%d "
	    "eventq len=%d\n",
	    g_unit->hci_cmdwait.ifq_len, g_unit->hci_cmdwait.ifq_maxlen,
	    g_unit->hci_eventqlen, g_unit->hci_eventq.ifq_len);
	kprintf("[harness] state: num_cmd_pkts=%d injected=%ld "
	    "cmds_to_driver=%ld rounds=%ld\n",
	    g_unit->hci_num_cmd_pkts, inj_events, fake_cmds_sent, inj_rounds);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hsh_state, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hsh_state, "I", "dump harness/unit state");

/* ------------------------------------------------------------------ */

static int
harness_mode(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		/*
		 * No netisr_register here: bt_proto.c is compiled verbatim
		 * and its SYSINIT(netbt_setup) registered NETISR_BLUETOOTH
		 * -> btintr (bt_input.c) when the module was loaded.
		 */
		kprintf("[harness] hci_sock_harness loaded: hsh_setup, "
		    "hsh_fire, hsh_stop, hsh_state\n");
		break;
	case MOD_UNLOAD:
		return EBUSY;
	default:
		break;
	}
	return 0;
}

static moduledata_t harness_moddata = {
	"hci_sock_harness",
	harness_mode,
	NULL
};

DECLARE_MODULE(hci_sock_harness, harness_moddata, SI_SUB_DRIVERS,
    SI_ORDER_MIDDLE);
