xref: /illumos-gate/usr/src/uts/common/os/ddi_hp_ndi.c (revision f17620a4f72a29025a22655ba8735ccd20ae174f)
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  * Copyright 2023 Oxide Computer Company
27  */
28 
29 /*
30  * Sun NDI hotplug interfaces
31  */
32 
33 #include <sys/note.h>
34 #include <sys/sysmacros.h>
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kmem.h>
39 #include <sys/cmn_err.h>
40 #include <sys/debug.h>
41 #include <sys/avintr.h>
42 #include <sys/autoconf.h>
43 #include <sys/sunndi.h>
44 #include <sys/ndi_impldefs.h>
45 #include <sys/ddi.h>
46 #include <sys/disp.h>
47 #include <sys/stat.h>
48 #include <sys/callb.h>
49 #include <sys/sysevent.h>
50 #include <sys/sysevent/eventdefs.h>
51 #include <sys/sysevent/dr.h>
52 #include <sys/taskq.h>
53 
54 /* Local functions prototype */
55 static void ddihp_cn_run_event(void *arg);
56 static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
57     ddi_hp_cn_state_t target_state);
58 
59 /*
60  * Global functions (called by hotplug controller or nexus drivers)
61  */
62 
63 /*
64  * Register the Hotplug Connection (CN)
65  */
66 int
67 ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p)
68 {
69 	ddi_hp_cn_handle_t	*hdlp;
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);
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);
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);
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);
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			ret;
141 
142 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n",
143 	    (void *)dip, cn_name));
144 
145 	ASSERT(!servicing_interrupt());
146 	if (servicing_interrupt())
147 		return (NDI_FAILURE);
148 
149 	/* Validate the arguments */
150 	if ((dip == NULL) || (cn_name == NULL))
151 		return (NDI_EINVAL);
152 
153 	ndi_devi_enter(dip);
154 
155 	hdlp = ddihp_cn_name_to_handle(dip, cn_name);
156 	if (hdlp == NULL) {
157 		ndi_devi_exit(dip);
158 		return (NDI_EINVAL);
159 	}
160 
161 	switch (ddihp_cn_unregister(hdlp)) {
162 	case DDI_SUCCESS:
163 		ret = NDI_SUCCESS;
164 		break;
165 	case DDI_EINVAL:
166 		ret = NDI_EINVAL;
167 		break;
168 	case DDI_EBUSY:
169 		ret = NDI_BUSY;
170 		break;
171 	default:
172 		ret = NDI_FAILURE;
173 		break;
174 	}
175 
176 	ndi_devi_exit(dip);
177 
178 	return (ret);
179 }
180 
181 /*
182  * Notify the Hotplug Connection (CN) to change state.
183  * Flag:
184  *	DDI_HP_REQ_SYNC	    Return after the change is finished.
185  *	DDI_HP_REQ_ASYNC    Return after the request is dispatched.
186  */
187 int
188 ndi_hp_state_change_req(dev_info_t *dip, char *cn_name,
189     ddi_hp_cn_state_t state, uint_t flag)
190 {
191 	ddi_hp_cn_async_event_entry_t	*eventp;
192 
193 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p "
194 	    "cn_name: %s, state %x, flag %x\n",
195 	    (void *)dip, cn_name, state, flag));
196 
197 	/* Validate the arguments */
198 	if (dip == NULL || cn_name == NULL)
199 		return (NDI_EINVAL);
200 
201 	if (!NEXUS_HAS_HP_OP(dip)) {
202 		return (NDI_ENOTSUP);
203 	}
204 	/*
205 	 * If the request is to handle the event synchronously, then call
206 	 * the event handler without queuing the event.
207 	 */
208 	if (flag & DDI_HP_REQ_SYNC) {
209 		ddi_hp_cn_handle_t	*hdlp;
210 		int			ret;
211 
212 		ASSERT(!servicing_interrupt());
213 		if (servicing_interrupt())
214 			return (NDI_FAILURE);
215 
216 		ndi_devi_enter(dip);
217 
218 		hdlp = ddihp_cn_name_to_handle(dip, cn_name);
219 		if (hdlp == NULL) {
220 			ndi_devi_exit(dip);
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);
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 	ddi_hp_cn_handle_t	*head, *curr, *prev;
276 
277 	DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n",
278 	    (void *)dip, arg));
279 
280 	ASSERT(!servicing_interrupt());
281 	if (servicing_interrupt())
282 		return;
283 
284 	/* Validate the arguments */
285 	if (dip == NULL)
286 		return;
287 
288 	ndi_devi_enter(dip);
289 
290 	head = DEVI(dip)->devi_hp_hdlp;
291 	curr = head;
292 	prev = NULL;
293 	while (curr != NULL) {
294 		DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p "
295 		    "current cn_name: %s\n",
296 		    (void *)dip, curr->cn_info.cn_name));
297 		switch ((*f)(&(curr->cn_info), arg)) {
298 		case DDI_WALK_TERMINATE:
299 			ndi_devi_exit(dip);
300 
301 			return;
302 		case DDI_WALK_CONTINUE:
303 		default:
304 			if (DEVI(dip)->devi_hp_hdlp != head) {
305 				/*
306 				 * The current node is head and it is removed
307 				 * by last call to (*f)()
308 				 */
309 				head = DEVI(dip)->devi_hp_hdlp;
310 				curr = head;
311 				prev = NULL;
312 			} else if (prev && prev->next != curr) {
313 				/*
314 				 * The current node is a middle node or tail
315 				 * node and it is removed by last call to
316 				 * (*f)()
317 				 */
318 				curr = prev->next;
319 			} else {
320 				/* no removal accurred on curr node */
321 				prev = curr;
322 				curr = curr->next;
323 			}
324 		}
325 	}
326 	ndi_devi_exit(dip);
327 }
328 
329 /*
330  * Local functions (called within this file)
331  */
332 
333 /*
334  * Wrapper function for ddihp_cn_req_handler() called from taskq
335  */
336 static void
337 ddihp_cn_run_event(void *arg)
338 {
339 	ddi_hp_cn_async_event_entry_t	*eventp =
340 	    (ddi_hp_cn_async_event_entry_t *)arg;
341 	dev_info_t			*dip = eventp->dip;
342 	dev_info_t			*pdip;
343 	ddi_hp_cn_handle_t		*hdlp;
344 
345 	/*
346 	 * See notes in ddihp_modctl().  This is another essentially identical
347 	 * path we get to internally rather than from userland, but the same
348 	 * problem applies here.
349 	 */
350 	pdip = ddi_get_parent(dip);
351 	if (pdip != NULL)
352 		ndi_devi_enter(pdip);
353 
354 	/* Lock before access */
355 	ndi_devi_enter(dip);
356 
357 	hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name);
358 	if (hdlp) {
359 		(void) ddihp_cn_req_handler(hdlp, eventp->target_state);
360 	} else {
361 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for "
362 		    "cn_name: %s dip %p. Request for target_state %x is"
363 		    " dropped. \n",
364 		    eventp->cn_name, (void *)dip, eventp->target_state));
365 	}
366 
367 	ndi_devi_exit(dip);
368 	if (pdip != NULL)
369 		ndi_devi_exit(pdip);
370 
371 	/* Release the devi's ref that is held from interrupt context. */
372 	ndi_rele_devi((dev_info_t *)DEVI(dip));
373 	kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1);
374 	kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
375 }
376 
377 /*
378  * Handle state change request of a Hotplug Connection (CN)
379  */
380 static int
381 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
382     ddi_hp_cn_state_t target_state)
383 {
384 	dev_info_t	*dip = hdlp->cn_dip;
385 	int		ret = DDI_SUCCESS;
386 
387 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:"
388 	    " hdlp %p, target_state %x\n",
389 	    (void *)hdlp, target_state));
390 
391 	ASSERT(DEVI_BUSY_OWNED(dip));
392 
393 	/*
394 	 * We do not want to fetch the state first, as calling ddihp_cn_getstate
395 	 * will update the cn_state member of the connection handle. The
396 	 * connector's hotplug operations rely on this value to know how
397 	 * target_state compares to the last known state of the device and make
398 	 * decisions about whether to clean up, post sysevents about the state
399 	 * change, and so on.
400 	 *
401 	 * Instead, just carry out the request to change the state. The
402 	 * connector's hotplug operations will update the state in the
403 	 * connection handle after they complete their necessary state change
404 	 * actions.
405 	 */
406 	if (hdlp->cn_info.cn_state != target_state) {
407 		ddi_hp_cn_state_t result_state = 0;
408 
409 		DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE,
410 		    (void *)&target_state, (void *)&result_state, ret);
411 
412 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, "
413 		    "hdlp %p changed state to %x, ret=%x\n",
414 		    (void *)dip, (void *)hdlp, result_state, ret));
415 	}
416 
417 	if (ret == DDI_SUCCESS)
418 		return (NDI_CLAIMED);
419 	else
420 		return (NDI_UNCLAIMED);
421 }
422