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
ndi_hp_register(dev_info_t * dip,ddi_hp_cn_info_t * info_p)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
ndi_hp_unregister(dev_info_t * dip,char * cn_name)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
ndi_hp_state_change_req(dev_info_t * dip,char * cn_name,ddi_hp_cn_state_t state,uint_t flag)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
ndi_hp_walk_cn(dev_info_t * dip,int (* f)(ddi_hp_cn_info_t *,void *),void * arg)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
ddihp_cn_run_event(void * arg)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
ddihp_cn_req_handler(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)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