xref: /illumos-gate/usr/src/uts/common/os/ddi_hp_ndi.c (revision fc8ae2ec)
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) == TASKQID_INVALID) {
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