| /* |
| * 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. |
| */ |
| |
| |
| /* |
| * Pseudo devices are devices implemented entirely in software; pseudonex |
| * (pseudo) is the traditional nexus for pseudodevices. Instances are |
| * typically specified via driver.conf files; e.g. a leaf device which |
| * should be attached below pseudonex will have an entry like: |
| * |
| * name="foo" parent="/pseudo" instance=0; |
| * |
| * pseudonex also supports the devctl (see <sys/devctl.h>) interface via |
| * its :devctl minor node. This allows priveleged userland applications to |
| * online/offline children of pseudo as needed. |
| * |
| * In general, we discourage widespread use of this tactic, as it may lead to a |
| * proliferation of nodes in /pseudo. It is preferred that implementors update |
| * pseudo.conf, adding another 'pseudo' nexus child of /pseudo, and then use |
| * that for their collection of device nodes. To do so, add a driver alias |
| * for the name of the nexus child and a line in pseudo.conf such as: |
| * |
| * name="foo" parent="/pseudo" instance=<n> valid-children="bar","baz"; |
| * |
| * Setting 'valid-children' is important because we have an annoying problem; |
| * we need to prevent pseudo devices with 'parent="pseudo"' set from binding |
| * to our new pseudonex child node. A better way might be to teach the |
| * spec-node code to understand that parent="pseudo" really means |
| * parent="/pseudo". |
| * |
| * At some point in the future, it would be desirable to extend the instance |
| * database to include nexus children of pseudo. Then we could use devctl |
| * or devfs to online nexus children of pseudo, auto-selecting an instance #, |
| * and the instance number selected would be preserved across reboot in |
| * path_to_inst. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/cmn_err.h> |
| #include <sys/conf.h> |
| #include <sys/ddi.h> |
| #include <sys/ddi_impldefs.h> |
| #include <sys/devops.h> |
| #include <sys/instance.h> |
| #include <sys/modctl.h> |
| #include <sys/open.h> |
| #include <sys/stat.h> |
| #include <sys/sunddi.h> |
| #include <sys/sunndi.h> |
| #include <sys/systm.h> |
| #include <sys/mkdev.h> |
| |
| /* |
| * Config information |
| */ |
| static int pseudonex_intr_op(dev_info_t *dip, dev_info_t *rdip, |
| ddi_intr_op_t op, ddi_intr_handle_impl_t *hdlp, void *result); |
| |
| static int pseudonex_attach(dev_info_t *, ddi_attach_cmd_t); |
| static int pseudonex_detach(dev_info_t *, ddi_detach_cmd_t); |
| static int pseudonex_open(dev_t *, int, int, cred_t *); |
| static int pseudonex_close(dev_t, int, int, cred_t *); |
| static int pseudonex_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); |
| static int pseudonex_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, |
| void *); |
| |
| static void *pseudonex_state; |
| |
| typedef struct pseudonex_state { |
| dev_info_t *pnx_devi; |
| } pseudonex_state_t; |
| |
| static struct bus_ops pseudonex_bus_ops = { |
| BUSO_REV, |
| nullbusmap, /* bus_map */ |
| NULL, /* bus_get_intrspec */ |
| NULL, /* bus_add_intrspec */ |
| NULL, /* bus_remove_intrspec */ |
| i_ddi_map_fault, /* bus_map_fault */ |
| ddi_no_dma_map, /* bus_dma_map */ |
| ddi_no_dma_allochdl, /* bus_dma_allochdl */ |
| NULL, /* bus_dma_freehdl */ |
| NULL, /* bus_dma_bindhdl */ |
| NULL, /* bus_dma_unbindhdl */ |
| NULL, /* bus_dma_flush */ |
| NULL, /* bus_dma_win */ |
| NULL, /* bus_dma_ctl */ |
| pseudonex_ctl, /* bus_ctl */ |
| ddi_bus_prop_op, /* bus_prop_op */ |
| 0, /* bus_get_eventcookie */ |
| 0, /* bus_add_eventcall */ |
| 0, /* bus_remove_eventcall */ |
| 0, /* bus_post_event */ |
| NULL, /* bus_intr_ctl */ |
| NULL, /* bus_config */ |
| NULL, /* bus_unconfig */ |
| NULL, /* bus_fm_init */ |
| NULL, /* bus_fm_fini */ |
| NULL, /* bus_fm_access_enter */ |
| NULL, /* bus_fm_access_exit */ |
| NULL, /* bus_power */ |
| pseudonex_intr_op /* bus_intr_op */ |
| }; |
| |
| static struct cb_ops pseudonex_cb_ops = { |
| pseudonex_open, /* open */ |
| pseudonex_close, /* close */ |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| nodev, /* read */ |
| nodev, /* write */ |
| pseudonex_ioctl, /* ioctl */ |
| nodev, /* devmap */ |
| nodev, /* mmap */ |
| nodev, /* segmap */ |
| nochpoll, /* poll */ |
| ddi_prop_op, /* cb_prop_op */ |
| 0, /* streamtab */ |
| D_MP | D_NEW | D_HOTPLUG /* Driver compatibility flag */ |
| }; |
| |
| static struct dev_ops pseudo_ops = { |
| DEVO_REV, /* devo_rev, */ |
| 0, /* refcnt */ |
| ddi_getinfo_1to1, /* info */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| pseudonex_attach, /* attach */ |
| pseudonex_detach, /* detach */ |
| nodev, /* reset */ |
| &pseudonex_cb_ops, /* driver operations */ |
| &pseudonex_bus_ops, /* bus operations */ |
| nulldev, /* power */ |
| ddi_quiesce_not_needed, /* quiesce */ |
| }; |
| |
| /* |
| * Module linkage information for the kernel. |
| */ |
| static struct modldrv modldrv = { |
| &mod_driverops, |
| "nexus driver for 'pseudo' 1.31", |
| &pseudo_ops, |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, (void *)&modldrv, NULL |
| }; |
| |
| int |
| _init(void) |
| { |
| int err; |
| |
| if ((err = ddi_soft_state_init(&pseudonex_state, |
| sizeof (pseudonex_state_t), 0)) != 0) { |
| return (err); |
| } |
| if ((err = mod_install(&modlinkage)) != 0) { |
| ddi_soft_state_fini(&pseudonex_state); |
| return (err); |
| } |
| return (0); |
| } |
| |
| int |
| _fini(void) |
| { |
| int err; |
| |
| if ((err = mod_remove(&modlinkage)) != 0) |
| return (err); |
| ddi_soft_state_fini(&pseudonex_state); |
| return (0); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| pseudonex_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) |
| { |
| int instance; |
| pseudonex_state_t *pnx_state; |
| |
| switch (cmd) { |
| case DDI_ATTACH: |
| break; |
| case DDI_RESUME: |
| return (DDI_SUCCESS); |
| default: |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * Save the devi for this instance in the soft_state data. |
| */ |
| instance = ddi_get_instance(devi); |
| if (ddi_soft_state_zalloc(pseudonex_state, instance) != DDI_SUCCESS) |
| return (DDI_FAILURE); |
| pnx_state = ddi_get_soft_state(pseudonex_state, instance); |
| pnx_state->pnx_devi = devi; |
| |
| if (ddi_create_minor_node(devi, "devctl", S_IFCHR, instance, |
| DDI_NT_NEXUS, 0) != DDI_SUCCESS) { |
| ddi_remove_minor_node(devi, NULL); |
| ddi_soft_state_free(pseudonex_state, instance); |
| return (DDI_FAILURE); |
| } |
| ddi_report_dev(devi); |
| return (DDI_SUCCESS); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| pseudonex_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) |
| { |
| int instance = ddi_get_instance(devi); |
| |
| if (cmd == DDI_SUSPEND) |
| return (DDI_SUCCESS); |
| |
| ddi_remove_minor_node(devi, NULL); |
| ddi_soft_state_free(pseudonex_state, instance); |
| return (DDI_SUCCESS); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| pseudonex_open(dev_t *devp, int flags, int otyp, cred_t *credp) |
| { |
| int instance; |
| |
| if (otyp != OTYP_CHR) |
| return (EINVAL); |
| |
| instance = getminor(*devp); |
| if (ddi_get_soft_state(pseudonex_state, instance) == NULL) |
| return (ENXIO); |
| |
| return (0); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| pseudonex_close(dev_t dev, int flags, int otyp, cred_t *credp) |
| { |
| int instance; |
| |
| if (otyp != OTYP_CHR) |
| return (EINVAL); |
| |
| instance = getminor(dev); |
| if (ddi_get_soft_state(pseudonex_state, instance) == NULL) |
| return (ENXIO); |
| |
| return (0); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| pseudonex_ioctl(dev_t dev, |
| int cmd, intptr_t arg, int mode, cred_t *cred_p, int *rval_p) |
| { |
| int instance; |
| pseudonex_state_t *pnx_state; |
| |
| instance = getminor(dev); |
| if ((pnx_state = ddi_get_soft_state(pseudonex_state, instance)) == NULL) |
| return (ENXIO); |
| ASSERT(pnx_state->pnx_devi); |
| return (ndi_devctl_ioctl(pnx_state->pnx_devi, cmd, arg, mode, 0)); |
| } |
| |
| /* |
| * pseudonex_intr_op: pseudonex convert an interrupt number to an |
| * interrupt. NO OP for pseudo drivers. |
| */ |
| /*ARGSUSED*/ |
| static int |
| pseudonex_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op, |
| ddi_intr_handle_impl_t *hdlp, void *result) |
| { |
| return (DDI_FAILURE); |
| } |
| |
| static int |
| pseudonex_check_assignment(dev_info_t *child, int test_inst) |
| { |
| dev_info_t *tdip; |
| kmutex_t *dmp; |
| const char *childname = ddi_driver_name(child); |
| major_t childmaj = ddi_name_to_major((char *)childname); |
| |
| dmp = &devnamesp[childmaj].dn_lock; |
| LOCK_DEV_OPS(dmp); |
| for (tdip = devnamesp[childmaj].dn_head; |
| tdip != NULL; tdip = ddi_get_next(tdip)) { |
| /* is this the current node? */ |
| if (tdip == child) |
| continue; |
| /* is this a duplicate instance? */ |
| if (test_inst == ddi_get_instance(tdip)) { |
| UNLOCK_DEV_OPS(dmp); |
| return (DDI_FAILURE); |
| } |
| } |
| UNLOCK_DEV_OPS(dmp); |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * This is a nasty, slow hack. But we're stuck with it until we do some |
| * major surgery on the instance assignment subsystem, to allow pseudonode |
| * instance assignment to be tracked there. |
| * |
| * To auto-assign an instance number, we exhaustively search the instance |
| * list for each possible instance number until we find one which is unused. |
| */ |
| static int |
| pseudonex_auto_assign(dev_info_t *child) |
| { |
| dev_info_t *tdip; |
| kmutex_t *dmp; |
| const char *childname = ddi_driver_name(child); |
| major_t childmaj = ddi_name_to_major((char *)childname); |
| int inst = 0; |
| |
| dmp = &devnamesp[childmaj].dn_lock; |
| LOCK_DEV_OPS(dmp); |
| for (inst = 0; inst <= MAXMIN32; inst++) { |
| for (tdip = devnamesp[childmaj].dn_head; tdip != NULL; |
| tdip = ddi_get_next(tdip)) { |
| /* is this the current node? */ |
| if (tdip == child) |
| continue; |
| if (inst == ddi_get_instance(tdip)) { |
| break; |
| } |
| } |
| if (tdip == NULL) { |
| UNLOCK_DEV_OPS(dmp); |
| return (inst); |
| } |
| } |
| UNLOCK_DEV_OPS(dmp); |
| return (-1); |
| } |
| |
| static int |
| pseudonex_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, |
| void *arg, void *result) |
| { |
| switch (ctlop) { |
| case DDI_CTLOPS_REPORTDEV: |
| if (rdip == NULL) |
| return (DDI_FAILURE); |
| cmn_err(CE_CONT, "?pseudo-device: %s%d\n", |
| ddi_driver_name(rdip), ddi_get_instance(rdip)); |
| return (DDI_SUCCESS); |
| |
| case DDI_CTLOPS_INITCHILD: |
| { |
| char name[12]; /* enough for a decimal integer */ |
| int instance = -1; |
| dev_info_t *child = (dev_info_t *)arg; |
| const char *childname = ddi_driver_name(child); |
| char **childlist; |
| uint_t nelems; |
| int auto_assign = 0; |
| |
| /* |
| * If this pseudonex node has a valid-children property, |
| * then that acts as an access control list for children |
| * allowed to attach beneath this node. Honor it. |
| */ |
| if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, |
| DDI_PROP_DONTPASS, "valid-children", &childlist, |
| &nelems) == DDI_PROP_SUCCESS) { |
| int i, ok = 0; |
| for (i = 0; i < nelems; i++) { |
| if (strcmp(childlist[i], childname) == 0) { |
| ok = 1; |
| break; |
| } |
| } |
| ddi_prop_free(childlist); |
| if (!ok) |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * Look up the "instance" property. If it does not exist, |
| * check to see if the "auto-assign-instance" property is set. |
| * If not, default to using instance 0; while not ideal, this |
| * is a legacy behavior we must continue to support. |
| */ |
| instance = ddi_prop_get_int(DDI_DEV_T_ANY, child, |
| DDI_PROP_DONTPASS, "instance", -1); |
| auto_assign = ddi_prop_exists(DDI_DEV_T_ANY, child, |
| DDI_PROP_DONTPASS, "auto-assign-instance"); |
| NDI_CONFIG_DEBUG((CE_NOTE, |
| "pseudonex: DDI_CTLOPS_INITCHILD(instance=%d, " |
| "auto-assign=%d)", instance, auto_assign)); |
| |
| if (instance != -1 && auto_assign != 0) { |
| NDI_CONFIG_DEBUG((CE_NOTE, "both instance and " |
| "auto-assign-instance properties specified. " |
| "Node rejected.")); |
| return (DDI_FAILURE); |
| } |
| |
| if (instance == -1 && auto_assign == 0) { |
| /* default to instance 0 if not specified */ |
| NDI_CONFIG_DEBUG((CE_NOTE, "defaulting to 0")); |
| instance = 0; |
| } |
| |
| /* |
| * If an instance has been specified, determine if this |
| * instance is already in use; if we need to pick an instance, |
| * we do it here. |
| */ |
| if (auto_assign) { |
| if ((instance = pseudonex_auto_assign(child)) == -1) { |
| NDI_CONFIG_DEBUG((CE_NOTE, "failed to " |
| "auto-select instance for %s", childname)); |
| return (DDI_FAILURE); |
| } |
| NDI_CONFIG_DEBUG((CE_NOTE, |
| "auto-selected instance for %s: %d", |
| childname, instance)); |
| } else { |
| if (pseudonex_check_assignment(child, instance) == |
| DDI_FAILURE) { |
| NDI_CONFIG_DEBUG((CE_WARN, |
| "Duplicate instance %d of node \"%s\" " |
| "ignored.", instance, childname)); |
| return (DDI_FAILURE); |
| } |
| NDI_CONFIG_DEBUG((CE_NOTE, |
| "using fixed-assignment instance for %s: %d", |
| childname, instance)); |
| } |
| |
| /* |
| * Attach the instance number to the node. This allows |
| * us to have multiple instances of the same pseudo |
| * device, they will be named 'device@instance'. If this |
| * breaks programs, we may need to special-case instance 0 |
| * into 'device'. Ick. devlinks appears to handle the |
| * new names ok, so if only names in /dev are used |
| * this may not be necessary. |
| */ |
| (void) snprintf(name, sizeof (name), "%d", instance); |
| DEVI(child)->devi_instance = instance; |
| ddi_set_name_addr(child, name); |
| return (DDI_SUCCESS); |
| } |
| |
| case DDI_CTLOPS_UNINITCHILD: |
| { |
| dev_info_t *child = (dev_info_t *)arg; |
| |
| NDI_CONFIG_DEBUG((CE_NOTE, |
| "DDI_CTLOPS_UNINITCHILD(%s, instance=%d)", |
| ddi_driver_name(child), DEVI(child)->devi_instance)); |
| |
| ddi_set_name_addr(child, NULL); |
| |
| return (DDI_SUCCESS); |
| } |
| |
| case DDI_CTLOPS_DMAPMAPC: |
| case DDI_CTLOPS_REPORTINT: |
| case DDI_CTLOPS_REGSIZE: |
| case DDI_CTLOPS_NREGS: |
| case DDI_CTLOPS_SIDDEV: |
| case DDI_CTLOPS_SLAVEONLY: |
| case DDI_CTLOPS_AFFINITY: |
| case DDI_CTLOPS_POKE: |
| case DDI_CTLOPS_PEEK: |
| /* |
| * These ops correspond to functions that "shouldn't" be called |
| * by a pseudo driver. So we whine when we're called. |
| */ |
| cmn_err(CE_CONT, "%s%d: invalid op (%d) from %s%d\n", |
| ddi_driver_name(dip), ddi_get_instance(dip), ctlop, |
| ddi_driver_name(rdip), ddi_get_instance(rdip)); |
| return (DDI_FAILURE); |
| |
| case DDI_CTLOPS_ATTACH: |
| case DDI_CTLOPS_BTOP: |
| case DDI_CTLOPS_BTOPR: |
| case DDI_CTLOPS_DETACH: |
| case DDI_CTLOPS_DVMAPAGESIZE: |
| case DDI_CTLOPS_IOMIN: |
| case DDI_CTLOPS_POWER: |
| case DDI_CTLOPS_PTOB: |
| default: |
| /* |
| * The ops that we pass up (default). We pass up memory |
| * allocation oriented ops that we receive - these may be |
| * associated with pseudo HBA drivers below us with target |
| * drivers below them that use ddi memory allocation |
| * interfaces like scsi_alloc_consistent_buf. |
| */ |
| return (ddi_ctlops(dip, rdip, ctlop, arg, result)); |
| } |
| } |