#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/socket.h>

#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>

/* from sys/netgraph/fec/ng_fec.h */
#define NGM_FEC_COOKIE			983566799
#define NGM_FEC_ADD_IFACE		0
#define NGM_FEC_DEL_IFACE		1

/*
 * fecdrive: drive the (patched) ng_fec node from a normal thread context
 * (kldload/sysctl), because the stock csock path runs netgraph messages
 * in netisr where ifnet_lock() is illegal (KASSERT panic).
 *
 * Commands (sysctl -n fecdrive.cmd="<cmd>"):
 *   create       - ng_make_node("fec") -> f_node (auto-named fec0)
 *   create2      - same, second node (fec1) stored in g_node
 *   add <ifname> - NGM_FEC_ADD_IFACE to f_node
 *   del <ifname> - NGM_FEC_DEL_IFACE to f_node
 *   addg <if>    - ADD_IFACE to g_node
 *   shutdown     - ng_rmnode(f_node)
 *   echo         - create an echo node (churns ng_node chunks)
 *   status       - print f_node/g_node pointers
 */

static node_p f_node;
static node_p g_node;

static int
fecdrive_send(node_p node, int cmd, const char *ifname)
{
	struct ng_mesg *msg;
	int len, error;

	len = (ifname != NULL) ? strlen(ifname) + 1 : 0;
	NG_MKMESSAGE(msg, NGM_FEC_COOKIE, cmd, len, M_WAITOK);
	if (msg == NULL)
		return (ENOMEM);
	if (ifname != NULL)
		strlcpy((char *)msg->data, ifname, len);
	error = ng_send_msg(node, msg, ".", NULL);
	return (error);
}

static int
fecdrive_sysctl(SYSCTL_HANDLER_ARGS)
{
	char cmd[64];
	int error;

	strlcpy(cmd, "", sizeof(cmd));
	error = sysctl_handle_string(oidp, cmd, sizeof(cmd), req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	kprintf("FECDRIVE: cmd='%s'\n", cmd);
	if (strcmp(cmd, "create") == 0 || strcmp(cmd, "create2") == 0) {
		node_p *slot = (strcmp(cmd, "create2") == 0) ? &g_node : &f_node;
		if (*slot != NULL) {
			kprintf("FECDRIVE: slot busy\n");
			return (EBUSY);
		}
		error = ng_make_node("fec", slot);
		kprintf("FECDRIVE: ng_make_node(fec) = %d node=%p\n",
		    error, *slot);
	} else if (strncmp(cmd, "add ", 4) == 0) {
		error = fecdrive_send(f_node, NGM_FEC_ADD_IFACE, cmd + 4);
		kprintf("FECDRIVE: add '%s' = %d\n", cmd + 4, error);
	} else if (strncmp(cmd, "addg ", 5) == 0) {
		error = fecdrive_send(g_node, NGM_FEC_ADD_IFACE, cmd + 5);
		kprintf("FECDRIVE: addg '%s' = %d\n", cmd + 5, error);
	} else if (strncmp(cmd, "del ", 4) == 0) {
		error = fecdrive_send(f_node, NGM_FEC_DEL_IFACE, cmd + 4);
		kprintf("FECDRIVE: del '%s' = %d\n", cmd + 4, error);
	} else if (strcmp(cmd, "shutdown") == 0) {
		if (f_node == NULL) {
			kprintf("FECDRIVE: no node\n");
			return (ENOENT);
		}
		ng_rmnode(f_node);
		f_node = NULL;
		kprintf("FECDRIVE: rmnode done\n");
	} else if (strcmp(cmd, "shutdowng") == 0) {
		if (g_node == NULL) {
			kprintf("FECDRIVE: no g node\n");
			return (ENOENT);
		}
		ng_rmnode(g_node);
		g_node = NULL;
		kprintf("FECDRIVE: rmnode g done\n");
	} else if (strcmp(cmd, "echo") == 0) {
		node_p e;
		error = ng_make_node("echo", &e);
		kprintf("FECDRIVE: ng_make_node(echo) = %d node=%p\n",
		    error, e);
	} else if (strcmp(cmd, "status") == 0) {
		kprintf("FECDRIVE: f_node=%p g_node=%p\n", f_node, g_node);
	} else {
		kprintf("FECDRIVE: unknown cmd\n");
		return (EINVAL);
	}
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, fecdrive, CTLTYPE_STRING | CTLFLAG_RW,
    0, 0, fecdrive_sysctl, "A", "drive ng_fec from thread context");

static int
fecdrive_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("fecdrive: loaded\n");
		return (0);
	default:
		return (0);
	}
}

static moduledata_t fecdrive_mod = { "fecdrive", fecdrive_load, NULL };
DECLARE_MODULE(fecdrive, fecdrive_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
