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