/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */


/*
 * UGEN: USB Generic Driver
 *
 * The "Universal Generic Driver"  (UGEN) for USB devices provides interfaces
 * to  talk to	USB  devices.  This is	very  useful for  Point of Sale sale
 * devices and other simple  devices like  USB	scanner, USB palm  pilot.
 * The UGEN provides a system call interface to USB  devices  enabling
 * a USB device vendor to write an application for their
 * device instead of  writing a driver. This facilitates the vendor to write
 * device management s/w quickly in userland.
 *
 * UGEN supports read/write/poll entry points. An application can be written
 * using  read/write/aioread/aiowrite/poll  system calls to communicate
 * with the device.
 */
#include <sys/usb/usba/usbai_version.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_ugen.h>
#include <sys/usb/clients/ugen/ugend.h>

/* Global variables */
static void	*ugen_skel_statep;

/* Prototypes declarations for the entry points */
static int	ugen_skel_getinfo(dev_info_t *, ddi_info_cmd_t,
						void *, void **);
static int	ugen_skel_open(dev_t *, int, int, cred_t *);
static int	ugen_skel_close(dev_t, int, int, cred_t *);
static int	ugen_skel_attach(dev_info_t *, ddi_attach_cmd_t);
static int	ugen_skel_detach(dev_info_t *, ddi_detach_cmd_t);
static int	ugen_skel_power(dev_info_t *, int, int);
static int	ugen_skel_read(dev_t, struct uio *, cred_t *);
static int	ugen_skel_write(dev_t, struct uio *, cred_t *);
static int	ugen_skel_poll(dev_t, short, int,  short *,
						struct pollhead **);

static int	ugen_skel_disconnect_ev_cb(dev_info_t *);
static int	ugen_skel_reconnect_ev_cb(dev_info_t *);

/* event support */
static usb_event_t ugen_skel_events = {
	ugen_skel_disconnect_ev_cb,
	ugen_skel_reconnect_ev_cb,
	NULL, NULL
};

/* Driver cb_ops structure */
static struct cb_ops ugen_skel_cb_ops = {
	ugen_skel_open,			/* open */
	ugen_skel_close,		/* close */
	nodev,				/* strategy */
	nodev,				/* print */
	nodev,				/* dump */
	ugen_skel_read,			/* read */
	ugen_skel_write,		/* write */
	nodev,				/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	ugen_skel_poll,			/* poll */
	ddi_prop_op,			/* cb_prop_op */
	0,				/* streamtab  */
	D_MP,				/* Driver compatibility flag */
	CB_REV,				/* revision */
	nodev,				/* aread */
	nodev				/* awrite */
};

/*
 * Modloading support
 *	driver dev_ops structure
 */
static struct dev_ops ugen_skel_ops = {
	DEVO_REV,			/* devo_rev, */
	0,				/* refct  */
	ugen_skel_getinfo,		/* info */
	nulldev,			/* indetify */
	nulldev,			/* probe */
	ugen_skel_attach,		/* attach */
	ugen_skel_detach,		/* detach */
	nodev,				/* reset */
	&ugen_skel_cb_ops,		/* driver operations */
	NULL,				/* bus operations */
	ugen_skel_power,		/* power */
	ddi_quiesce_not_needed,	/* devo_quiesce */
};

static struct modldrv modldrv = {
	&mod_driverops,			/* Module type */
	"USB Generic driver",	/* Name of the module. */
	&ugen_skel_ops,			/* driver ops */
};

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


int
_init()
{
	int	rval;

	if ((rval = ddi_soft_state_init(&ugen_skel_statep,
	    sizeof (ugen_skel_state_t), UGEN_INSTANCES)) != 0) {

		return (rval);
	}

	if ((rval = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&ugen_skel_statep);

		return (rval);
	}

	return (rval);
}


int
_fini()
{
	int rval;

	if ((rval = mod_remove(&modlinkage)) != 0) {

		return (rval);
	}
	ddi_soft_state_fini(&ugen_skel_statep);

	return (rval);
}


int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


/*ARGSUSED*/
static int
ugen_skel_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
    void **result)
{
	int		rval = DDI_FAILURE;
	int		instance =
	    UGEN_MINOR_TO_INSTANCE(getminor((dev_t)arg));
	ugen_skel_state_t *ugen_skelp;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		ugen_skelp = ddi_get_soft_state(ugen_skel_statep, instance);
		if (ugen_skelp != NULL) {
			*result = ugen_skelp->ugen_skel_dip;
			if (*result != NULL) {
				rval = DDI_SUCCESS;
			}
		} else {
			*result = NULL;
		}

		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(uintptr_t)instance;
		rval = DDI_SUCCESS;

		break;
	default:

		break;
	}

	return (rval);
}


/*
 * ugen_skel_attach()
 */
static int
ugen_skel_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	ugen_skel_state_t	*ugen_skelp;
	int			instance;	/* Driver instance number */
	int			rval;
	usb_ugen_info_t		usb_ugen_info;

	/* Get instance number */
	instance = ddi_get_instance(dip);

	switch (cmd) {
	case DDI_ATTACH:

		break;
	case DDI_RESUME:
		ugen_skelp = ddi_get_soft_state(ugen_skel_statep, instance);
		if (ugen_skelp == NULL) {

			return (DDI_FAILURE);
		}

		rval = usb_ugen_attach(ugen_skelp->ugen_skel_hdl, cmd);

		return (rval == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
	default:

		return (DDI_FAILURE);
	}

	if (ddi_soft_state_zalloc(ugen_skel_statep, instance) ==
	    DDI_SUCCESS) {
		ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
		    instance);
	}
	if (ugen_skelp == NULL) {

		return (DDI_FAILURE);
	}

	if ((rval = usb_client_attach(dip, USBDRV_VERSION, 0)) !=
	    USB_SUCCESS) {

		goto fail;
	}

	ugen_skelp->ugen_skel_dip	= dip;
	ugen_skelp->ugen_skel_instance	= instance;

	/* get a ugen handle */
	bzero(&usb_ugen_info, sizeof (usb_ugen_info));
	usb_ugen_info.usb_ugen_flags =
	    USB_UGEN_ENABLE_PM | USB_UGEN_REMOVE_CHILDREN;
	usb_ugen_info.usb_ugen_minor_node_ugen_bits_mask =
	    (dev_t)UGEN_MINOR_UGEN_BITS_MASK;
	usb_ugen_info.usb_ugen_minor_node_instance_mask =
	    (dev_t)~UGEN_MINOR_UGEN_BITS_MASK;
	ugen_skelp->ugen_skel_hdl = usb_ugen_get_hdl(dip,
	    &usb_ugen_info);

	if (usb_ugen_attach(ugen_skelp->ugen_skel_hdl, cmd) != USB_SUCCESS) {

		goto fail;
	}

	/* register for hotplug events */
	if (usb_register_event_cbs(dip, &ugen_skel_events, 0) != USB_SUCCESS) {

		goto fail;
	}

	ddi_report_dev(dip);

	return (DDI_SUCCESS);

fail:
	if (ugen_skelp) {
		usb_unregister_event_cbs(dip, &ugen_skel_events);
		usb_ugen_release_hdl(ugen_skelp->
		    ugen_skel_hdl);
		ddi_soft_state_free(ugen_skel_statep,
		    ugen_skelp->ugen_skel_instance);
		usb_client_detach(dip, NULL);
	}

	return (DDI_FAILURE);
}


/*
 * ugen_skel_detach()
 */
static int
ugen_skel_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		rval = USB_FAILURE;
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    ddi_get_instance(dip));

	if (ugen_skelp) {
		switch (cmd) {
		case DDI_DETACH:
			rval = usb_ugen_detach(ugen_skelp->ugen_skel_hdl, cmd);
			if (rval == USB_SUCCESS) {
				usb_unregister_event_cbs(dip,
				    &ugen_skel_events);
				usb_ugen_release_hdl(ugen_skelp->
				    ugen_skel_hdl);
				ddi_soft_state_free(ugen_skel_statep,
				    ugen_skelp->ugen_skel_instance);
				usb_client_detach(dip, NULL);
			}

			break;
		case DDI_SUSPEND:
			rval = usb_ugen_detach(ugen_skelp->ugen_skel_hdl, cmd);

			break;
		default:

			break;
		}
	}

	return (rval == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
}


/*
 * ugen_skel_disconnect_ev_cb:
 */
static int
ugen_skel_disconnect_ev_cb(dev_info_t *dip)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    ddi_get_instance(dip));

	return (usb_ugen_disconnect_ev_cb(ugen_skelp->ugen_skel_hdl));
}


/*
 * ugen_skel_reconnect_ev_cb:
 */
static int
ugen_skel_reconnect_ev_cb(dev_info_t *dip)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    ddi_get_instance(dip));

	return (usb_ugen_reconnect_ev_cb(ugen_skelp->ugen_skel_hdl));
}


/*
 * ugen_skel_open:
 */
static int
ugen_skel_open(dev_t *devp, int flag, int sflag, cred_t *cr)
{
	ugen_skel_state_t *ugen_skelp;

	if ((ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    UGEN_MINOR_TO_INSTANCE(getminor(*devp)))) == NULL) {
		/* deferred detach */

		return (ENXIO);
	}

	return (usb_ugen_open(ugen_skelp->ugen_skel_hdl, devp, flag,
	    sflag, cr));
}


/*
 * ugen_skel_close()
 */
static int
ugen_skel_close(dev_t dev, int flag, int otype, cred_t *cr)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    UGEN_MINOR_TO_INSTANCE(getminor(dev)));

	return (usb_ugen_close(ugen_skelp->ugen_skel_hdl, dev, flag,
	    otype, cr));
}


/*
 * ugen_skel_read/write()
 */
static int
ugen_skel_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    UGEN_MINOR_TO_INSTANCE(getminor(dev)));
	if (ugen_skelp == NULL) {

		return (ENXIO);
	}

	return (usb_ugen_read(ugen_skelp->ugen_skel_hdl, dev,
	    uiop, credp));
}


static int
ugen_skel_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    UGEN_MINOR_TO_INSTANCE(getminor(dev)));
	if (ugen_skelp == NULL) {

		return (ENXIO);
	}
	return (usb_ugen_write(ugen_skelp->ugen_skel_hdl,
	    dev, uiop, credp));
}


/*
 * ugen_skel_poll
 */
static int
ugen_skel_poll(dev_t dev, short events,
    int anyyet,  short *reventsp, struct pollhead **phpp)
{
	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    UGEN_MINOR_TO_INSTANCE(getminor(dev)));
	if (ugen_skelp == NULL) {

		return (ENXIO);
	}

	return (usb_ugen_poll(ugen_skelp->ugen_skel_hdl, dev, events,
	    anyyet, reventsp, phpp));
}


/*
 * ugen_skel_power:
 *	PM entry point
 */
static int
ugen_skel_power(dev_info_t *dip, int comp, int level)
{
	int rval;

	ugen_skel_state_t *ugen_skelp = ddi_get_soft_state(ugen_skel_statep,
	    ddi_get_instance(dip));
	if (ugen_skelp == NULL) {

		return (DDI_FAILURE);
	}
	rval = usb_ugen_power(ugen_skelp->ugen_skel_hdl, comp, level);

	return (rval == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
}