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