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 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#include <sys/debug.h>
27#include <sys/types.h>
28#include <sys/errno.h>
29#include <sys/cred.h>
30#include <sys/dditypes.h>
31#include <sys/sunddi.h>
32#include <sys/sunndi.h>
33#include <sys/ddi.h>
34#include <sys/ddi_impldefs.h>
35#include <sys/ndi_impldefs.h>
36#include <sys/kmem.h>
37#include <sys/note.h>
38
39#include <sys/sbdpriv.h>
40#include <sys/sbd_io.h>
41#include <sys/machsystm.h>
42
43
44extern void sbd_errno_decode(int err, sbderror_t *ep, dev_info_t *dip);
45extern sbd_state_t ostate_cvt(sbd_istate_t);
46
47/*
48 * Given a dev_info_t of a branch root, walk down the
49 * branch to attach drivers
50 */
51/*ARGSUSED*/
52void
53sbd_attach_io(sbd_handle_t *hp, sbderror_t *ep, dev_info_t *dip, int unit)
54{
55	sbd_board_t	*sbp = SBDH2BD(hp->h_sbd);
56
57	ASSERT(e_ddi_branch_held(dip));
58
59	(void) e_ddi_branch_configure(dip, NULL, 0);
60
61	ASSERT(sbp->sb_iopath[unit] != NULL);
62
63	(void) ddi_pathname(dip, sbp->sb_iopath[unit]);
64}
65
66/*
67 * remove device nodes for the branch indicated by dip
68 * Hold the status lock so that status can safely do ddi_pathname().
69 */
70/*ARGSUSED*/
71void
72sbd_detach_io(sbd_handle_t *hp, sbderror_t *ep, dev_info_t *dip, int unit)
73{
74	int rv;
75	dev_info_t *fdip = NULL;
76	sbd_board_t *sbp = SBDH2BD(hp->h_sbd);
77
78	ASSERT(e_ddi_branch_held(dip));
79	mutex_enter(&sbp->sb_slock);
80	rv = e_ddi_branch_unconfigure(dip, &fdip, DEVI_BRANCH_EVENT);
81	mutex_exit(&sbp->sb_slock);
82	if (rv) {
83		/*
84		 * If non-NULL, fdip is returned held and must be released.
85		 */
86		if (fdip != NULL) {
87			sbd_errno_decode(rv, ep, fdip);
88			ddi_release_devi(fdip);
89		} else {
90			sbd_errno_decode(rv, ep, dip);
91		}
92	}
93}
94
95/*ARGSUSED*/
96void
97sbd_init_io_unit(sbd_board_t *sbp, int unit)
98{
99	sbd_istate_t	new_state;
100	sbd_io_unit_t	*ip;
101	dev_info_t	*dip;
102
103	ip = SBD_GET_BOARD_IOUNIT(sbp, unit);
104
105	if (SBD_DEV_IS_ATTACHED(sbp, SBD_COMP_IO, unit)) {
106		new_state = SBD_STATE_CONFIGURED;
107	} else if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_IO, unit)) {
108		new_state = SBD_STATE_CONNECTED;
109	} else {
110		new_state = SBD_STATE_EMPTY;
111	}
112	dip = sbp->sb_devlist[NIX(SBD_COMP_IO)][unit];
113	ip->sbi_cm.sbdev_cond = sbd_get_comp_cond(dip);
114
115	/*
116	 * Any changes to this io component should be performed above
117	 * this call to ensure the component is fully initialized
118	 * before transitioning to the new state.
119	 */
120	SBD_DEVICE_TRANSITION(sbp, SBD_COMP_IO, unit, new_state);
121}
122
123/*ARGSUSED*/
124int
125sbd_disconnect_io(sbd_handle_t *hp, int unit)
126{
127	return (0);
128}
129
130int
131sbd_pre_attach_io(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
132{
133	_NOTE(ARGUNUSED(hp))
134	_NOTE(ARGUNUSED(devlist))
135	_NOTE(ARGUNUSED(devnum))
136
137	return (0);
138}
139
140/*ARGSUSED*/
141int
142sbd_pre_detach_io(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
143{
144	fn_t	f = "sbd_pre_detach_io";
145
146	PR_IO("%s...\n", f);
147
148	if (devnum <= 0)
149		return (-1);
150
151	/* fail if any I/O devices are referenced */
152	if (sbd_check_io_refs(hp, devlist, devnum) > 0) {
153		PR_IO("%s: failed - I/O devices ref'd\n", f);
154		return (-1);
155	}
156
157	return (0);
158}
159
160/*ARGSUSED*/
161int
162sbd_post_attach_io(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
163{
164	_NOTE(ARGUNUSED(hp))
165	_NOTE(ARGUNUSED(devlist))
166	_NOTE(ARGUNUSED(devnum))
167
168	return (0);
169}
170
171/*ARGSUSED*/
172int
173sbd_post_detach_io(sbd_handle_t *hp, sbd_devlist_t *devlist, int devnum)
174{
175	return (0);
176}
177
178/*ARGSUSED*/
179int
180sbd_io_status(sbd_handle_t *hp, sbd_devset_t devset, sbd_dev_stat_t *dsp)
181{
182	int		i, ix;
183	sbd_board_t	*sbp;
184	sbd_io_stat_t	*isp;
185	sbd_io_unit_t	*ip;
186	sbd_istate_t	dstate;
187	sbdp_handle_t	*hdp;
188	sbderror_t	*ep;
189	sbd_error_t	*sep;
190
191	/*
192	 * Only look for requested devices that are actually present.
193	 */
194	sbp = SBDH2BD(hp->h_sbd);
195
196	ep = HD2MACHERR(hp);
197	sep = kmem_zalloc(sizeof (sbd_error_t), KM_SLEEP);
198	hdp = sbd_get_sbdp_handle(sbp, hp);
199
200	/*
201	 * Concurrent status and unconfigure, disconnect are allowed.
202	 * To prevent DR code from accessing stale dips, check the
203	 * present devset and access the dips with status lock held.
204	 * Disconnect and unconfigure code change dip state with
205	 * status lock (sb_slock) held.
206	 */
207	mutex_enter(&sbp->sb_slock);
208
209	devset &= SBD_DEVS_PRESENT(sbp);
210
211	for (i = ix = 0; i < MAX_IO_UNITS_PER_BOARD; i++) {
212		dev_info_t	*dip;
213		int		unit;
214		int		namelen;
215		int		refcount = 0;
216
217		if (DEVSET_IN_SET(devset, SBD_COMP_IO, i) == 0)
218			continue;
219		/*
220		 * Check to make sure the io component is in a state
221		 * where its fully initialized.
222		 */
223		if (SBD_DEVICE_STATE(sbp, SBD_COMP_IO, i) == SBD_STATE_EMPTY)
224			continue;
225
226		dip = sbp->sb_devlist[NIX(SBD_COMP_IO)][i];
227		if (dip == NULL)
228			continue;
229
230		isp = &dsp->d_io;
231
232		bzero((caddr_t)isp, sizeof (*isp));
233		namelen = sizeof (isp->is_name);
234		(void) ddi_getlongprop_buf(DDI_DEV_T_ANY, dip,
235		    DDI_PROP_DONTPASS, OBP_DEVICETYPE,
236		    (caddr_t)isp->is_name, &namelen);
237
238		isp->is_unit = sbdp_get_unit_num(hdp, dip);
239		if (isp->is_unit < 0) {
240			if (hp->h_flags & SBD_IOCTL_FLAG_FORCE)
241				continue;
242			else {
243				SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp));
244				break;
245			}
246		}
247		unit = isp->is_unit;
248
249		dstate = SBD_DEVICE_STATE(sbp, SBD_COMP_IO, unit);
250		isp->is_ostate	= ostate_cvt(dstate);
251		isp->is_type = SBD_COMP_IO;
252		ip = SBD_GET_BOARD_IOUNIT(sbp, unit);
253		ip->sbi_cm.sbdev_cond = sbd_get_comp_cond(dip);
254		isp->is_cm.c_cond = ip->sbi_cm.sbdev_cond;
255		isp->is_cm.c_busy = ip->sbi_cm.sbdev_busy;
256		isp->is_cm.c_time = ip->sbi_cm.sbdev_time;
257
258
259		/*
260		 * This is safe to do as unconfigure and disconnect
261		 * hold the status lock while changing dip state.
262		 */
263		(void) ddi_pathname(dip, isp->is_pathname);
264
265		/*
266		 * We use a dummy handle in which to collect
267		 * the major numbers of unsafe devices.
268		 */
269		sbdp_check_devices(dip, &refcount, sep, NULL);
270
271		isp->is_referenced = (refcount == 0) ? 0 : 1;
272
273		isp->is_unsafe_count = 0;
274
275		/*
276		 * Reset error field since we don't care about
277		 * errors at this level.  The unsafe devices
278		 * will be reported in the structure.
279		 */
280		SBD_SET_ERR(ep, ESBD_NOERROR);
281		ep->e_rsc[0] = '\0';
282
283		ix++;
284		dsp++;
285	}
286
287	mutex_exit(&sbp->sb_slock);
288
289	kmem_free(sep, sizeof (sbd_error_t));
290	sbd_release_sbdp_handle(hdp);
291
292	return (ix);
293}
294
295/*ARGSUSED*/
296int
297sbd_io_cnt(sbd_handle_t *hp, sbd_devset_t devset)
298{
299	int		i, ix;
300	sbd_board_t	*sbp;
301
302	sbp = SBDH2BD(hp->h_sbd);
303
304	/*
305	 * Only look for requested devices that are actually present.
306	 */
307	devset &= SBD_DEVS_PRESENT(sbp);
308
309	for (i = ix = 0; i < MAX_IO_UNITS_PER_BOARD; i++) {
310		dev_info_t	*dip;
311
312		if (DEVSET_IN_SET(devset, SBD_COMP_IO, i) == 0)
313			continue;
314
315		dip = sbp->sb_devlist[NIX(SBD_COMP_IO)][i];
316		if (dip == NULL)
317			continue;
318
319		ix++;
320	}
321
322	return (ix);
323}
324
325int
326sbd_check_io_refs(sbd_handle_t *hp, sbd_devlist_t devlist[], int devnum)
327{
328	register int	i, reftotal = 0;
329	fn_t	f = "sbd_check_io_refs";
330	sbd_error_t *sep;
331	sbderror_t *ep;
332
333	sep = kmem_zalloc(sizeof (sbd_error_t), KM_SLEEP);
334	ep = HD2MACHERR(hp);
335
336	for (i = 0; i < devnum; i++) {
337		dev_info_t	*dip;
338		int		ref;
339		int		refcount_non_gldv3;
340
341		dip = devlist[i].dv_dip;
342		ref = 0;
343		refcount_non_gldv3 = 0;
344		sbdp_check_devices(dip, &ref, sep, &refcount_non_gldv3);
345		ASSERT(refcount_non_gldv3 >= 0);
346		ASSERT(ref >= refcount_non_gldv3);
347		/*
348		 * Ignore reference counts of non-gldv3 network devices
349		 * as Crossbow creates reference counts for non-active
350		 * (unplumbed) instances.  Reference count check in
351		 * detach() known to prevent device from detaching
352		 * as necessary.
353		 */
354		ref -= refcount_non_gldv3;
355		if (ref) {
356			if (SBD_GET_ERR(ep) == 0) {
357				SBD_GET_PERR(sep, ep);
358			}
359			SBD_GET_PERR(sep, &devlist[i].dv_error);
360		}
361		PR_IO("%s: dip(%s) ref = %d\n", f, ddi_get_name(dip), ref);
362		reftotal += ref;
363	}
364
365	kmem_free(sep, sizeof (sbd_error_t));
366
367	return (reftotal);
368}
369
370int
371sbd_check_io_attached(dev_info_t *dip, void *arg)
372{
373	dev_info_t **tdip;
374
375	tdip = (dev_info_t **)arg;
376
377	if (dip == *tdip) {
378		int state;
379
380		state = ddi_get_devstate(dip);
381		if (i_ddi_devi_attached(dip) || (state == DDI_DEVSTATE_UP)) {
382			*tdip = NULL;
383			return (DDI_WALK_TERMINATE);
384		}
385	}
386	return (DDI_WALK_CONTINUE);
387}
388
389int
390sbd_pre_release_io(sbd_handle_t *hp,
391	sbd_devlist_t *devlist, int devnum)
392{
393	fn_t	f = "sbd_pre_release_io";
394	int	rv = 0;
395	int	i;
396
397	ASSERT(devnum > 0);
398
399	/* fail if any I/O devices are referenced */
400	if ((rv = sbd_check_io_refs(hp, devlist, devnum)) > 0) {
401		/*
402		 * One of the devices may have failed check to see which
403		 * and set in the main handle
404		 */
405		for (i = 0; i < devnum; i++) {
406			if (SBD_GET_ERR(&devlist[i].dv_error) != 0) {
407				(void) sbd_set_err_in_hdl(hp,
408				    &devlist[i].dv_error);
409				break;
410			}
411		}
412		PR_IO("%s: failed - I/O devices ref'd\n", f);
413	}
414
415	return (rv);
416}
417