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 */
52static void ddihp_cn_run_event(void *arg);
53static 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 */
63int
64ndi_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
123fail:
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 */
134int
135ndi_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 */
186int
187ndi_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 */
271void
272ndi_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 */
337static void
338ddihp_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 */
370static int
371ddihp_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