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