root/usr/src/uts/sun4v/io/mdesc.c
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * sun4v machine description driver
 */

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include <sys/mdesc.h>
#include <sys/mach_descrip.h>

#define MDESC_NAME      "mdesc"

/*
 * Operational state flags
 */

#define MDESC_GOT_HANDLE        0x10            /* Got mdesc handle */
#define MDESC_BUSY              0x20            /* Device is busy */

static void             *mdesc_state_head;
static vmem_t           *mdesc_minor;
static uint16_t         mdesc_max_opens = 256;
static uint16_t         mdesc_opens = 0;
static int              mdesc_attached = 0;
static dev_info_t       *mdesc_devi;
static kmutex_t         mdesc_lock;

struct mdesc_state {
        int             instance;
        dev_t           dev;
        kmutex_t        lock;
        kcondvar_t      cv;
        size_t          mdesc_len;
        md_t            *mdesc;
        int             flags;
};

typedef struct mdesc_state mdesc_state_t;

static int mdesc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int mdesc_attach(dev_info_t *, ddi_attach_cmd_t);
static int mdesc_detach(dev_info_t *, ddi_detach_cmd_t);
static int mdesc_open(dev_t *, int, int, cred_t *);
static int mdesc_close(dev_t, int, int, cred_t *);
static int mdesc_read(dev_t, struct uio *, cred_t *);
static int mdesc_write(dev_t, struct uio *, cred_t *);
static int mdesc_rw(dev_t, struct uio *, enum uio_rw);
static int mdesc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static struct cb_ops mdesc_cb_ops = {
        mdesc_open,             /* cb_open */
        mdesc_close,            /* cb_close */
        nodev,                  /* cb_strategy */
        nodev,                  /* cb_print */
        nodev,                  /* cb_dump */
        mdesc_read,             /* cb_read */
        nodev,                  /* cb_write */
        mdesc_ioctl,            /* cb_ioctl */
        nodev,                  /* cb_devmap */
        nodev,                  /* cb_mmap */
        nodev,                  /* cb_segmap */
        nochpoll,               /* cb_chpoll */
        ddi_prop_op,            /* cb_prop_op */
        (struct streamtab *)NULL, /* cb_str */
        D_MP | D_64BIT,         /* cb_flag */
        CB_REV,                 /* cb_rev */
        nodev,                  /* cb_aread */
        nodev                   /* cb_awrite */
};

static struct dev_ops mdesc_dev_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* devo_refcnt */
        mdesc_getinfo,          /* devo_getinfo */
        nulldev,                /* devo_identify */
        nulldev,                /* devo_probe */
        mdesc_attach,           /* devo_attach */
        mdesc_detach,           /* devo_detach */
        nodev,                  /* devo_reset */
        &mdesc_cb_ops,          /* devo_cb_ops */
        (struct bus_ops *)NULL, /* devo_bus_ops */
        nulldev,                /* devo_power */
        ddi_quiesce_not_needed,         /* quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops,
        "Machine Description Driver",
        &mdesc_dev_ops};

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


int
_init(void)
{
        int retval;

        if ((retval = ddi_soft_state_init(&mdesc_state_head,
            sizeof (struct mdesc_state), mdesc_max_opens)) != 0)
                return (retval);
        if ((retval = mod_install(&modlinkage)) != 0) {
                ddi_soft_state_fini(&mdesc_state_head);
                return (retval);
        }

        return (retval);
}




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




int
_fini(void)
{
        int retval;

        if ((retval = mod_remove(&modlinkage)) != 0)
                return (retval);
        ddi_soft_state_fini(&mdesc_state_head);

        return (retval);
}




/*ARGSUSED*/
static int
mdesc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
        struct mdesc_state *mdsp;
        int retval = DDI_FAILURE;

        ASSERT(resultp != NULL);

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                mdsp = ddi_get_soft_state(mdesc_state_head,
                    getminor((dev_t)arg));
                if (mdsp != NULL) {
                        *resultp = mdesc_devi;
                        retval = DDI_SUCCESS;
                } else
                        *resultp = NULL;
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *resultp = (void *)(uintptr_t)getminor((dev_t)arg);
                retval = DDI_SUCCESS;
                break;
        }

        return (retval);
}




static int
mdesc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        int instance = ddi_get_instance(dip);

        switch (cmd) {
        case DDI_ATTACH:

                if (ddi_create_minor_node(dip, MDESC_NAME, S_IFCHR, instance,
                    DDI_PSEUDO, 0) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "%s@%d: Unable to create minor node",
                            MDESC_NAME, instance);
                        return (DDI_FAILURE);
                }
                ddi_report_dev(dip);
                mdesc_devi = dip;
                mdesc_minor = vmem_create("mdesc_minor", (void *) 1,
                    mdesc_max_opens, 1, NULL, NULL, NULL, 0,
                    VM_SLEEP | VMC_IDENTIFIER);
                mutex_init(&mdesc_lock, NULL, MUTEX_DRIVER, NULL);
                mdesc_attached = 1;
                return (DDI_SUCCESS);
        case DDI_RESUME:
                return (DDI_SUCCESS);
        default:
                return (DDI_FAILURE);
        }
}

/*ARGSUSED*/
static int
mdesc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_DETACH:
                mutex_destroy(&mdesc_lock);
                vmem_destroy(mdesc_minor);
                ddi_remove_minor_node(mdesc_devi, NULL);
                mdesc_attached = 0;
                return (DDI_SUCCESS);

        case DDI_SUSPEND:
                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }
}

static void
mdesc_destroy_state(mdesc_state_t *mdsp)
{
        minor_t minor = getminor(mdsp->dev);

        if (mdsp->flags & MDESC_GOT_HANDLE)
                (void) md_fini_handle(mdsp->mdesc);

        cv_destroy(&mdsp->cv);
        mutex_destroy(&mdsp->lock);
        ddi_soft_state_free(mdesc_state_head, minor);
        vmem_free(mdesc_minor, (void *)(uintptr_t)minor, 1);
}

static mdesc_state_t *
mdesc_create_state(dev_t *devp)
{
        major_t major;
        minor_t minor;
        mdesc_state_t *mdsp;

        minor = (minor_t)(uintptr_t)vmem_alloc(mdesc_minor, 1,
            VM_BESTFIT | VM_SLEEP);

        if (ddi_soft_state_zalloc(mdesc_state_head, minor) !=
            DDI_SUCCESS) {
                cmn_err(CE_WARN, "%s@%d: Unable to allocate state",
                    MDESC_NAME, minor);
                vmem_free(mdesc_minor, (void *)(uintptr_t)minor, 1);
                return (NULL);
        }

        mdsp = ddi_get_soft_state(mdesc_state_head, minor);

        if (devp != NULL) {
                major = getemajor(*devp);
        } else {
                major = ddi_driver_major(mdesc_devi);
        }

        mdsp->dev = makedevice(major, minor);

        if (devp != NULL)
                *devp = mdsp->dev;

        mdsp->instance = minor;

        mutex_init(&mdsp->lock, NULL, MUTEX_DRIVER, NULL);

        cv_init(&mdsp->cv, NULL, CV_DRIVER, NULL);

        mdsp->mdesc = md_get_handle();

        if (mdsp->mdesc == NULL) {
                mdesc_destroy_state(mdsp);
                return (NULL);
        }
        mdsp->flags |= MDESC_GOT_HANDLE;

        mdsp->mdesc_len = md_get_bin_size(mdsp->mdesc);

        if (mdsp->mdesc_len == 0) {
                mdesc_destroy_state(mdsp);
                mdsp = NULL;
        }

        return (mdsp);
}


/*ARGSUSED*/
static int
mdesc_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
        struct mdesc_state *mdsp;

        if (otyp != OTYP_CHR)
                return (EINVAL);
        if (!mdesc_attached)
                return (ENXIO);

        mutex_enter(&mdesc_lock);

        if (mdesc_opens >= mdesc_max_opens) {
                mutex_exit(&mdesc_lock);
                return (ENXIO);
        }

        mdsp = mdesc_create_state(devp);

        if (mdsp == NULL) {
                mutex_exit(&mdesc_lock);
                return (ENXIO);
        }

        mdesc_opens++;

        mutex_exit(&mdesc_lock);

        return (0);
}

/*ARGSUSED*/
static int
mdesc_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
        struct mdesc_state *mdsp;
        int instance = getminor(dev);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        mutex_enter(&mdesc_lock);
        if (mdesc_opens == 0) {
                mutex_exit(&mdesc_lock);
                return (0);
        }
        mutex_exit(&mdesc_lock);

        if ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(mdsp->instance == instance);

        mdesc_destroy_state(mdsp);
        mutex_enter(&mdesc_lock);
        mdesc_opens--;
        mutex_exit(&mdesc_lock);

        return (0);
}




/*ARGSUSED*/
static int
mdesc_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return (mdesc_rw(dev, uiop, UIO_READ));
}




/*ARGSUSED*/
static int
mdesc_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return (ENXIO); /* This driver version does not allow updates */
}




static int
mdesc_rw(dev_t dev, struct uio *uiop, enum uio_rw rw)
{
        struct mdesc_state *mdsp;
        int instance = getminor(dev);
        size_t len;
        int retval;
        caddr_t buf;

        len = uiop->uio_resid;

        if ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(mdsp->instance == instance);

        if (len == 0)
                return (0);

        mutex_enter(&mdsp->lock);

        while (mdsp->flags & MDESC_BUSY) {
                if (cv_wait_sig(&mdsp->cv, &mdsp->lock) == 0) {
                        mutex_exit(&mdsp->lock);
                        return (EINTR);
                }
        }

        if (uiop->uio_offset < 0 || uiop->uio_offset > mdsp->mdesc_len) {
                mutex_exit(&mdsp->lock);
                return (EINVAL);
        }

        if (len > (mdsp->mdesc_len - uiop->uio_offset))
                len = mdsp->mdesc_len - uiop->uio_offset;

                /* already checked that offset<mdesc_len above */
        if (len == 0) {
                mutex_exit(&mdsp->lock);
                return (rw == UIO_WRITE ? ENOSPC : 0);
        }

        mdsp->flags |= MDESC_BUSY;
        mutex_exit(&mdsp->lock);

        buf = md_get_md_raw(mdsp->mdesc);
        if (buf == NULL)
                return (ENXIO);

        retval = uiomove((void *)(buf + uiop->uio_offset),
            len, rw, uiop);

        mutex_enter(&mdsp->lock);
        mdsp->flags &= ~MDESC_BUSY;
        cv_broadcast(&mdsp->cv);
        mutex_exit(&mdsp->lock);

        return (retval);
}




/*ARGSUSED*/
static int
mdesc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
        struct mdesc_state *mdsp;
        int instance = getminor(dev);

        if ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL)
                return (ENXIO);

        ASSERT(mdsp->instance == instance);

        switch (cmd) {
        case MDESCIOCGSZ: {
                /*
                 * We are not guaranteed that ddi_copyout(9F) will read
                 * atomically anything larger than a byte.  Therefore we
                 * must duplicate the size before copying it out to the user.
                 */
                size_t sz = mdsp->mdesc_len;

                if (!(mode & FREAD))
                        return (EACCES);

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(mode & FMODELS)) {
                case DDI_MODEL_ILP32: {
                        size32_t sz32 = (size32_t)sz;
                        if (ddi_copyout(&sz32, (void *)arg, sizeof (size32_t),
                            mode) != 0)
                                return (EFAULT);
                        return (0);
                }
                case DDI_MODEL_NONE:
                        if (ddi_copyout(&sz, (void *)arg, sizeof (size_t),
                            mode) != 0)
                                return (EFAULT);
                        return (0);
                default:
                        cmn_err(CE_WARN,
                            "mdesc: Invalid data model %d in ioctl\n",
                            ddi_model_convert_from(mode & FMODELS));
                        return (ENOTSUP);
                }
#else /* ! _MULTI_DATAMODEL */
                if (ddi_copyout(&sz, (void *)arg, sizeof (size_t), mode) != 0)
                        return (EFAULT);
                return (0);
#endif /* _MULTI_DATAMODEL */
        }

        default:
                return (ENOTTY);
        }
}