summaryrefslogtreecommitdiff
path: root/usr/src/uts/sparc/os/bootdev.c
blob: b45ae4835f662d30476e2fc55c1358a916c2e77e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/systm.h>
#include <sys/pathname.h>
#include <sys/modctl.h>
#include <sys/sunndi.h>
#include <sys/sunmdi.h>
#include <sys/mdi_impldefs.h>
#include <sys/promif.h>

struct parinfo {
	dev_info_t *dip;
	mdi_pathinfo_t *pip;
	dev_info_t *pdip;
};

/*
 * internal functions
 */
static int resolve_devfs_name(char *, char *);
static dev_info_t *find_alternate_node(dev_info_t *, major_t);
static dev_info_t *get_path_parent(dev_info_t *, struct parinfo *);

/* internal global data */
static struct modlmisc modlmisc = {
	&mod_miscops, "bootdev misc module 1.21"
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modlmisc, NULL
};

int
_init()
{
	return (mod_install(&modlinkage));
}

int
_fini()
{
	return (mod_remove(&modlinkage));
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/*
 * convert a prom device path to an equivalent path in /devices
 * Does not deal with aliases.  Does deal with pathnames which
 * are not fully qualified.  This routine is generalized
 * to work across several flavors of OBP
 */
int
i_promname_to_devname(char *prom_name, char *ret_buf)
{
	if (prom_name == NULL || ret_buf == NULL ||
	    (strlen(prom_name) >= MAXPATHLEN)) {
		return (EINVAL);
	}
	if (i_ddi_prompath_to_devfspath(prom_name, ret_buf) != DDI_SUCCESS)
		return (EINVAL);

	return (0);
}

/*
 * translate a devfs pathname to one that will be acceptable
 * by the prom.  In most cases, there is no translation needed.
 * For systems supporting generically named devices, the prom
 * may support nodes such as 'disk' that do not have any unit
 * address information (i.e. target,lun info).  If this is the
 * case, the ddi framework will reject the node as invalid and
 * populate the devinfo tree with nodes froms the .conf file
 * (e.g. sd).  In this case, the names that show up in /devices
 * are sd - since the prom only knows about 'disk' nodes, this
 * routine detects this situation and does the conversion
 * There are also cases such as pluto where the disk node in the
 * prom is named "SUNW,ssd" but in /devices the name is "ssd".
 *
 * If MPxIO is enabled, the translation involves following
 * pathinfo nodes to the "best" parent. See get_obp_parent().
 *
 * return a 0 on success with the new device string in ret_buf.
 * Otherwise return the appropriate error code as we may be called
 * from the openprom driver.
 */
int
i_devname_to_promname(char *dev_name, char *ret_buf, size_t len)
{
	dev_info_t *dip, *pdip, *cdip, *idip;
	char *dev_path, *prom_path;
	char *unit_address, *minorname, *nodename;
	major_t major;
	int depth, old_depth;
	struct parinfo *parinfo;
	struct parinfo *info;
	char *rptr, *optr, *offline;
	size_t olen, rlen;

	/* do some sanity checks */
	if ((dev_name == NULL) || (ret_buf == NULL) ||
	    (strlen(dev_name) > MAXPATHLEN)) {
		return (EINVAL);
	}

	/*
	 * Convert to a /devices name. Fail the translation if
	 * the name doesn't exist.
	 */
	dev_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
	if (resolve_devfs_name(dev_name, dev_path) != 0 ||
	    strncmp(dev_path, "/devices/", 9) != 0) {
		kmem_free(dev_path, MAXPATHLEN);
		return (EINVAL);
	}
	dev_name = dev_path + sizeof ("/devices") - 1;

	bzero(ret_buf, len);

	if (prom_finddevice(dev_name) != OBP_BADNODE) {
		/* we are done */
		(void) snprintf(ret_buf, len, "%s", dev_name);
		kmem_free(dev_path, MAXPATHLEN);
		return (0);
	}

	/*
	 * if we get here, then some portion of the device path is
	 * not understood by the prom.  We need to look for alternate
	 * names (e.g. replace ssd by disk) and mpxio enabled devices.
	 */
	dip = e_ddi_hold_devi_by_path(dev_name, 0);
	if (dip == NULL) {
		cmn_err(CE_NOTE, "cannot find dip for %s", dev_name);
		kmem_free(dev_path, MAXPATHLEN);
		return (EINVAL);
	}

	/* find the closest ancestor which is a prom node */
	pdip = dip;
	parinfo = kmem_alloc(OBP_STACKDEPTH * sizeof (*parinfo), KM_SLEEP);
	for (depth = 0; ndi_dev_is_prom_node(pdip) == 0; depth++) {
		if (depth == OBP_STACKDEPTH) {
			kmem_free(dev_path, MAXPATHLEN);
			kmem_free(parinfo, OBP_STACKDEPTH * sizeof (*parinfo));
			return (EINVAL); /* must not have been an obp node */
		}

		pdip = get_path_parent(pdip, &parinfo[depth]);
	}
	ASSERT(pdip);	/* at least root is prom node */
	ASSERT(depth > 0);

	prom_path = kmem_alloc(MAXPATHLEN, KM_SLEEP);

	offline = kmem_zalloc(len, KM_SLEEP); /* offline paths */
	olen = len;
	rlen = len;

	rptr = ret_buf;
	optr = offline;

	old_depth = depth;
	do {
		bzero(prom_path, MAXPATHLEN);
		if (pdip)
			(void) ddi_pathname(pdip, prom_path);

		ndi_hold_devi(pdip);
		for (depth = old_depth; depth > 0; depth--) {
			info = &parinfo[depth - 1];
			idip = info->dip;

			nodename = ddi_node_name(idip);
			if (info->pip) {
				unit_address = MDI_PI(info->pip)->pi_addr;
			} else {
				unit_address = ddi_get_name_addr(idip);
			}

			if (pdip) {
				major = ddi_driver_major(idip);
				cdip = find_alternate_node(pdip, major);
				ndi_rele_devi(pdip);
				if (cdip) {
					nodename = ddi_node_name(cdip);
				}
			}

			/*
			 * node name + unitaddr to the prom_path
			 */
			(void) strcat(prom_path, "/");
			(void) strcat(prom_path, nodename);
			if (unit_address && (*unit_address)) {
				(void) strcat(prom_path, "@");
				(void) strcat(prom_path, unit_address);
			}
			pdip = cdip;
		}

		if (pdip) {
			ndi_rele_devi(pdip); /* hold from find_alternate_node */
		}

		minorname = strrchr(dev_name, ':');
		if (minorname && (minorname[1] != '\0')) {
			(void) strcat(prom_path, minorname);
		}

		if (!info || !info->pip || MDI_PI_IS_ONLINE(info->pip)) {
			(void) snprintf(rptr, rlen, "%s", prom_path);
			rlen -= strlen(rptr) + 1;
			rptr += strlen(rptr) + 1;
			if (rlen <= 0) /* drop paths we can't store */
				break;
		} else {	/* path is offline */
			(void) snprintf(optr, olen, "%s", prom_path);
			olen -= strlen(optr) + 1;
			if (olen > 0) /* drop paths we can't store */
				optr += strlen(optr) + 1;

		}

		/*
		 * The following code assumes that the phci client is at leaf
		 * level and that all phci nodes are prom nodes.
		 */
		info = &parinfo[0];
		if (info && info->dip && info->pip) {
			info->pip =
			    (mdi_pathinfo_t *)MDI_PI(info->pip)->pi_client_link;
			if (info->pip) {
				pdip = mdi_pi_get_phci(info->pip);
				pdip = ddi_get_parent(pdip);
			} else {
				break;
			}
		} else {
			break;
		}

	} while (info && info->pip && pdip);

	ndi_rele_devi(dip); /* release hold from e_ddi_hold_devi_by_path() */

	/* release holds from get_path_parent() */
	for (depth = old_depth; depth > 0; depth--) {
		info = &parinfo[depth - 1];

		/* replace with mdi_rele_path() when mpxio goes into genunix */
		if (info && info->pip) {
			MDI_PI_LOCK(info->pip);
			MDI_PI_RELE(info->pip);
			if (MDI_PI(info->pip)->pi_ref_cnt == 0)
				cv_broadcast(&MDI_PI(info->pip)->pi_ref_cv);
			MDI_PI_UNLOCK(info->pip);
		}
		if (info && info->pdip)
			ndi_rele_devi(info->pdip);
	}

	/* now add as much of offline to ret_buf as possible */
	bcopy(offline, rptr, rlen);

	ret_buf[len - 1] = '\0';
	ret_buf[len - 2] = '\0';

	kmem_free(offline, len);
	kmem_free(dev_path, MAXPATHLEN);
	kmem_free(prom_path, MAXPATHLEN);
	kmem_free(parinfo, OBP_STACKDEPTH * sizeof (*parinfo));
	return (0);
}

/*
 * check for a possible substitute node.  This routine searches the
 * children of parent_dip, looking for a node that:
 *	1. is a prom node
 *	2. binds to the same major number
 *	3. there is no need to verify that the unit-address information
 *		match since it is likely that the substitute node
 *		will have none (e.g. disk) - this would be the reason the
 *		framework rejected it in the first place.
 *
 * assumes parent_dip is held
 */
static dev_info_t *
find_alternate_node(dev_info_t *parent_dip, major_t major)
{
	int circ;
	dev_info_t *child_dip;

	/* lock down parent to keep children from being removed */
	ndi_devi_enter(parent_dip, &circ);
	for (child_dip = ddi_get_child(parent_dip); child_dip != NULL;
	    child_dip = ddi_get_next_sibling(child_dip)) {

		/* look for obp node with matching major */
		if ((ndi_dev_is_prom_node(child_dip) != 0) &&
		    (ddi_driver_major(child_dip) == major)) {
			ndi_hold_devi(child_dip);
			break;
		}
	}
	ndi_devi_exit(parent_dip, circ);
	return (child_dip);
}

/*
 * given an absolute pathname, convert it, if possible, to a devfs
 * name.  Examples:
 * /dev/rsd3a to /pci@1f,4000/glm@3/sd@0,0:a
 * /dev/dsk/c0t0d0s0 to /pci@1f,4000/glm@3/sd@0,0:a
 * /devices/pci@1f,4000/glm@3/sd@0,0:a to /pci@1f,4000/glm@3/sd@0,0:a
 * /pci@1f,4000/glm@3/sd@0,0:a unchanged
 *
 * This routine deals with symbolic links, physical pathname with and
 * without /devices stripped. Returns 0 on success or -1 on failure.
 */
static int
resolve_devfs_name(char *name, char *buffer)
{
	int error;
	char *fullname = NULL;
	struct pathname pn, rpn;

	/* if not a /dev or /device name, prepend /devices */
	if (strncmp(name, "/dev/", 5) != 0 &&
	    strncmp(name, "/devices/", 9) != 0) {
		fullname = kmem_alloc(MAXPATHLEN, KM_SLEEP);
		(void) snprintf(fullname, MAXPATHLEN, "/devices%s", name);
		name = fullname;
	}

	if (pn_get(name, UIO_SYSSPACE, &pn) != 0) {
		if (fullname)
			kmem_free(fullname, MAXPATHLEN);
		return (-1);
	}

	pn_alloc(&rpn);
	error = lookuppn(&pn, &rpn, FOLLOW, NULL, NULL);
	if (error == 0)
		bcopy(rpn.pn_path, buffer, rpn.pn_pathlen);

	pn_free(&pn);
	pn_free(&rpn);
	if (fullname)
		kmem_free(fullname, MAXPATHLEN);

	return (error);
}

/*
 * If bootstring contains a device path, we need to convert to a format
 * the prom will understand.  To do so, we convert the existing path to
 * a prom-compatible path and return the value of new_path.  If the
 * caller specifies new_path as NULL, we allocate an appropriately
 * sized new_path on behalf of the caller.  If the caller invokes this
 * function with new_path = NULL, they must do so from a context in
 * which it is safe to perform a sleeping memory allocation.
 */
char *
i_convert_boot_device_name(char *cur_path, char *new_path, size_t *len)
{
	char *ptr;
	int rval;

	ASSERT(cur_path != NULL && len != NULL);
	ASSERT(new_path == NULL || *len >= MAXPATHLEN);

	if (new_path == NULL) {
		*len = MAXPATHLEN + MAXNAMELEN;
		new_path = kmem_alloc(*len, KM_SLEEP);
	}

	if ((ptr = strchr(cur_path, ' ')) != NULL)
		*ptr = '\0';

	rval = i_devname_to_promname(cur_path, new_path, *len);

	if (ptr != NULL)
		*ptr = ' ';

	if (rval == 0) {
		if (ptr != NULL) {
			(void) snprintf(new_path + strlen(new_path),
			    *len - strlen(new_path), "%s", ptr);
		}
	} else {		/* the conversion failed */
		(void) snprintf(new_path, *len, "%s", cur_path);
	}

	return (new_path);
}

/*
 * Get the parent dip. If dip is mpxio client, get the first online
 * path and return the phci dip with both pathinfo and dip held
 * otherwise just return with the dip held.
 */
static dev_info_t *
get_path_parent(dev_info_t *dip, struct parinfo *info)
{
	dev_info_t *pdip;
	mdi_pathinfo_t *pip;
	int circ;

	if (!MDI_CLIENT(dip)) {
		pdip = ddi_get_parent(dip);
		pip = NULL;
		goto finish;
	}

	/* find and hold the pathinfo */

	ndi_devi_enter(dip, &circ);
	pip = mdi_get_next_phci_path(dip, NULL);

	if (pip == NULL) {
		ndi_devi_exit(dip, circ);
		return (NULL);
	}

	/* replace with mdi_hold_path() when mpxio goes into genunix */
	MDI_PI_LOCK(pip);
	MDI_PI_HOLD(pip);
	MDI_PI_UNLOCK(pip);

	ndi_devi_exit(dip, circ);
	pdip = mdi_pi_get_phci(pip);

finish:
	ndi_hold_devi(pdip);

	info->dip = dip;
	info->pip = pip;
	info->pdip = pdip;
	return (pdip);
}