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
_init()61 _init()
62 {
63 return (mod_install(&modlinkage));
64 }
65
66 int
_fini()67 _fini()
68 {
69 return (mod_remove(&modlinkage));
70 }
71
72 int
_info(struct modinfo * modinfop)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
i_promname_to_devname(char * prom_name,char * ret_buf)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
i_devi_to_promname(dev_info_t * dip,char * prom_path,dev_info_t ** alt_dipp)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
i_devname_to_promname(char * dev_name,char * ret_buf,size_t len)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 *
find_alternate_node(dev_info_t * parent_dip,major_t major)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
resolve_devfs_name(char * name,char * buffer)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 *
i_convert_boot_device_name(char * cur_path,char * new_path,size_t * len)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 *
get_parent(dev_info_t * dip,struct parinfo * info)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