Home | History | Annotate | Download | only in os
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * Sun NDI hotplug interfaces
     28  */
     29 
     30 #include <sys/note.h>
     31 #include <sys/sysmacros.h>
     32 #include <sys/types.h>
     33 #include <sys/param.h>
     34 #include <sys/systm.h>
     35 #include <sys/kmem.h>
     36 #include <sys/cmn_err.h>
     37 #include <sys/debug.h>
     38 #include <sys/avintr.h>
     39 #include <sys/autoconf.h>
     40 #include <sys/sunndi.h>
     41 #include <sys/ndi_impldefs.h>
     42 #include <sys/ddi.h>
     43 #include <sys/disp.h>
     44 #include <sys/stat.h>
     45 #include <sys/callb.h>
     46 #include <sys/sysevent.h>
     47 #include <sys/sysevent/eventdefs.h>
     48 #include <sys/sysevent/dr.h>
     49 #include <sys/taskq.h>
     50 
     51 /* Local functions prototype */
     52 static void ddihp_cn_run_event(void *arg);
     53 static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
     54     ddi_hp_cn_state_t target_state);
     55 
     56 /*
     57  * Global functions (called by hotplug controller or nexus drivers)
     58  */
     59 
     60 /*
     61  * Register the Hotplug Connection (CN)
     62  */
     63 int
     64 ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p)
     65 {
     66 	ddi_hp_cn_handle_t	*hdlp;
     67 	int			count;
     68 
     69 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, info_p %p\n",
     70 	    (void *)dip, (void *)info_p));
     71 
     72 	ASSERT(!servicing_interrupt());
     73 	if (servicing_interrupt())
     74 		return (NDI_FAILURE);
     75 
     76 	/* Validate the arguments */
     77 	if ((dip == NULL) || (info_p == NULL))
     78 		return (NDI_EINVAL);
     79 
     80 	if (!NEXUS_HAS_HP_OP(dip)) {
     81 		return (NDI_ENOTSUP);
     82 	}
     83 	/* Lock before access */
     84 	ndi_devi_enter(dip, &count);
     85 
     86 	hdlp = ddihp_cn_name_to_handle(dip, info_p->cn_name);
     87 	if (hdlp) {
     88 		/* This cn_name is already registered. */
     89 		ndi_devi_exit(dip, count);
     90 
     91 		return (NDI_SUCCESS);
     92 	}
     93 	/*
     94 	 * Create and initialize hotplug Connection handle
     95 	 */
     96 	hdlp = (ddi_hp_cn_handle_t *)kmem_zalloc(
     97 	    (sizeof (ddi_hp_cn_handle_t)), KM_SLEEP);
     98 
     99 	/* Copy the Connection information */
    100 	hdlp->cn_dip = dip;
    101 	bcopy(info_p, &(hdlp->cn_info), sizeof (*info_p));
    102 
    103 	/* Copy cn_name */
    104 	hdlp->cn_info.cn_name = ddi_strdup(info_p->cn_name, KM_SLEEP);
    105 
    106 	if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) {
    107 		DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, hdlp %p"
    108 		    "ddi_cn_getstate failed\n", (void *)dip, (void *)hdlp));
    109 
    110 		goto fail;
    111 	}
    112 
    113 	/*
    114 	 * Append the handle to the list
    115 	 */
    116 	DDIHP_LIST_APPEND(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp),
    117 	    hdlp);
    118 
    119 	ndi_devi_exit(dip, count);
    120 
    121 	return (NDI_SUCCESS);
    122 
    123 fail:
    124 	kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1);
    125 	kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t));
    126 	ndi_devi_exit(dip, count);
    127 
    128 	return (NDI_FAILURE);
    129 }
    130 
    131 /*
    132  * Unregister a Hotplug Connection (CN)
    133  */
    134 int
    135 ndi_hp_unregister(dev_info_t *dip, char *cn_name)
    136 {
    137 	ddi_hp_cn_handle_t	*hdlp;
    138 	int			count;
    139 	int			ret;
    140 
    141 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n",
    142 	    (void *)dip, cn_name));
    143 
    144 	ASSERT(!servicing_interrupt());
    145 	if (servicing_interrupt())
    146 		return (NDI_FAILURE);
    147 
    148 	/* Validate the arguments */
    149 	if ((dip == NULL) || (cn_name == NULL))
    150 		return (NDI_EINVAL);
    151 
    152 	ndi_devi_enter(dip, &count);
    153 
    154 	hdlp = ddihp_cn_name_to_handle(dip, cn_name);
    155 	if (hdlp == NULL) {
    156 		ndi_devi_exit(dip, count);
    157 		return (NDI_EINVAL);
    158 	}
    159 
    160 	switch (ddihp_cn_unregister(hdlp)) {
    161 	case DDI_SUCCESS:
    162 		ret = NDI_SUCCESS;
    163 		break;
    164 	case DDI_EINVAL:
    165 		ret = NDI_EINVAL;
    166 		break;
    167 	case DDI_EBUSY:
    168 		ret = NDI_BUSY;
    169 		break;
    170 	default:
    171 		ret = NDI_FAILURE;
    172 		break;
    173 	}
    174 
    175 	ndi_devi_exit(dip, count);
    176 
    177 	return (ret);
    178 }
    179 
    180 /*
    181  * Notify the Hotplug Connection (CN) to change state.
    182  * Flag:
    183  *	DDI_HP_REQ_SYNC	    Return after the change is finished.
    184  *	DDI_HP_REQ_ASYNC    Return after the request is dispatched.
    185  */
    186 int
    187 ndi_hp_state_change_req(dev_info_t *dip, char *cn_name,
    188     ddi_hp_cn_state_t state, uint_t flag)
    189 {
    190 	ddi_hp_cn_async_event_entry_t	*eventp;
    191 
    192 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p "
    193 	    "cn_name: %s, state %x, flag %x\n",
    194 	    (void *)dip, cn_name, state, flag));
    195 
    196 	/* Validate the arguments */
    197 	if (dip == NULL || cn_name == NULL)
    198 		return (NDI_EINVAL);
    199 
    200 	if (!NEXUS_HAS_HP_OP(dip)) {
    201 		return (NDI_ENOTSUP);
    202 	}
    203 	/*
    204 	 * If the request is to handle the event synchronously, then call
    205 	 * the event handler without queuing the event.
    206 	 */
    207 	if (flag & DDI_HP_REQ_SYNC) {
    208 		ddi_hp_cn_handle_t	*hdlp;
    209 		int			count;
    210 		int			ret;
    211 
    212 		ASSERT(!servicing_interrupt());
    213 		if (servicing_interrupt())
    214 			return (NDI_FAILURE);
    215 
    216 		ndi_devi_enter(dip, &count);
    217 
    218 		hdlp = ddihp_cn_name_to_handle(dip, cn_name);
    219 		if (hdlp == NULL) {
    220 			ndi_devi_exit(dip, count);
    221 
    222 			return (NDI_EINVAL);
    223 		}
    224 
    225 		DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p "
    226 		    "calling ddihp_cn_req_handler() directly to handle "
    227 		    "target_state %x\n", (void *)hdlp, state));
    228 
    229 		ret = ddihp_cn_req_handler(hdlp, state);
    230 
    231 		ndi_devi_exit(dip, count);
    232 
    233 		return (ret);
    234 	}
    235 
    236 	eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t),
    237 	    KM_NOSLEEP);
    238 	if (eventp == NULL)
    239 		return (NDI_NOMEM);
    240 
    241 	eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP);
    242 	if (eventp->cn_name == NULL) {
    243 		kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
    244 		return (NDI_NOMEM);
    245 	}
    246 	eventp->dip = dip;
    247 	eventp->target_state = state;
    248 
    249 	/*
    250 	 * Hold the parent's ref so that it won't disappear when the taskq is
    251 	 * scheduled to run.
    252 	 */
    253 	ndi_hold_devi(dip);
    254 
    255 	if (!taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp,
    256 	    TQ_NOSLEEP)) {
    257 		ndi_rele_devi(dip);
    258 		DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: "
    259 		    "taskq_dispatch failed! dip %p "
    260 		    "target_state %x\n", (void *)dip, state));
    261 		return (NDI_NOMEM);
    262 	}
    263 
    264 	return (NDI_CLAIMED);
    265 }
    266 
    267 /*
    268  * Walk the link of Hotplug Connection handles of a dip:
    269  *	DEVI(dip)->devi_hp_hdlp->[link of connections]
    270  */
    271 void
    272 ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *,
    273     void *), void *arg)
    274 {
    275 	int			count;
    276 	ddi_hp_cn_handle_t	*head, *curr, *prev;
    277 
    278 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n",
    279 	    (void *)dip, arg));
    280 
    281 	ASSERT(!servicing_interrupt());
    282 	if (servicing_interrupt())
    283 		return;
    284 
    285 	/* Validate the arguments */
    286 	if (dip == NULL)
    287 		return;
    288 
    289 	ndi_devi_enter(dip, &count);
    290 
    291 	head = DEVI(dip)->devi_hp_hdlp;
    292 	curr = head;
    293 	prev = NULL;
    294 	while (curr != NULL) {
    295 		DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p "
    296 		    "current cn_name: %s\n",
    297 		    (void *)dip, curr->cn_info.cn_name));
    298 		switch ((*f)(&(curr->cn_info), arg)) {
    299 		case DDI_WALK_TERMINATE:
    300 			ndi_devi_exit(dip, count);
    301 
    302 			return;
    303 		case DDI_WALK_CONTINUE:
    304 		default:
    305 			if (DEVI(dip)->devi_hp_hdlp != head) {
    306 				/*
    307 				 * The current node is head and it is removed
    308 				 * by last call to (*f)()
    309 				 */
    310 				head = DEVI(dip)->devi_hp_hdlp;
    311 				curr = head;
    312 				prev = NULL;
    313 			} else if (prev && prev->next != curr) {
    314 				/*
    315 				 * The current node is a middle node or tail
    316 				 * node and it is removed by last call to
    317 				 * (*f)()
    318 				 */
    319 				curr = prev->next;
    320 			} else {
    321 				/* no removal accurred on curr node */
    322 				prev = curr;
    323 				curr = curr->next;
    324 			}
    325 		}
    326 	}
    327 	ndi_devi_exit(dip, count);
    328 }
    329 
    330 /*
    331  * Local functions (called within this file)
    332  */
    333 
    334 /*
    335  * Wrapper function for ddihp_cn_req_handler() called from taskq
    336  */
    337 static void
    338 ddihp_cn_run_event(void *arg)
    339 {
    340 	ddi_hp_cn_async_event_entry_t	*eventp =
    341 	    (ddi_hp_cn_async_event_entry_t *)arg;
    342 	dev_info_t			*dip = eventp->dip;
    343 	ddi_hp_cn_handle_t		*hdlp;
    344 	int				count;
    345 
    346 	/* Lock before access */
    347 	ndi_devi_enter(dip, &count);
    348 
    349 	hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name);
    350 	if (hdlp) {
    351 		(void) ddihp_cn_req_handler(hdlp, eventp->target_state);
    352 	} else {
    353 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for "
    354 		    "cn_name: %s dip %p. Request for target_state %x is"
    355 		    " dropped. \n",
    356 		    eventp->cn_name, (void *)dip, eventp->target_state));
    357 	}
    358 
    359 	ndi_devi_exit(dip, count);
    360 
    361 	/* Release the devi's ref that is held from interrupt context. */
    362 	ndi_rele_devi((dev_info_t *)DEVI(dip));
    363 	kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1);
    364 	kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
    365 }
    366 
    367 /*
    368  * Handle state change request of a Hotplug Connection (CN)
    369  */
    370 static int
    371 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
    372     ddi_hp_cn_state_t target_state)
    373 {
    374 	dev_info_t	*dip = hdlp->cn_dip;
    375 	int		ret = DDI_SUCCESS;
    376 
    377 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:"
    378 	    " hdlp %p, target_state %x\n",
    379 	    (void *)hdlp, target_state));
    380 
    381 	ASSERT(DEVI_BUSY_OWNED(dip));
    382 
    383 	if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) {
    384 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, "
    385 		    "hdlp %p ddi_cn_getstate failed\n", (void *)dip,
    386 		    (void *)hdlp));
    387 
    388 		return (NDI_UNCLAIMED);
    389 	}
    390 	if (hdlp->cn_info.cn_state != target_state) {
    391 		ddi_hp_cn_state_t result_state = 0;
    392 
    393 		DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE,
    394 		    (void *)&target_state, (void *)&result_state, ret);
    395 
    396 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, "
    397 		    "hdlp %p changed state to %x, ret=%x\n",
    398 		    (void *)dip, (void *)hdlp, result_state, ret));
    399 	}
    400 
    401 	if (ret == DDI_SUCCESS)
    402 		return (NDI_CLAIMED);
    403 	else
    404 		return (NDI_UNCLAIMED);
    405 }
    406