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