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