/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * sun4v machine description driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MDESC_NAME "mdesc" /* * Operational state flags */ #define MDESC_DIDMINOR 0x2 /* Created minors */ #define MDESC_DIDMUTEX 0x8 /* Created mutex */ #define MDESC_DIDCV 0x10 /* Created cv */ #define MDESC_BUSY 0x20 /* Device is busy */ static void *mdesc_state_head; struct mdesc_state { int instance; dev_info_t *devi; kmutex_t lock; kcondvar_t cv; size_t mdesc_len; uint8_t *mdesc; int flags; }; 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 */ }; static struct modldrv modldrv = { &mod_driverops, "Machine Description Driver 1.0", &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), 1)) != 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: if ((mdsp = ddi_get_soft_state(mdesc_state_head, getminor((dev_t)arg))) != NULL) { *resultp = mdsp->devi; retval = DDI_SUCCESS; } else *resultp = NULL; break; case DDI_INFO_DEVT2INSTANCE: *resultp = (void *)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); struct mdesc_state *mdsp; switch (cmd) { case DDI_ATTACH: if (ddi_soft_state_zalloc(mdesc_state_head, instance) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s@%d: Unable to allocate state", MDESC_NAME, instance); return (DDI_FAILURE); } if ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL) { cmn_err(CE_WARN, "%s@%d: Unable to obtain state", MDESC_NAME, instance); ddi_soft_state_free(dip, instance); return (DDI_FAILURE); } 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); (void) mdesc_detach(dip, DDI_DETACH); return (DDI_FAILURE); } mdsp->flags |= MDESC_DIDMINOR; mdsp->instance = instance; mdsp->devi = dip; mutex_init(&mdsp->lock, NULL, MUTEX_DRIVER, NULL); mdsp->flags |= MDESC_DIDMUTEX; cv_init(&mdsp->cv, NULL, CV_DRIVER, NULL); mdsp->flags |= MDESC_DIDCV; /* point the driver at the kernel's copy of the data */ mdsp->mdesc = (uint8_t *)machine_descrip.va; mdsp->mdesc_len = (machine_descrip.va != NULL) ? machine_descrip.size : 0; ddi_report_dev(dip); return (DDI_SUCCESS); case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } static int mdesc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(dip); struct mdesc_state *mdsp; switch (cmd) { case DDI_DETACH: mdsp = ddi_get_soft_state(mdesc_state_head, instance); if (mdsp != NULL) { ASSERT(!(mdsp->flags & MDESC_BUSY)); if (mdsp->flags & MDESC_DIDCV) cv_destroy(&mdsp->cv); if (mdsp->flags & MDESC_DIDMUTEX) mutex_destroy(&mdsp->lock); if (mdsp->flags & MDESC_DIDMINOR) ddi_remove_minor_node(dip, NULL); } ddi_soft_state_free(mdesc_state_head, instance); return (DDI_SUCCESS); case DDI_SUSPEND: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /*ARGSUSED*/ static int mdesc_open(dev_t *devp, int flag, int otyp, cred_t *credp) { int instance = getminor(*devp); struct mdesc_state *mdsp; if ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL) return (ENXIO); ASSERT(mdsp->instance == instance); if (otyp != OTYP_CHR) return (EINVAL); 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 ((mdsp = ddi_get_soft_state(mdesc_state_head, instance)) == NULL) return (ENXIO); ASSERT(mdsp->instance == instance); if (otyp != OTYP_CHR) return (EINVAL); 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; 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 offsetlock); return (rw == UIO_WRITE ? ENOSPC : 0); } mdsp->flags |= MDESC_BUSY; mutex_exit(&mdsp->lock); retval = uiomove((void *)(mdsp->mdesc + 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); } }