/*
 * DF-5763 rig — drives the VERBATIM production sys/netbt/bt_proto.c
 * (linked unmodified into this KLD by build.sh) through the DragonFly
 * socket layer to interrogate the dom_dispose wiring:
 *
 *   hw.btdisp.info       (RD)  wiring facts: domain name, dom_dispose
 *                               pointer, btsw[0] flags, pool pointers
 *   hw.btdisp.arm        (RW)  writing 1 sets btsw[0].pr_flags |= PR_RIGHTS
 *                               — the ONE-BIT precondition that DragonFly's
 *                               sorflush()/so-receive PR_RIGHTS gate requires
 *                               before it will ever call dom_dispose
 *                               (sys/kern/uipc_socket.c:1986-1987,1609-1611).
 *                               Production btsw[] never sets PR_RIGHTS, so
 *                               production netbt_dispose is DEAD CODE.
 *
 * CONTROL: sockets + shutdown WITHOUT arm must never disturb the pools.
 * ARMED:   the next sorflush runs production netbt_dispose() ->
 *          zdestroy() of the three GLOBAL vm_zones (vm_zone.c:516-544,
 *          kfree(z) at :543); any repeat destroys freed zones.
 *
 * netbt_dispose is static in bt_proto.c; its address is captured from the
 * module symbol table by build.sh (nm) and compared at runtime against
 * btdomain.dom_dispose printed by hw.btdisp.info.
 */
#include <sys/param.h>
#include <sys/domain.h>
#include <sys/kernel.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/socketvar2.h>
#include <sys/msgport2.h>
#include <sys/libkern.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/rfcomm.h>
#include <netbt/sco.h>

extern struct domain btdomain;		/* production bt_proto.c */
extern struct protosw btsw[];		/* production bt_proto.c */
extern struct pr_usrreqs hci_usrreqs;	/* production bt_proto.c:40 */

static int armed;

/*
 * minimal pru_attach so socreate() succeeds (uipc_socket.c:217 requires
 * pru_attach != NULL).  Real hci_sattach lives in hci_socket.c; we only
 * need the socket object for sorflush().
 */
static void
btdisp_stub_attach(union netmsg *msg)
{
	struct socket *so = msg->attach.base.nm_so;

	lwkt_replymsg(&msg->attach.base.lmsg, soreserve(so, 4096, 4096, NULL));
}

static int
btdisp_info(SYSCTL_HANDLER_ARGS)
{
	char buf[256];

	ksnprintf(buf, sizeof(buf),
	    "domain=%s fam=%d dom_dispose=%p btsw[0].pr_flags=0x%08x "
	    "PR_RIGHTS=%d pools: pdu=%p req=%p credit=%p",
	    btdomain.dom_name, btdomain.dom_family,
	    btdomain.dom_dispose,
	    btsw[0].pr_flags, !!(btsw[0].pr_flags & PR_RIGHTS),
	    l2cap_pdu_pool, l2cap_req_pool, rfcomm_credit_pool);

	return (sysctl_handle_string(oidp, buf, sizeof(buf), req));
}

static int
btdisp_arm(SYSCTL_HANDLER_ARGS)
{
	int error, val = armed;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val && !armed) {
		kprintf("btdisp: ARMING btsw[0].pr_flags 0x%08x -> 0x%08x "
		    "(set PR_RIGHTS; sorflush will now call dom_dispose)\n",
		    btsw[0].pr_flags, btsw[0].pr_flags | PR_RIGHTS);
		btsw[0].pr_flags |= PR_RIGHTS;
		armed = 1;
	}
	return (0);
}

SYSCTL_NODE(_hw, OID_AUTO, btdisp, CTLFLAG_RW, 0, "DF-5763 rig");
SYSCTL_PROC(_hw_btdisp, OID_AUTO, info,
    CTLTYPE_STRING | CTLFLAG_RD, NULL, 0, btdisp_info, "A",
    "dom_dispose wiring facts");
SYSCTL_PROC(_hw_btdisp, OID_AUTO, arm,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, btdisp_arm, "I",
    "set PR_RIGHTS on btsw[0]");

static void
btdisp_init(void *dummy __unused)
{
	/* make raw HCI sockets creatable through the production protosw */
	hci_usrreqs.pru_attach = btdisp_stub_attach;
}
SYSINIT(btdisp_init, SI_BOOT2_KLD, SI_ORDER_ANY + 1, btdisp_init, NULL);
