#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/devops.h>
#include <sys/conf.h>
#include <sys/cpuvar.h>
#include <sys/kobj.h>
#include <sys/spa.h>
#include <sys/arc.h>
#include <sys/vfs.h>
#include <sys/dnlc.h>

static dev_info_t *arcflushdevi;

#define	FTAG ((char *)(uintptr_t)__func__)

static int arcflushioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp);
static int arcflushclose(dev_t dev, int flag, int otyp, cred_t *crepd);
static int arcflushopen(dev_t *devp, int flag, int otyp, cred_t *credp);

static struct cb_ops    arcflush_cb_ops = {
	arcflushopen,		/* open */
	arcflushclose,		/* close */
	nodev,			/* strategy */
	nodev,			/* print */
	nodev,			/* dump */
	nodev,			/* read */
	nodev,			/* write */
	arcflushioctl,		/* ioctl */
	nodev,			/* devmap */
	nodev,			/* mmap */
	nodev,			/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* prop_op */
	(struct streamtab *)0,	/* streamtab */
	D_NEW | D_64BIT | D_MP,	/* flags */
	CB_REV,
	nodev,
	nodev
};

static int arcflushattach(dev_info_t *, ddi_attach_cmd_t);
static int arcflushdetach(dev_info_t *, ddi_detach_cmd_t);
static int arcflushinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);

static struct dev_ops arcflush_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* refcnt */
	arcflushinfo,		/* info */
	nulldev,		/* identify */
	nulldev,		/* probe */
	arcflushattach,		/* attach */
	arcflushdetach,		/* detach */
	nodev,			/* reset */
	&arcflush_cb_ops,	/* driver operations */
	(struct bus_ops *)NULL, /* bus operations */
	NULL			/* power */
};

static struct modldrv modldrv = {
	&mod_driverops,		/* type of module - a driver */
	"arcflush driver",
	&arcflush_ops,
};

static struct modlinkage modlinkage = {
	MODREV_1,
	{ (void *)&modldrv, NULL }
};

int
_init()
{
	int error = mod_install(&modlinkage);
	return (error);
}

int
_fini()
{
	int error;

	error = mod_remove(&modlinkage);
	return (error);
}

int
_info(struct modinfo *modinfop)
{
	int error;

	error = mod_info(&modlinkage, modinfop);
	return error;
}

static int
arcflushclose(dev_t dev, int flag, int otyp, cred_t *crepd)
{
	if (otyp != OTYP_CHR)
		return (EINVAL);

	return (0);
}

static int
arcflushopen(dev_t *devp, int flag, int otyp, cred_t *credp)
{
	if (otyp != OTYP_CHR)
		return (EINVAL);
	if (drv_priv(credp) != 0)
		return (EPERM);

	return (0);
}

static int
arcflushioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp)
{
	spa_t *spa;
	int ret;
	char name[MAXPATHLEN + 1];
	vfs_t *vfsp;

	if (ddi_copyin((void *)arg, name, MAXPATHLEN, mode) != 0)
		return EFAULT;
	name[MAXPATHLEN] = 0;

	switch (cmd) {
	case 0xf10053:
		ret = spa_open(name, &spa, FTAG);
		if (ret != 0)
			return (ret);
		arc_flush(spa, FALSE);
		spa_close(spa, FTAG);
		break;
	case 0xf10054:
		vfsp = vfs_mntpoint2vfsp(name);
		if (vfsp == NULL)
			return (EINVAL);
		dnlc_purge_vfsp(vfsp, 0);
		break;
	default:
		return ENOTTY;
	}

	return (0);
}

/*ARGSUSED*/
static int
arcflushattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	if (cmd != DDI_ATTACH) {
		return (DDI_FAILURE);
	}

	if (ddi_create_minor_node(devi, "arcflush", S_IFCHR, 0, DDI_PSEUDO, 0)
			== DDI_FAILURE) {
		cmn_err(CE_NOTE, "arcflushattach fail: "
			"ddi_create_minor_node\n");
		return (DDI_FAILURE);
	}
	ddi_report_dev(devi);

	arcflushdevi = devi;

	return (DDI_SUCCESS);
}

static int
arcflushdetach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	if (cmd != DDI_DETACH) {
		return (DDI_FAILURE);
	}

	arcflushdevi = NULL;

	ddi_prop_remove_all(devi);
	ddi_remove_minor_node(devi, NULL);

	return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
arcflushinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	int error;
	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		*result = (void *)arcflushdevi;
		error = DDI_SUCCESS;
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}