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 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <regex.h> 27 #include <devfsadm.h> 28 #include <stdio.h> 29 #include <strings.h> 30 #include <stdlib.h> 31 #include <limits.h> 32 #include <ctype.h> 33 #include <sys/mc_amd.h> 34 #include <bsm/devalloc.h> 35 36 extern int system_labeled; 37 38 static int lp(di_minor_t minor, di_node_t node); 39 static int serial_dialout(di_minor_t minor, di_node_t node); 40 static int serial(di_minor_t minor, di_node_t node); 41 static int diskette(di_minor_t minor, di_node_t node); 42 static int vt00(di_minor_t minor, di_node_t node); 43 static int kdmouse(di_minor_t minor, di_node_t node); 44 static int bmc(di_minor_t minor, di_node_t node); 45 static int smbios(di_minor_t minor, di_node_t node); 46 static int agp_process(di_minor_t minor, di_node_t node); 47 static int drm_node(di_minor_t minor, di_node_t node); 48 static int mc_node(di_minor_t minor, di_node_t node); 49 static int xsvc(di_minor_t minor, di_node_t node); 50 static int srn(di_minor_t minor, di_node_t node); 51 static int ucode(di_minor_t minor, di_node_t node); 52 static int heci(di_minor_t minor, di_node_t node); 53 54 55 static devfsadm_create_t misc_cbt[] = { 56 { "vt00", "ddi_display", NULL, 57 TYPE_EXACT, ILEVEL_0, vt00 58 }, 59 { "drm", "ddi_display:drm", NULL, 60 TYPE_EXACT, ILEVEL_0, drm_node 61 }, 62 { "mouse", "ddi_mouse", "mouse8042", 63 TYPE_EXACT | DRV_EXACT, ILEVEL_0, kdmouse 64 }, 65 { "pseudo", "ddi_pseudo", "bmc", 66 TYPE_EXACT | DRV_EXACT, ILEVEL_0, bmc, 67 }, 68 { "pseudo", "ddi_pseudo", "smbios", 69 TYPE_EXACT | DRV_EXACT, ILEVEL_1, smbios, 70 }, 71 /* floppies share the same class, but not link regex, as hard disks */ 72 { "disk", "ddi_block:diskette", NULL, 73 TYPE_EXACT, ILEVEL_1, diskette 74 }, 75 { "parallel", "ddi_printer", NULL, 76 TYPE_EXACT, ILEVEL_1, lp 77 }, 78 { "serial", "ddi_serial:mb", NULL, 79 TYPE_EXACT, ILEVEL_1, serial 80 }, 81 { "serial", "ddi_serial:dialout,mb", NULL, 82 TYPE_EXACT, ILEVEL_1, serial_dialout 83 }, 84 { "agp", "ddi_agp:pseudo", NULL, 85 TYPE_EXACT, ILEVEL_0, agp_process 86 }, 87 { "agp", "ddi_agp:target", NULL, 88 TYPE_EXACT, ILEVEL_0, agp_process 89 }, 90 { "agp", "ddi_agp:cpugart", NULL, 91 TYPE_EXACT, ILEVEL_0, agp_process 92 }, 93 { "agp", "ddi_agp:master", NULL, 94 TYPE_EXACT, ILEVEL_0, agp_process 95 }, 96 { "pseudo", "ddi_pseudo", NULL, 97 TYPE_EXACT, ILEVEL_0, xsvc 98 }, 99 { "pseudo", "ddi_pseudo", NULL, 100 TYPE_EXACT, ILEVEL_0, srn 101 }, 102 { "memory-controller", "ddi_mem_ctrl", NULL, 103 TYPE_EXACT, ILEVEL_0, mc_node 104 }, 105 { "pseudo", "ddi_pseudo", "ucode", 106 TYPE_EXACT | DRV_EXACT, ILEVEL_0, ucode, 107 }, 108 { "pseudo", "ddi_pseudo", "heci", 109 TYPE_EXACT | DRV_EXACT, ILEVEL_0, heci, 110 } 111 }; 112 113 DEVFSADM_CREATE_INIT_V0(misc_cbt); 114 115 static char *debug_mid = "misc_mid"; 116 117 typedef enum { 118 DRIVER_AGPPSEUDO = 0, 119 DRIVER_AGPTARGET, 120 DRIVER_CPUGART, 121 DRIVER_AGPMASTER_DRM_I915, 122 DRIVER_AGPMASTER_DRM_RADEON, 123 DRIVER_AGPMASTER_VGATEXT, 124 DRIVER_UNKNOWN 125 } driver_defs_t; 126 127 typedef struct { 128 char *driver_name; 129 int index; 130 } driver_name_table_entry_t; 131 132 static driver_name_table_entry_t driver_name_table[] = { 133 { "agpgart", DRIVER_AGPPSEUDO }, 134 { "agptarget", DRIVER_AGPTARGET }, 135 { "amd64_gart", DRIVER_CPUGART }, 136 /* AGP master device managed by drm driver */ 137 { "i915", DRIVER_AGPMASTER_DRM_I915 }, 138 { "radeon", DRIVER_AGPMASTER_DRM_RADEON }, 139 { "vgatext", DRIVER_AGPMASTER_VGATEXT }, 140 { NULL, DRIVER_UNKNOWN } 141 }; 142 143 static devfsadm_enumerate_t agptarget_rules[1] = 144 { "^agp$/^agptarget([0-9]+)$", 1, MATCH_ALL }; 145 static devfsadm_enumerate_t cpugart_rules[1] = 146 { "^agp$/^cpugart([0-9]+)$", 1, MATCH_ALL }; 147 static devfsadm_enumerate_t agpmaster_rules[1] = 148 { "^agp$/^agpmaster([0-9]+)$", 1, MATCH_ALL }; 149 150 static devfsadm_remove_t misc_remove_cbt[] = { 151 { "vt", "vt[0-9][0-9]", RM_PRE|RM_ALWAYS, 152 ILEVEL_0, devfsadm_rm_all 153 }, 154 { "pseudo", "^ucode$", RM_ALWAYS | RM_PRE | RM_HOT, 155 ILEVEL_0, devfsadm_rm_all 156 }, 157 { "mouse", "^kdmouse$", RM_ALWAYS | RM_PRE, 158 ILEVEL_0, devfsadm_rm_all 159 }, 160 { "disk", "^(diskette|rdiskette)([0-9]*)$", 161 RM_ALWAYS | RM_PRE, ILEVEL_1, devfsadm_rm_all 162 }, 163 { "parallel", "^(lp|ecpp)([0-9]+)$", RM_ALWAYS | RM_PRE, 164 ILEVEL_1, devfsadm_rm_all 165 }, 166 { "serial", "^(tty|ttyd)([0-9]+)$", RM_ALWAYS | RM_PRE, 167 ILEVEL_1, devfsadm_rm_all 168 }, 169 { "serial", "^tty[a-z]$", RM_ALWAYS | RM_PRE, 170 ILEVEL_1, devfsadm_rm_all 171 } 172 }; 173 174 DEVFSADM_REMOVE_INIT_V0(misc_remove_cbt); 175 176 /* 177 * Handles minor node type "ddi_display", in addition to generic processing 178 * done by display(). 179 * 180 * This creates a /dev/vt00 link to /dev/fb, for backwards compatibility. 181 */ 182 /* ARGSUSED */ 183 int 184 vt00(di_minor_t minor, di_node_t node) 185 { 186 (void) devfsadm_secondary_link("vt00", "fb", 0); 187 return (DEVFSADM_CONTINUE); 188 } 189 190 /* 191 * type=ddi_block:diskette;addr=0,0;minor=c diskette 192 * type=ddi_block:diskette;addr=0,0;minor=c,raw rdiskette 193 * type=ddi_block:diskette;addr1=0;minor=c diskette\A2 194 * type=ddi_block:diskette;addr1=0;minor=c,raw rdiskette\A2 195 */ 196 static int 197 diskette(di_minor_t minor, di_node_t node) 198 { 199 int flags = 0; 200 char *a2; 201 char link[PATH_MAX]; 202 char *addr = di_bus_addr(node); 203 char *mn = di_minor_name(minor); 204 205 if (system_labeled) 206 flags = DA_ADD|DA_FLOPPY; 207 208 if (strcmp(addr, "0,0") == 0) { 209 if (strcmp(mn, "c") == 0) { 210 (void) devfsadm_mklink("diskette", node, minor, flags); 211 } else if (strcmp(mn, "c,raw") == 0) { 212 (void) devfsadm_mklink("rdiskette", node, minor, flags); 213 } 214 215 } 216 217 if (addr[0] == '0') { 218 if ((a2 = strchr(addr, ',')) != NULL) { 219 a2++; 220 if (strcmp(mn, "c") == 0) { 221 (void) strcpy(link, "diskette"); 222 (void) strcat(link, a2); 223 (void) devfsadm_mklink(link, node, minor, 224 flags); 225 } else if (strcmp(mn, "c,raw") == 0) { 226 (void) strcpy(link, "rdiskette"); 227 (void) strcat(link, a2); 228 (void) devfsadm_mklink(link, node, minor, 229 flags); 230 } 231 } 232 } 233 234 return (DEVFSADM_CONTINUE); 235 } 236 237 /* 238 * type=ddi_printer;name=lp;addr=1,3bc lp0 239 * type=ddi_printer;name=lp;addr=1,378 lp1 240 * type=ddi_printer;name=lp;addr=1,278 lp2 241 */ 242 static int 243 lp(di_minor_t minor, di_node_t node) 244 { 245 char *addr = di_bus_addr(node); 246 char *buf; 247 char path[PATH_MAX + 1]; 248 devfsadm_enumerate_t rules[1] = {"^ecpp([0-9]+)$", 1, MATCH_ALL}; 249 250 if (strcmp(addr, "1,3bc") == 0) { 251 (void) devfsadm_mklink("lp0", node, minor, 0); 252 253 } else if (strcmp(addr, "1,378") == 0) { 254 (void) devfsadm_mklink("lp1", node, minor, 0); 255 256 } else if (strcmp(addr, "1,278") == 0) { 257 (void) devfsadm_mklink("lp2", node, minor, 0); 258 } 259 260 if (strcmp(di_driver_name(node), "ecpp") != 0) { 261 return (DEVFSADM_CONTINUE); 262 } 263 264 if ((buf = di_devfs_path(node)) == NULL) { 265 return (DEVFSADM_CONTINUE); 266 } 267 268 (void) snprintf(path, sizeof (path), "%s:%s", 269 buf, di_minor_name(minor)); 270 271 di_devfs_path_free(buf); 272 273 if (devfsadm_enumerate_int(path, 0, &buf, rules, 1)) { 274 return (DEVFSADM_CONTINUE); 275 } 276 277 (void) snprintf(path, sizeof (path), "ecpp%s", buf); 278 free(buf); 279 (void) devfsadm_mklink(path, node, minor, 0); 280 return (DEVFSADM_CONTINUE); 281 } 282 283 /* 284 * type=ddi_serial:mb;minor=a tty00 285 * type=ddi_serial:mb;minor=b tty01 286 * type=ddi_serial:mb;minor=c tty02 287 * type=ddi_serial:mb;minor=d tty03 288 */ 289 static int 290 serial(di_minor_t minor, di_node_t node) 291 { 292 293 char *mn = di_minor_name(minor); 294 char link[PATH_MAX]; 295 296 (void) strcpy(link, "tty"); 297 (void) strcat(link, mn); 298 (void) devfsadm_mklink(link, node, minor, 0); 299 300 if (strcmp(mn, "a") == 0) { 301 (void) devfsadm_mklink("tty00", node, minor, 0); 302 303 } else if (strcmp(mn, "b") == 0) { 304 (void) devfsadm_mklink("tty01", node, minor, 0); 305 306 } else if (strcmp(mn, "c") == 0) { 307 (void) devfsadm_mklink("tty02", node, minor, 0); 308 309 } else if (strcmp(mn, "d") == 0) { 310 (void) devfsadm_mklink("tty03", node, minor, 0); 311 } 312 return (DEVFSADM_CONTINUE); 313 } 314 315 /* 316 * type=ddi_serial:dialout,mb;minor=a,cu ttyd0 317 * type=ddi_serial:dialout,mb;minor=b,cu ttyd1 318 * type=ddi_serial:dialout,mb;minor=c,cu ttyd2 319 * type=ddi_serial:dialout,mb;minor=d,cu ttyd3 320 */ 321 static int 322 serial_dialout(di_minor_t minor, di_node_t node) 323 { 324 char *mn = di_minor_name(minor); 325 326 if (strcmp(mn, "a,cu") == 0) { 327 (void) devfsadm_mklink("ttyd0", node, minor, 0); 328 (void) devfsadm_mklink("cua0", node, minor, 0); 329 330 } else if (strcmp(mn, "b,cu") == 0) { 331 (void) devfsadm_mklink("ttyd1", node, minor, 0); 332 (void) devfsadm_mklink("cua1", node, minor, 0); 333 334 } else if (strcmp(mn, "c,cu") == 0) { 335 (void) devfsadm_mklink("ttyd2", node, minor, 0); 336 (void) devfsadm_mklink("cua2", node, minor, 0); 337 338 } else if (strcmp(mn, "d,cu") == 0) { 339 (void) devfsadm_mklink("ttyd3", node, minor, 0); 340 (void) devfsadm_mklink("cua3", node, minor, 0); 341 } 342 return (DEVFSADM_CONTINUE); 343 } 344 345 static int 346 kdmouse(di_minor_t minor, di_node_t node) 347 { 348 (void) devfsadm_mklink("kdmouse", node, minor, 0); 349 return (DEVFSADM_CONTINUE); 350 } 351 352 static int 353 bmc(di_minor_t minor, di_node_t node) 354 { 355 (void) devfsadm_mklink("bmc", node, minor, 0); 356 return (DEVFSADM_CONTINUE); 357 } 358 359 static int 360 smbios(di_minor_t minor, di_node_t node) 361 { 362 (void) devfsadm_mklink("smbios", node, minor, 0); 363 return (DEVFSADM_CONTINUE); 364 } 365 366 static int 367 agp_process(di_minor_t minor, di_node_t node) 368 { 369 char *minor_nm, *drv_nm; 370 char *devfspath; 371 char *I_path, *p_path, *buf; 372 char *name = (char *)NULL; 373 int i, index; 374 devfsadm_enumerate_t rules[1]; 375 376 minor_nm = di_minor_name(minor); 377 drv_nm = di_driver_name(node); 378 379 if ((minor_nm == NULL) || (drv_nm == NULL)) { 380 return (DEVFSADM_CONTINUE); 381 } 382 383 devfsadm_print(debug_mid, "agp_process: minor=%s node=%s\n", 384 minor_nm, di_node_name(node)); 385 386 devfspath = di_devfs_path(node); 387 if (devfspath == NULL) { 388 devfsadm_print(debug_mid, "agp_process: devfspath is NULL\n"); 389 return (DEVFSADM_CONTINUE); 390 } 391 392 I_path = (char *)malloc(PATH_MAX); 393 394 if (I_path == NULL) { 395 di_devfs_path_free(devfspath); 396 devfsadm_print(debug_mid, "agp_process: malloc failed\n"); 397 return (DEVFSADM_CONTINUE); 398 } 399 400 p_path = (char *)malloc(PATH_MAX); 401 402 if (p_path == NULL) { 403 devfsadm_print(debug_mid, "agp_process: malloc failed\n"); 404 di_devfs_path_free(devfspath); 405 free(I_path); 406 return (DEVFSADM_CONTINUE); 407 } 408 409 (void) strlcpy(p_path, devfspath, PATH_MAX); 410 (void) strlcat(p_path, ":", PATH_MAX); 411 (void) strlcat(p_path, minor_nm, PATH_MAX); 412 di_devfs_path_free(devfspath); 413 414 devfsadm_print(debug_mid, "agp_process: path %s\n", p_path); 415 416 for (i = 0; ; i++) { 417 if ((driver_name_table[i].driver_name == NULL) || 418 (strcmp(drv_nm, driver_name_table[i].driver_name) == 0)) { 419 index = driver_name_table[i].index; 420 break; 421 } 422 } 423 switch (index) { 424 case DRIVER_AGPPSEUDO: 425 devfsadm_print(debug_mid, 426 "agp_process: psdeudo driver name\n"); 427 name = "agpgart"; 428 (void) snprintf(I_path, PATH_MAX, "%s", name); 429 devfsadm_print(debug_mid, 430 "mklink %s -> %s\n", I_path, p_path); 431 432 (void) devfsadm_mklink(I_path, node, minor, 0); 433 434 free(I_path); 435 free(p_path); 436 return (DEVFSADM_CONTINUE); 437 case DRIVER_AGPTARGET: 438 devfsadm_print(debug_mid, 439 "agp_process: target driver name\n"); 440 rules[0] = agptarget_rules[0]; 441 name = "agptarget"; 442 break; 443 case DRIVER_CPUGART: 444 devfsadm_print(debug_mid, 445 "agp_process: cpugart driver name\n"); 446 rules[0] = cpugart_rules[0]; 447 name = "cpugart"; 448 break; 449 case DRIVER_AGPMASTER_DRM_I915: 450 case DRIVER_AGPMASTER_DRM_RADEON: 451 case DRIVER_AGPMASTER_VGATEXT: 452 devfsadm_print(debug_mid, 453 "agp_process: agpmaster driver name\n"); 454 rules[0] = agpmaster_rules[0]; 455 name = "agpmaster"; 456 break; 457 case DRIVER_UNKNOWN: 458 devfsadm_print(debug_mid, 459 "agp_process: unknown driver name=%s\n", drv_nm); 460 free(I_path); 461 free(p_path); 462 return (DEVFSADM_CONTINUE); 463 } 464 465 if (devfsadm_enumerate_int(p_path, 0, &buf, rules, 1)) { 466 devfsadm_print(debug_mid, "agp_process: exit/coninue\n"); 467 free(I_path); 468 free(p_path); 469 return (DEVFSADM_CONTINUE); 470 } 471 472 473 (void) snprintf(I_path, PATH_MAX, "agp/%s%s", name, buf); 474 475 devfsadm_print(debug_mid, "agp_process: p_path=%s buf=%s\n", 476 p_path, buf); 477 478 free(buf); 479 480 devfsadm_print(debug_mid, "mklink %s -> %s\n", I_path, p_path); 481 482 (void) devfsadm_mklink(I_path, node, minor, 0); 483 484 free(p_path); 485 free(I_path); 486 487 return (DEVFSADM_CONTINUE); 488 } 489 490 static int 491 drm_node(di_minor_t minor, di_node_t node) 492 { 493 char *minor_nm, *drv_nm; 494 char *devfspath; 495 char *I_path, *p_path, *buf; 496 char *name = "card"; 497 498 devfsadm_enumerate_t drm_rules[1] = {"^dri$/^card([0-9]+)$", 1, 499 MATCH_ALL }; 500 501 502 minor_nm = di_minor_name(minor); 503 drv_nm = di_driver_name(node); 504 if ((minor_nm == NULL) || (drv_nm == NULL)) { 505 return (DEVFSADM_CONTINUE); 506 } 507 508 devfsadm_print(debug_mid, "drm_node: minor=%s node=%s type=%s\n", 509 minor_nm, di_node_name(node), di_minor_nodetype(minor)); 510 511 devfspath = di_devfs_path(node); 512 if (devfspath == NULL) { 513 devfsadm_print(debug_mid, "drm_node: devfspath is NULL\n"); 514 return (DEVFSADM_CONTINUE); 515 } 516 517 I_path = (char *)malloc(PATH_MAX); 518 519 if (I_path == NULL) { 520 di_devfs_path_free(devfspath); 521 devfsadm_print(debug_mid, "drm_node: malloc failed\n"); 522 return (DEVFSADM_CONTINUE); 523 } 524 525 p_path = (char *)malloc(PATH_MAX); 526 527 if (p_path == NULL) { 528 devfsadm_print(debug_mid, "drm_node: malloc failed\n"); 529 di_devfs_path_free(devfspath); 530 free(I_path); 531 return (DEVFSADM_CONTINUE); 532 } 533 534 (void) strlcpy(p_path, devfspath, PATH_MAX); 535 (void) strlcat(p_path, ":", PATH_MAX); 536 (void) strlcat(p_path, minor_nm, PATH_MAX); 537 di_devfs_path_free(devfspath); 538 539 devfsadm_print(debug_mid, "drm_node: p_path %s\n", p_path); 540 541 if (devfsadm_enumerate_int(p_path, 0, &buf, drm_rules, 1)) { 542 free(p_path); 543 devfsadm_print(debug_mid, "drm_node: exit/coninue\n"); 544 return (DEVFSADM_CONTINUE); 545 } 546 (void) snprintf(I_path, PATH_MAX, "dri/%s%s", name, buf); 547 548 devfsadm_print(debug_mid, "drm_node: p_path=%s buf=%s\n", 549 p_path, buf); 550 551 free(buf); 552 553 devfsadm_print(debug_mid, "mklink %s -> %s\n", I_path, p_path); 554 (void) devfsadm_mklink(I_path, node, minor, 0); 555 556 free(p_path); 557 free(I_path); 558 559 return (0); 560 } 561 562 /* 563 * /dev/mc/mc<chipid> -> /devices/.../pci1022,1102@<chipid+24>,2:mc-amd 564 */ 565 static int 566 mc_node(di_minor_t minor, di_node_t node) 567 { 568 const char *minorname = di_minor_name(minor); 569 const char *busaddr = di_bus_addr(node); 570 char linkpath[PATH_MAX]; 571 int unitaddr; 572 char *c; 573 574 if (minorname == NULL || busaddr == NULL) 575 return (DEVFSADM_CONTINUE); 576 577 errno = 0; 578 unitaddr = strtol(busaddr, &c, 16); 579 580 if (errno != 0) 581 return (DEVFSADM_CONTINUE); 582 583 if (unitaddr == 0) { 584 (void) snprintf(linkpath, sizeof (linkpath), "mc/mc"); 585 } else if (unitaddr >= MC_AMD_DEV_OFFSET) { 586 (void) snprintf(linkpath, sizeof (linkpath), "mc/mc%u", 587 unitaddr - MC_AMD_DEV_OFFSET); 588 } else { 589 (void) snprintf(linkpath, sizeof (linkpath), "mc/mc%u", 590 minor->dev_minor); 591 } 592 (void) devfsadm_mklink(linkpath, node, minor, 0); 593 return (DEVFSADM_CONTINUE); 594 } 595 596 /* 597 * Creates \M0 devlink for xsvc node 598 */ 599 static int 600 xsvc(di_minor_t minor, di_node_t node) 601 { 602 char *mn; 603 604 if (strcmp(di_node_name(node), "xsvc") != 0) 605 return (DEVFSADM_CONTINUE); 606 607 mn = di_minor_name(minor); 608 if (mn == NULL) 609 return (DEVFSADM_CONTINUE); 610 611 (void) devfsadm_mklink(mn, node, minor, 0); 612 return (DEVFSADM_CONTINUE); 613 } 614 615 /* 616 * Creates \M0 devlink for srn device 617 */ 618 static int 619 srn(di_minor_t minor, di_node_t node) 620 { 621 char *mn; 622 623 if (strcmp(di_node_name(node), "srn") != 0) 624 return (DEVFSADM_CONTINUE); 625 626 mn = di_minor_name(minor); 627 if (mn == NULL) 628 return (DEVFSADM_CONTINUE); 629 630 (void) devfsadm_mklink(mn, node, minor, 0); 631 return (DEVFSADM_CONTINUE); 632 } 633 634 /* 635 * /dev/ucode -> /devices/pseudo/ucode@0:ucode 636 */ 637 static int 638 ucode(di_minor_t minor, di_node_t node) 639 { 640 (void) devfsadm_mklink("ucode", node, minor, 0); 641 return (DEVFSADM_CONTINUE); 642 } 643 644 static int 645 heci(di_minor_t minor, di_node_t node) 646 { 647 if (strcmp(di_minor_name(minor), "AMT") == 0) { 648 (void) devfsadm_mklink("heci", node, minor, 0); 649 } 650 return (DEVFSADM_CONTINUE); 651 } 652