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 2025 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
ndi_hp_register(dev_info_t * dip,ddi_hp_cn_info_t * info_p)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
ndi_hp_unregister(dev_info_t * dip,char * cn_name)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
ndi_hp_state_change_req(dev_info_t * dip,char * cn_name,ddi_hp_cn_state_t state,uint_t flag)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 dev_info_t *pdip;
210 ddi_hp_cn_handle_t *hdlp;
211 int ret;
212
213 ASSERT(!servicing_interrupt());
214 if (servicing_interrupt())
215 return (NDI_FAILURE);
216
217 /*
218 * We know that some of the functions that are called further
219 * from here on may enter critical sections on the parent of
220 * this node. In order to prevent deadlocks, we maintain the
221 * invariant that, if we lock a child, the parent must already
222 * be locked. This is the first place in the call stack where
223 * we may do so, so we lock the parent here.
224 *
225 * See the theory statement near `ndi_devi_enter` in
226 * `common/os/devcfg.c` for more details.
227 */
228 pdip = ddi_get_parent(dip);
229 if (pdip != NULL)
230 ndi_devi_enter(pdip);
231 ndi_devi_enter(dip);
232
233 hdlp = ddihp_cn_name_to_handle(dip, cn_name);
234 if (hdlp == NULL) {
235 ndi_devi_exit(dip);
236 if (pdip != NULL)
237 ndi_devi_exit(pdip);
238
239 return (NDI_EINVAL);
240 }
241
242 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p "
243 "calling ddihp_cn_req_handler() directly to handle "
244 "target_state %x\n", (void *)hdlp, state));
245
246 ret = ddihp_cn_req_handler(hdlp, state);
247
248 ndi_devi_exit(dip);
249 if (pdip != NULL)
250 ndi_devi_exit(pdip);
251
252 return (ret);
253 }
254
255 eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t),
256 KM_NOSLEEP);
257 if (eventp == NULL)
258 return (NDI_NOMEM);
259
260 eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP);
261 if (eventp->cn_name == NULL) {
262 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
263 return (NDI_NOMEM);
264 }
265 eventp->dip = dip;
266 eventp->target_state = state;
267
268 /*
269 * Hold the parent's ref so that it won't disappear when the taskq is
270 * scheduled to run.
271 */
272 ndi_hold_devi(dip);
273
274 if (taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp,
275 TQ_NOSLEEP) == TASKQID_INVALID) {
276 ndi_rele_devi(dip);
277 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: "
278 "taskq_dispatch failed! dip %p "
279 "target_state %x\n", (void *)dip, state));
280 return (NDI_NOMEM);
281 }
282
283 return (NDI_CLAIMED);
284 }
285
286 /*
287 * Walk the link of Hotplug Connection handles of a dip:
288 * DEVI(dip)->devi_hp_hdlp->[link of connections]
289 */
290 void
ndi_hp_walk_cn(dev_info_t * dip,int (* f)(ddi_hp_cn_info_t *,void *),void * arg)291 ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *,
292 void *), void *arg)
293 {
294 ddi_hp_cn_handle_t *head, *curr, *prev;
295
296 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n",
297 (void *)dip, arg));
298
299 ASSERT(!servicing_interrupt());
300 if (servicing_interrupt())
301 return;
302
303 /* Validate the arguments */
304 if (dip == NULL)
305 return;
306
307 ndi_devi_enter(dip);
308
309 head = DEVI(dip)->devi_hp_hdlp;
310 curr = head;
311 prev = NULL;
312 while (curr != NULL) {
313 DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p "
314 "current cn_name: %s\n",
315 (void *)dip, curr->cn_info.cn_name));
316 switch ((*f)(&(curr->cn_info), arg)) {
317 case DDI_WALK_TERMINATE:
318 ndi_devi_exit(dip);
319
320 return;
321 case DDI_WALK_CONTINUE:
322 default:
323 if (DEVI(dip)->devi_hp_hdlp != head) {
324 /*
325 * The current node is head and it is removed
326 * by last call to (*f)()
327 */
328 head = DEVI(dip)->devi_hp_hdlp;
329 curr = head;
330 prev = NULL;
331 } else if (prev && prev->next != curr) {
332 /*
333 * The current node is a middle node or tail
334 * node and it is removed by last call to
335 * (*f)()
336 */
337 curr = prev->next;
338 } else {
339 /* no removal accurred on curr node */
340 prev = curr;
341 curr = curr->next;
342 }
343 }
344 }
345 ndi_devi_exit(dip);
346 }
347
348 /*
349 * Local functions (called within this file)
350 */
351
352 /*
353 * Wrapper function for ddihp_cn_req_handler() called from taskq
354 */
355 static void
ddihp_cn_run_event(void * arg)356 ddihp_cn_run_event(void *arg)
357 {
358 ddi_hp_cn_async_event_entry_t *eventp =
359 (ddi_hp_cn_async_event_entry_t *)arg;
360 dev_info_t *dip = eventp->dip;
361 dev_info_t *pdip;
362 ddi_hp_cn_handle_t *hdlp;
363
364 /*
365 * See notes in ddihp_modctl(). This is another essentially identical
366 * path we get to internally rather than from userland, but the same
367 * problem applies here.
368 */
369 pdip = ddi_get_parent(dip);
370 if (pdip != NULL)
371 ndi_devi_enter(pdip);
372
373 /* Lock before access */
374 ndi_devi_enter(dip);
375
376 hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name);
377 if (hdlp) {
378 (void) ddihp_cn_req_handler(hdlp, eventp->target_state);
379 } else {
380 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for "
381 "cn_name: %s dip %p. Request for target_state %x is"
382 " dropped. \n",
383 eventp->cn_name, (void *)dip, eventp->target_state));
384 }
385
386 ndi_devi_exit(dip);
387 if (pdip != NULL)
388 ndi_devi_exit(pdip);
389
390 /* Release the devi's ref that is held from interrupt context. */
391 ndi_rele_devi((dev_info_t *)DEVI(dip));
392 kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1);
393 kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t));
394 }
395
396 /*
397 * Handle state change request of a Hotplug Connection (CN)
398 */
399 static int
ddihp_cn_req_handler(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)400 ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp,
401 ddi_hp_cn_state_t target_state)
402 {
403 dev_info_t *dip = hdlp->cn_dip;
404 int ret = DDI_SUCCESS;
405
406 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:"
407 " hdlp %p, target_state %x\n",
408 (void *)hdlp, target_state));
409
410 ASSERT(DEVI_BUSY_OWNED(dip));
411
412 /*
413 * We do not want to fetch the state first, as calling ddihp_cn_getstate
414 * will update the cn_state member of the connection handle. The
415 * connector's hotplug operations rely on this value to know how
416 * target_state compares to the last known state of the device and make
417 * decisions about whether to clean up, post sysevents about the state
418 * change, and so on.
419 *
420 * Instead, just carry out the request to change the state. The
421 * connector's hotplug operations will update the state in the
422 * connection handle after they complete their necessary state change
423 * actions.
424 */
425 if (hdlp->cn_info.cn_state != target_state) {
426 ddi_hp_cn_state_t result_state = 0;
427
428 DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE,
429 (void *)&target_state, (void *)&result_state, ret);
430
431 DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, "
432 "hdlp %p changed state to %x, ret=%x\n",
433 (void *)dip, (void *)hdlp, result_state, ret));
434 }
435
436 if (ret == DDI_SUCCESS)
437 return (NDI_CLAIMED);
438 else
439 return (NDI_UNCLAIMED);
440 }
441