xref: /illumos-gate/usr/src/uts/sparc/os/bootdev.c (revision 8eea8e29)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/systm.h>
30 #include <sys/pathname.h>
31 #include <sys/modctl.h>
32 #include <sys/sunndi.h>
33 #include <sys/sunmdi.h>
34 #include <sys/mdi_impldefs.h>
35 #include <sys/promif.h>
36 
37 struct parinfo {
38 	dev_info_t *dip;
39 	mdi_pathinfo_t *pip;
40 	dev_info_t *pdip;
41 };
42 
43 /*
44  * internal functions
45  */
46 static int resolve_devfs_name(char *, char *);
47 static dev_info_t *find_alternate_node(dev_info_t *, major_t);
48 static dev_info_t *get_path_parent(dev_info_t *, struct parinfo *);
49 
50 /* internal global data */
51 static struct modlmisc modlmisc = {
52 	&mod_miscops, "bootdev misc module 1.21"
53 };
54 
55 static struct modlinkage modlinkage = {
56 	MODREV_1, (void *)&modlmisc, NULL
57 };
58 
59 int
60 _init()
61 {
62 	return (mod_install(&modlinkage));
63 }
64 
65 int
66 _fini()
67 {
68 	return (mod_remove(&modlinkage));
69 }
70 
71 int
72 _info(struct modinfo *modinfop)
73 {
74 	return (mod_info(&modlinkage, modinfop));
75 }
76 
77 /*
78  * convert a prom device path to an equivalent path in /devices
79  * Does not deal with aliases.  Does deal with pathnames which
80  * are not fully qualified.  This routine is generalized
81  * to work across several flavors of OBP
82  */
83 int
84 i_promname_to_devname(char *prom_name, char *ret_buf)
85 {
86 	if (prom_name == NULL || ret_buf == NULL ||
87 	    (strlen(prom_name) >= MAXPATHLEN)) {
88 		return (EINVAL);
89 	}
90 	if (i_ddi_prompath_to_devfspath(prom_name, ret_buf) != DDI_SUCCESS)
91 		return (EINVAL);
92 
93 	return (0);
94 }
95 
96 /*
97  * translate a devfs pathname to one that will be acceptable
98  * by the prom.  In most cases, there is no translation needed.
99  * For systems supporting generically named devices, the prom
100  * may support nodes such as 'disk' that do not have any unit
101  * address information (i.e. target,lun info).  If this is the
102  * case, the ddi framework will reject the node as invalid and
103  * populate the devinfo tree with nodes froms the .conf file
104  * (e.g. sd).  In this case, the names that show up in /devices
105  * are sd - since the prom only knows about 'disk' nodes, this
106  * routine detects this situation and does the conversion
107  * There are also cases such as pluto where the disk node in the
108  * prom is named "SUNW,ssd" but in /devices the name is "ssd".
109  *
110  * If MPxIO is enabled, the translation involves following
111  * pathinfo nodes to the "best" parent. See get_obp_parent().
112  *
113  * return a 0 on success with the new device string in ret_buf.
114  * Otherwise return the appropriate error code as we may be called
115  * from the openprom driver.
116  */
117 int
118 i_devname_to_promname(char *dev_name, char *ret_buf, size_t len)
119 {
120 	dev_info_t *dip, *pdip, *cdip, *idip;
121 	char *dev_path, *prom_path;
122 	char *unit_address, *minorname, *nodename;
123 	major_t major;
124 	int depth, old_depth;
125 	struct parinfo *parinfo;
126 	struct parinfo *info;
127 	char *rptr, *optr, *offline;
128 	size_t olen, rlen;
129 
130 	/* do some sanity checks */
131 	if ((dev_name == NULL) || (ret_buf == NULL) ||
132 	    (strlen(dev_name) > MAXPATHLEN)) {
133 		return (EINVAL);
134 	}
135 
136 	/*
137 	 * Convert to a /devices name. Fail the translation if
138 	 * the name doesn't exist.
139 	 */
140 	dev_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
141 	if (resolve_devfs_name(dev_name, dev_path) != 0 ||
142 	    strncmp(dev_path, "/devices/", 9) != 0) {
143 		kmem_free(dev_path, MAXPATHLEN);
144 		return (EINVAL);
145 	}
146 	dev_name = dev_path + sizeof ("/devices") - 1;
147 
148 	bzero(ret_buf, len);
149 
150 	if (prom_finddevice(dev_name) != OBP_BADNODE) {
151 		/* we are done */
152 		(void) snprintf(ret_buf, len, "%s", dev_name);
153 		kmem_free(dev_path, MAXPATHLEN);
154 		return (0);
155 	}
156 
157 	/*
158 	 * if we get here, then some portion of the device path is
159 	 * not understood by the prom.  We need to look for alternate
160 	 * names (e.g. replace ssd by disk) and mpxio enabled devices.
161 	 */
162 	dip = e_ddi_hold_devi_by_path(dev_name, 0);
163 	if (dip == NULL) {
164 		cmn_err(CE_NOTE, "cannot find dip for %s", dev_name);
165 		kmem_free(dev_path, MAXPATHLEN);
166 		return (EINVAL);
167 	}
168 
169 	/* find the closest ancestor which is a prom node */
170 	pdip = dip;
171 	parinfo = kmem_alloc(OBP_STACKDEPTH * sizeof (*parinfo), KM_SLEEP);
172 	for (depth = 0; ndi_dev_is_prom_node(pdip) == 0; depth++) {
173 		if (depth == OBP_STACKDEPTH) {
174 			kmem_free(dev_path, MAXPATHLEN);
175 			kmem_free(parinfo, OBP_STACKDEPTH * sizeof (*parinfo));
176 			return (EINVAL); /* must not have been an obp node */
177 		}
178 
179 		pdip = get_path_parent(pdip, &parinfo[depth]);
180 	}
181 	ASSERT(pdip);	/* at least root is prom node */
182 	ASSERT(depth > 0);
183 
184 	prom_path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
185 
186 	offline = kmem_zalloc(len, KM_SLEEP); /* offline paths */
187 	olen = len;
188 	rlen = len;
189 
190 	rptr = ret_buf;
191 	optr = offline;
192 
193 	old_depth = depth;
194 	do {
195 		bzero(prom_path, MAXPATHLEN);
196 		if (pdip)
197 			(void) ddi_pathname(pdip, prom_path);
198 
199 		ndi_hold_devi(pdip);
200 		for (depth = old_depth; depth > 0; depth--) {
201 			info = &parinfo[depth - 1];
202 			idip = info->dip;
203 
204 			nodename = ddi_node_name(idip);
205 			if (info->pip) {
206 				unit_address = MDI_PI(info->pip)->pi_addr;
207 			} else {
208 				unit_address = ddi_get_name_addr(idip);
209 			}
210 
211 			if (pdip) {
212 				major = ddi_driver_major(idip);
213 				cdip = find_alternate_node(pdip, major);
214 				ndi_rele_devi(pdip);
215 				if (cdip) {
216 					nodename = ddi_node_name(cdip);
217 				}
218 			}
219 
220 			/*
221 			 * node name + unitaddr to the prom_path
222 			 */
223 			(void) strcat(prom_path, "/");
224 			(void) strcat(prom_path, nodename);
225 			if (unit_address && (*unit_address)) {
226 				(void) strcat(prom_path, "@");
227 				(void) strcat(prom_path, unit_address);
228 			}
229 			pdip = cdip;
230 		}
231 
232 		if (pdip) {
233 			ndi_rele_devi(pdip); /* hold from find_alternate_node */
234 		}
235 
236 		minorname = strrchr(dev_name, ':');
237 		if (minorname && (minorname[1] != '\0')) {
238 			(void) strcat(prom_path, minorname);
239 		}
240 
241 		if (!info || !info->pip || MDI_PI_IS_ONLINE(info->pip)) {
242 			(void) snprintf(rptr, rlen, "%s", prom_path);
243 			rlen -= strlen(rptr) + 1;
244 			rptr += strlen(rptr) + 1;
245 			if (rlen <= 0) /* drop paths we can't store */
246 				break;
247 		} else {	/* path is offline */
248 			(void) snprintf(optr, olen, "%s", prom_path);
249 			olen -= strlen(optr) + 1;
250 			if (olen > 0) /* drop paths we can't store */
251 				optr += strlen(optr) + 1;
252 
253 		}
254 
255 		/*
256 		 * The following code assumes that the phci client is at leaf
257 		 * level and that all phci nodes are prom nodes.
258 		 */
259 		info = &parinfo[0];
260 		if (info && info->dip && info->pip) {
261 			info->pip =
262 			    (mdi_pathinfo_t *)MDI_PI(info->pip)->pi_client_link;
263 			if (info->pip) {
264 				pdip = mdi_pi_get_phci(info->pip);
265 				pdip = ddi_get_parent(pdip);
266 			} else {
267 				break;
268 			}
269 		} else {
270 			break;
271 		}
272 
273 	} while (info && info->pip && pdip);
274 
275 	ndi_rele_devi(dip); /* release hold from e_ddi_hold_devi_by_path() */
276 
277 	/* release holds from get_path_parent() */
278 	for (depth = old_depth; depth > 0; depth--) {
279 		info = &parinfo[depth - 1];
280 
281 		/* replace with mdi_rele_path() when mpxio goes into genunix */
282 		if (info && info->pip) {
283 			MDI_PI_LOCK(info->pip);
284 			MDI_PI_RELE(info->pip);
285 			if (MDI_PI(info->pip)->pi_ref_cnt == 0)
286 				cv_broadcast(&MDI_PI(info->pip)->pi_ref_cv);
287 			MDI_PI_UNLOCK(info->pip);
288 		}
289 		if (info && info->pdip)
290 			ndi_rele_devi(info->pdip);
291 	}
292 
293 	/* now add as much of offline to ret_buf as possible */
294 	bcopy(offline, rptr, rlen);
295 
296 	ret_buf[len - 1] = '\0';
297 	ret_buf[len - 2] = '\0';
298 
299 	kmem_free(offline, len);
300 	kmem_free(dev_path, MAXPATHLEN);
301 	kmem_free(prom_path, MAXPATHLEN);
302 	kmem_free(parinfo, OBP_STACKDEPTH * sizeof (*parinfo));
303 	return (0);
304 }
305 
306 /*
307  * check for a possible substitute node.  This routine searches the
308  * children of parent_dip, looking for a node that:
309  *	1. is a prom node
310  *	2. binds to the same major number
311  *	3. there is no need to verify that the unit-address information
312  *		match since it is likely that the substitute node
313  *		will have none (e.g. disk) - this would be the reason the
314  *		framework rejected it in the first place.
315  *
316  * assumes parent_dip is held
317  */
318 static dev_info_t *
319 find_alternate_node(dev_info_t *parent_dip, major_t major)
320 {
321 	int circ;
322 	dev_info_t *child_dip;
323 
324 	/* lock down parent to keep children from being removed */
325 	ndi_devi_enter(parent_dip, &circ);
326 	for (child_dip = ddi_get_child(parent_dip); child_dip != NULL;
327 	    child_dip = ddi_get_next_sibling(child_dip)) {
328 
329 		/* look for obp node with matching major */
330 		if ((ndi_dev_is_prom_node(child_dip) != 0) &&
331 		    (ddi_driver_major(child_dip) == major)) {
332 			ndi_hold_devi(child_dip);
333 			break;
334 		}
335 	}
336 	ndi_devi_exit(parent_dip, circ);
337 	return (child_dip);
338 }
339 
340 /*
341  * given an absolute pathname, convert it, if possible, to a devfs
342  * name.  Examples:
343  * /dev/rsd3a to /pci@1f,4000/glm@3/sd@0,0:a
344  * /dev/dsk/c0t0d0s0 to /pci@1f,4000/glm@3/sd@0,0:a
345  * /devices/pci@1f,4000/glm@3/sd@0,0:a to /pci@1f,4000/glm@3/sd@0,0:a
346  * /pci@1f,4000/glm@3/sd@0,0:a unchanged
347  *
348  * This routine deals with symbolic links, physical pathname with and
349  * without /devices stripped. Returns 0 on success or -1 on failure.
350  */
351 static int
352 resolve_devfs_name(char *name, char *buffer)
353 {
354 	int error;
355 	char *fullname = NULL;
356 	struct pathname pn, rpn;
357 
358 	/* if not a /dev or /device name, prepend /devices */
359 	if (strncmp(name, "/dev/", 5) != 0 &&
360 	    strncmp(name, "/devices/", 9) != 0) {
361 		fullname = kmem_alloc(MAXPATHLEN, KM_SLEEP);
362 		(void) snprintf(fullname, MAXPATHLEN, "/devices%s", name);
363 		name = fullname;
364 	}
365 
366 	if (pn_get(name, UIO_SYSSPACE, &pn) != 0) {
367 		if (fullname)
368 			kmem_free(fullname, MAXPATHLEN);
369 		return (-1);
370 	}
371 
372 	pn_alloc(&rpn);
373 	error = lookuppn(&pn, &rpn, FOLLOW, NULL, NULL);
374 	if (error == 0)
375 		bcopy(rpn.pn_path, buffer, rpn.pn_pathlen);
376 
377 	pn_free(&pn);
378 	pn_free(&rpn);
379 	if (fullname)
380 		kmem_free(fullname, MAXPATHLEN);
381 
382 	return (error);
383 }
384 
385 /*
386  * If bootstring contains a device path, we need to convert to a format
387  * the prom will understand.  To do so, we convert the existing path to
388  * a prom-compatible path and return the value of new_path.  If the
389  * caller specifies new_path as NULL, we allocate an appropriately
390  * sized new_path on behalf of the caller.  If the caller invokes this
391  * function with new_path = NULL, they must do so from a context in
392  * which it is safe to perform a sleeping memory allocation.
393  */
394 char *
395 i_convert_boot_device_name(char *cur_path, char *new_path, size_t *len)
396 {
397 	char *ptr;
398 	int rval;
399 
400 	ASSERT(cur_path != NULL && len != NULL);
401 	ASSERT(new_path == NULL || *len >= MAXPATHLEN);
402 
403 	if (new_path == NULL) {
404 		*len = MAXPATHLEN + MAXNAMELEN;
405 		new_path = kmem_alloc(*len, KM_SLEEP);
406 	}
407 
408 	if ((ptr = strchr(cur_path, ' ')) != NULL)
409 		*ptr = '\0';
410 
411 	rval = i_devname_to_promname(cur_path, new_path, *len);
412 
413 	if (ptr != NULL)
414 		*ptr = ' ';
415 
416 	if (rval == 0) {
417 		if (ptr != NULL) {
418 			(void) snprintf(new_path + strlen(new_path),
419 			    *len - strlen(new_path), "%s", ptr);
420 		}
421 	} else {		/* the conversion failed */
422 		(void) snprintf(new_path, *len, "%s", cur_path);
423 	}
424 
425 	return (new_path);
426 }
427 
428 /*
429  * Get the parent dip. If dip is mpxio client, get the first online
430  * path and return the phci dip with both pathinfo and dip held
431  * otherwise just return with the dip held.
432  */
433 static dev_info_t *
434 get_path_parent(dev_info_t *dip, struct parinfo *info)
435 {
436 	dev_info_t *pdip;
437 	mdi_pathinfo_t *pip;
438 	int circ;
439 
440 	if (!MDI_CLIENT(dip)) {
441 		pdip = ddi_get_parent(dip);
442 		pip = NULL;
443 		goto finish;
444 	}
445 
446 	/* find and hold the pathinfo */
447 
448 	ndi_devi_enter(dip, &circ);
449 	pip = mdi_get_next_phci_path(dip, NULL);
450 
451 	if (pip == NULL) {
452 		ndi_devi_exit(dip, circ);
453 		return (NULL);
454 	}
455 
456 	/* replace with mdi_hold_path() when mpxio goes into genunix */
457 	MDI_PI_LOCK(pip);
458 	MDI_PI_HOLD(pip);
459 	MDI_PI_UNLOCK(pip);
460 
461 	ndi_devi_exit(dip, circ);
462 	pdip = mdi_pi_get_phci(pip);
463 
464 finish:
465 	ndi_hold_devi(pdip);
466 
467 	info->dip = dip;
468 	info->pip = pip;
469 	info->pdip = pdip;
470 	return (pdip);
471 }
472