1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2022 Oxide Computer Company 14 */ 15 16 #include <err.h> 17 #include <stdio.h> 18 #include <unistd.h> 19 #include <ofmt.h> 20 #include <strings.h> 21 #include <sys/pci.h> 22 23 #include "pcieadm.h" 24 25 typedef struct pcieadm_show_devs { 26 pcieadm_t *psd_pia; 27 ofmt_handle_t psd_ofmt; 28 boolean_t psd_funcs; 29 int psd_nfilts; 30 char **psd_filts; 31 boolean_t *psd_used; 32 uint_t psd_nprint; 33 } pcieadm_show_devs_t; 34 35 typedef enum pcieadm_show_devs_otype { 36 PCIEADM_SDO_VID, 37 PCIEADM_SDO_DID, 38 PCIEADM_SDO_BDF, 39 PCIEADM_SDO_BDF_BUS, 40 PCIEADM_SDO_BDF_DEV, 41 PCIEADM_SDO_BDF_FUNC, 42 PCIEADM_SDO_DRIVER, 43 PCIEADM_SDO_INSTANCE, 44 PCIEADM_SDO_INSTNUM, 45 PCIEADM_SDO_TYPE, 46 PCIEADM_SDO_VENDOR, 47 PCIEADM_SDO_DEVICE, 48 PCIEADM_SDO_PATH, 49 PCIEADM_SDO_MAXSPEED, 50 PCIEADM_SDO_MAXWIDTH, 51 PCIEADM_SDO_CURSPEED, 52 PCIEADM_SDO_CURWIDTH, 53 PCIEADM_SDO_SUPSPEEDS 54 } pcieadm_show_devs_otype_t; 55 56 typedef struct pcieadm_show_devs_ofmt { 57 int psdo_vid; 58 int psdo_did; 59 uint_t psdo_bus; 60 uint_t psdo_dev; 61 uint_t psdo_func; 62 const char *psdo_path; 63 const char *psdo_vendor; 64 const char *psdo_device; 65 const char *psdo_driver; 66 int psdo_instance; 67 int psdo_mwidth; 68 int psdo_cwidth; 69 int64_t psdo_mspeed; 70 int64_t psdo_cspeed; 71 int psdo_nspeeds; 72 int64_t *psdo_sspeeds; 73 } pcieadm_show_devs_ofmt_t; 74 75 static uint_t 76 pcieadm_speed2gen(int64_t speed) 77 { 78 if (speed == 2500000000LL) { 79 return (1); 80 } else if (speed == 5000000000LL) { 81 return (2); 82 } else if (speed == 8000000000LL) { 83 return (3); 84 } else if (speed == 16000000000LL) { 85 return (4); 86 } else if (speed == 32000000000LL) { 87 return (5); 88 } else { 89 return (0); 90 } 91 } 92 93 static const char * 94 pcieadm_speed2str(int64_t speed) 95 { 96 if (speed == 2500000000LL) { 97 return ("2.5"); 98 } else if (speed == 5000000000LL) { 99 return ("5.0"); 100 } else if (speed == 8000000000LL) { 101 return ("8.0"); 102 } else if (speed == 16000000000LL) { 103 return ("16.0"); 104 } else if (speed == 32000000000LL) { 105 return ("32.0"); 106 } else { 107 return (NULL); 108 } 109 } 110 111 static boolean_t 112 pcieadm_show_devs_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen) 113 { 114 const char *str; 115 pcieadm_show_devs_ofmt_t *psdo = ofarg->ofmt_cbarg; 116 boolean_t first = B_TRUE; 117 118 switch (ofarg->ofmt_id) { 119 case PCIEADM_SDO_BDF: 120 if (snprintf(buf, buflen, "%x/%x/%x", psdo->psdo_bus, 121 psdo->psdo_dev, psdo->psdo_func) >= buflen) { 122 return (B_FALSE); 123 } 124 break; 125 case PCIEADM_SDO_BDF_BUS: 126 if (snprintf(buf, buflen, "%x", psdo->psdo_bus) >= buflen) { 127 return (B_FALSE); 128 } 129 break; 130 case PCIEADM_SDO_BDF_DEV: 131 if (snprintf(buf, buflen, "%x", psdo->psdo_dev) >= buflen) { 132 return (B_FALSE); 133 } 134 break; 135 case PCIEADM_SDO_BDF_FUNC: 136 if (snprintf(buf, buflen, "%x", psdo->psdo_func) >= buflen) { 137 return (B_FALSE); 138 } 139 break; 140 case PCIEADM_SDO_INSTANCE: 141 if (psdo->psdo_driver == NULL || psdo->psdo_instance == -1) { 142 (void) snprintf(buf, buflen, "--"); 143 } else if (snprintf(buf, buflen, "%s%d", psdo->psdo_driver, 144 psdo->psdo_instance) >= buflen) { 145 return (B_FALSE); 146 } 147 break; 148 case PCIEADM_SDO_DRIVER: 149 if (psdo->psdo_driver == NULL) { 150 (void) snprintf(buf, buflen, "--"); 151 } else if (strlcpy(buf, psdo->psdo_driver, buflen) >= buflen) { 152 return (B_FALSE); 153 } 154 break; 155 case PCIEADM_SDO_INSTNUM: 156 if (psdo->psdo_instance == -1) { 157 (void) snprintf(buf, buflen, "--"); 158 } else if (snprintf(buf, buflen, "%d", psdo->psdo_instance) >= 159 buflen) { 160 return (B_FALSE); 161 } 162 break; 163 case PCIEADM_SDO_PATH: 164 if (strlcat(buf, psdo->psdo_path, buflen) >= buflen) { 165 return (B_TRUE); 166 } 167 break; 168 case PCIEADM_SDO_VID: 169 if (psdo->psdo_vid == -1) { 170 (void) strlcat(buf, "--", buflen); 171 } else if (snprintf(buf, buflen, "%x", psdo->psdo_vid) >= 172 buflen) { 173 return (B_FALSE); 174 } 175 break; 176 case PCIEADM_SDO_DID: 177 if (psdo->psdo_did == -1) { 178 (void) strlcat(buf, "--", buflen); 179 } else if (snprintf(buf, buflen, "%x", psdo->psdo_did) >= 180 buflen) { 181 return (B_FALSE); 182 } 183 break; 184 case PCIEADM_SDO_VENDOR: 185 if (strlcat(buf, psdo->psdo_vendor, buflen) >= buflen) { 186 return (B_FALSE); 187 } 188 break; 189 case PCIEADM_SDO_DEVICE: 190 if (strlcat(buf, psdo->psdo_device, buflen) >= buflen) { 191 return (B_FALSE); 192 } 193 break; 194 case PCIEADM_SDO_MAXWIDTH: 195 if (psdo->psdo_mwidth <= 0) { 196 (void) strlcat(buf, "--", buflen); 197 } else if (snprintf(buf, buflen, "x%u", psdo->psdo_mwidth) >= 198 buflen) { 199 return (B_FALSE); 200 } 201 break; 202 case PCIEADM_SDO_CURWIDTH: 203 if (psdo->psdo_cwidth <= 0) { 204 (void) strlcat(buf, "--", buflen); 205 } else if (snprintf(buf, buflen, "x%u", psdo->psdo_cwidth) >= 206 buflen) { 207 return (B_FALSE); 208 } 209 break; 210 case PCIEADM_SDO_MAXSPEED: 211 str = pcieadm_speed2str(psdo->psdo_mspeed); 212 if (str == NULL) { 213 (void) strlcat(buf, "--", buflen); 214 } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { 215 return (B_FALSE); 216 } 217 break; 218 case PCIEADM_SDO_CURSPEED: 219 str = pcieadm_speed2str(psdo->psdo_cspeed); 220 if (str == NULL) { 221 (void) strlcat(buf, "--", buflen); 222 } else if (snprintf(buf, buflen, "%s GT/s", str) >= buflen) { 223 return (B_FALSE); 224 } 225 break; 226 case PCIEADM_SDO_SUPSPEEDS: 227 buf[0] = 0; 228 for (int i = 0; i < psdo->psdo_nspeeds; i++) { 229 const char *str; 230 231 str = pcieadm_speed2str(psdo->psdo_sspeeds[i]); 232 if (str == NULL) { 233 continue; 234 } 235 236 if (!first) { 237 if (strlcat(buf, ",", buflen) >= buflen) { 238 return (B_FALSE); 239 } 240 } 241 first = B_FALSE; 242 243 if (strlcat(buf, str, buflen) >= buflen) { 244 return (B_FALSE); 245 } 246 } 247 break; 248 case PCIEADM_SDO_TYPE: 249 if (pcieadm_speed2gen(psdo->psdo_cspeed) == 0 || 250 psdo->psdo_mwidth == -1) { 251 if (strlcat(buf, "PCI", buflen) >= buflen) { 252 return (B_FALSE); 253 } 254 } else { 255 if (snprintf(buf, buflen, "PCIe Gen %ux%u", 256 pcieadm_speed2gen(psdo->psdo_cspeed), 257 psdo->psdo_cwidth) >= buflen) { 258 return (B_FALSE); 259 } 260 } 261 break; 262 default: 263 abort(); 264 } 265 return (B_TRUE); 266 } 267 268 static const char *pcieadm_show_dev_fields = "bdf,type,instance,device"; 269 static const char *pcieadm_show_dev_speeds = 270 "bdf,driver,maxspeed,curspeed,maxwidth,curwidth,supspeeds"; 271 static const ofmt_field_t pcieadm_show_dev_ofmt[] = { 272 { "VID", 6, PCIEADM_SDO_VID, pcieadm_show_devs_ofmt_cb }, 273 { "DID", 6, PCIEADM_SDO_DID, pcieadm_show_devs_ofmt_cb }, 274 { "BDF", 8, PCIEADM_SDO_BDF, pcieadm_show_devs_ofmt_cb }, 275 { "DRIVER", 15, PCIEADM_SDO_DRIVER, pcieadm_show_devs_ofmt_cb }, 276 { "INSTANCE", 15, PCIEADM_SDO_INSTANCE, pcieadm_show_devs_ofmt_cb }, 277 { "INSTNUM", 8, PCIEADM_SDO_INSTNUM, pcieadm_show_devs_ofmt_cb }, 278 { "TYPE", 15, PCIEADM_SDO_TYPE, pcieadm_show_devs_ofmt_cb }, 279 { "VENDOR", 30, PCIEADM_SDO_VENDOR, pcieadm_show_devs_ofmt_cb }, 280 { "DEVICE", 30, PCIEADM_SDO_DEVICE, pcieadm_show_devs_ofmt_cb }, 281 { "PATH", 30, PCIEADM_SDO_PATH, pcieadm_show_devs_ofmt_cb }, 282 { "BUS", 4, PCIEADM_SDO_BDF_BUS, pcieadm_show_devs_ofmt_cb }, 283 { "DEV", 4, PCIEADM_SDO_BDF_DEV, pcieadm_show_devs_ofmt_cb }, 284 { "FUNC", 4, PCIEADM_SDO_BDF_FUNC, pcieadm_show_devs_ofmt_cb }, 285 { "MAXSPEED", 10, PCIEADM_SDO_MAXSPEED, pcieadm_show_devs_ofmt_cb }, 286 { "MAXWIDTH", 10, PCIEADM_SDO_MAXWIDTH, pcieadm_show_devs_ofmt_cb }, 287 { "CURSPEED", 10, PCIEADM_SDO_CURSPEED, pcieadm_show_devs_ofmt_cb }, 288 { "CURWIDTH", 10, PCIEADM_SDO_CURWIDTH, pcieadm_show_devs_ofmt_cb }, 289 { "SUPSPEEDS", 20, PCIEADM_SDO_SUPSPEEDS, pcieadm_show_devs_ofmt_cb }, 290 { NULL, 0, 0, NULL } 291 }; 292 293 static boolean_t 294 pcieadm_show_devs_match(pcieadm_show_devs_t *psd, 295 pcieadm_show_devs_ofmt_t *psdo) 296 { 297 char dinst[128], bdf[128]; 298 299 if (psd->psd_nfilts == 0) { 300 return (B_TRUE); 301 } 302 303 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1) { 304 (void) snprintf(dinst, sizeof (dinst), "%s%d", 305 psdo->psdo_driver, psdo->psdo_instance); 306 } 307 (void) snprintf(bdf, sizeof (bdf), "%x/%x/%x", psdo->psdo_bus, 308 psdo->psdo_dev, psdo->psdo_func); 309 310 for (uint_t i = 0; i < psd->psd_nfilts; i++) { 311 const char *filt = psd->psd_filts[i]; 312 313 if (strcmp(filt, psdo->psdo_path) == 0) { 314 psd->psd_used[i] = B_TRUE; 315 return (B_TRUE); 316 } 317 318 if (strcmp(filt, bdf) == 0) { 319 psd->psd_used[i] = B_TRUE; 320 return (B_TRUE); 321 } 322 323 if (psdo->psdo_driver != NULL && 324 strcmp(filt, psdo->psdo_driver) == 0) { 325 psd->psd_used[i] = B_TRUE; 326 return (B_TRUE); 327 } 328 329 if (psdo->psdo_driver != NULL && psdo->psdo_instance != -1 && 330 strcmp(filt, dinst) == 0) { 331 psd->psd_used[i] = B_TRUE; 332 return (B_TRUE); 333 } 334 335 if (strncmp("/devices", filt, strlen("/devices")) == 0) { 336 filt += strlen("/devices"); 337 } 338 339 if (strcmp(filt, psdo->psdo_path) == 0) { 340 psd->psd_used[i] = B_TRUE; 341 return (B_TRUE); 342 } 343 } 344 return (B_FALSE); 345 } 346 347 static int 348 pcieadm_show_devs_walk_cb(di_node_t node, void *arg) 349 { 350 int nprop, *regs = NULL, *did, *vid, *mwidth, *cwidth; 351 int64_t *mspeed, *cspeed, *sspeeds; 352 char *path = NULL; 353 pcieadm_show_devs_t *psd = arg; 354 int ret = DI_WALK_CONTINUE; 355 char venstr[64], devstr[64]; 356 pcieadm_show_devs_ofmt_t oarg; 357 pcidb_hdl_t *pcidb = psd->psd_pia->pia_pcidb; 358 359 bzero(&oarg, sizeof (oarg)); 360 361 path = di_devfs_path(node); 362 if (path == NULL) { 363 err(EXIT_FAILURE, "failed to construct devfs path for node: " 364 "%s (%s)", di_node_name(node)); 365 } 366 367 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s); 368 if (nprop <= 0) { 369 errx(EXIT_FAILURE, "failed to lookup regs array for %s", 370 path); 371 } 372 373 oarg.psdo_path = path; 374 oarg.psdo_bus = PCI_REG_BUS_G(regs[0]); 375 oarg.psdo_dev = PCI_REG_DEV_G(regs[0]); 376 oarg.psdo_func = PCI_REG_FUNC_G(regs[0]); 377 378 if (oarg.psdo_func != 0 && !psd->psd_funcs) { 379 goto done; 380 } 381 382 oarg.psdo_driver = di_driver_name(node); 383 oarg.psdo_instance = di_instance(node); 384 385 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did); 386 if (nprop != 1) { 387 oarg.psdo_did = -1; 388 } else { 389 oarg.psdo_did = (uint16_t)*did; 390 } 391 392 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid); 393 if (nprop != 1) { 394 oarg.psdo_vid = -1; 395 } else { 396 oarg.psdo_vid = (uint16_t)*vid; 397 } 398 399 oarg.psdo_vendor = "--"; 400 if (oarg.psdo_vid != -1) { 401 pcidb_vendor_t *vend = pcidb_lookup_vendor(pcidb, 402 oarg.psdo_vid); 403 if (vend != NULL) { 404 oarg.psdo_vendor = pcidb_vendor_name(vend); 405 } else { 406 (void) snprintf(venstr, sizeof (venstr), 407 "Unknown vendor: 0x%x", oarg.psdo_vid); 408 oarg.psdo_vendor = venstr; 409 } 410 } 411 412 oarg.psdo_device = "--"; 413 if (oarg.psdo_vid != -1 && oarg.psdo_did != -1) { 414 pcidb_device_t *dev = pcidb_lookup_device(pcidb, 415 oarg.psdo_vid, oarg.psdo_did); 416 if (dev != NULL) { 417 oarg.psdo_device = pcidb_device_name(dev); 418 } else { 419 (void) snprintf(devstr, sizeof (devstr), 420 "Unknown device: 0x%x", oarg.psdo_did); 421 oarg.psdo_device = devstr; 422 } 423 } 424 425 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 426 "pcie-link-maximum-width", &mwidth); 427 if (nprop != 1) { 428 oarg.psdo_mwidth = -1; 429 } else { 430 oarg.psdo_mwidth = *mwidth; 431 } 432 433 nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, 434 "pcie-link-current-width", &cwidth); 435 if (nprop != 1) { 436 oarg.psdo_cwidth = -1; 437 } else { 438 oarg.psdo_cwidth = *cwidth; 439 } 440 441 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 442 "pcie-link-maximum-speed", &mspeed); 443 if (nprop != 1) { 444 oarg.psdo_mspeed = -1; 445 } else { 446 oarg.psdo_mspeed = *mspeed; 447 } 448 449 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 450 "pcie-link-current-speed", &cspeed); 451 if (nprop != 1) { 452 oarg.psdo_cspeed = -1; 453 } else { 454 oarg.psdo_cspeed = *cspeed; 455 } 456 457 nprop = di_prop_lookup_int64(DDI_DEV_T_ANY, node, 458 "pcie-link-supported-speeds", &sspeeds); 459 if (nprop > 0) { 460 oarg.psdo_nspeeds = nprop; 461 oarg.psdo_sspeeds = sspeeds; 462 } else { 463 oarg.psdo_nspeeds = 0; 464 oarg.psdo_sspeeds = NULL; 465 } 466 467 if (pcieadm_show_devs_match(psd, &oarg)) { 468 ofmt_print(psd->psd_ofmt, &oarg); 469 psd->psd_nprint++; 470 } 471 472 done: 473 if (path != NULL) { 474 di_devfs_path_free(path); 475 } 476 477 return (ret); 478 } 479 480 void 481 pcieadm_show_devs_usage(FILE *f) 482 { 483 (void) fprintf(f, "\tshow-devs\t[-F] [-H] [-s | -o field[,...] [-p]] " 484 "[filter...]\n"); 485 } 486 487 static void 488 pcieadm_show_devs_help(const char *fmt, ...) 489 { 490 if (fmt != NULL) { 491 va_list ap; 492 493 va_start(ap, fmt); 494 vwarnx(fmt, ap); 495 va_end(ap); 496 (void) fprintf(stderr, "\n"); 497 } 498 499 (void) fprintf(stderr, "Usage: %s show-devs [-F] [-H] [-s | -o " 500 "field[,...] [-p]] [filter...]\n", pcieadm_progname); 501 502 (void) fprintf(stderr, "\nList PCI devices and functions in the " 503 "system. Each <filter> selects a set\nof devices to show and " 504 "can be a driver name, instance, /devices path, or\nb/d/f.\n\n" 505 "\t-F\t\tdo not display PCI functions\n" 506 "\t-H\t\tomit the column header\n" 507 "\t-o field\toutput fields to print\n" 508 "\t-p\t\tparsable output (requires -o)\n" 509 "\t-s\t\tlist speeds and widths\n\n" 510 "The following fields are supported:\n" 511 "\tvid\t\tthe PCI vendor ID in hex\n" 512 "\tdid\t\tthe PCI device ID in hex\n" 513 "\tvendor\t\tthe name of the PCI vendor\n" 514 "\tdevice\t\tthe name of the PCI device\n" 515 "\tinstance\tthe name of this particular instance, e.g. igb0\n" 516 "\tdriver\t\tthe name of the driver attached to the device\n" 517 "\tinstnum\t\tthe instance number of a device, e.g. 2 for nvme2\n" 518 "\tpath\t\tthe /devices path of the device\n" 519 "\tbdf\t\tthe PCI bus/device/function, with values in hex\n" 520 "\tbus\t\tthe PCI bus number of the device in hex\n" 521 "\tdev\t\tthe PCI device number of the device in hex\n" 522 "\tfunc\t\tthe PCI function number of the device in hex\n" 523 "\ttype\t\ta string describing the PCIe generation and width\n" 524 "\tmaxspeed\tthe maximum supported PCIe speed of the device\n" 525 "\tcurspeed\tthe current PCIe speed of the device\n" 526 "\tmaxwidth\tthe maximum supported PCIe lane count of the device\n" 527 "\tcurwidth\tthe current lane count of the PCIe device\n" 528 "\tsupspeeds\tthe list of speeds the device supports\n"); 529 } 530 531 int 532 pcieadm_show_devs(pcieadm_t *pcip, int argc, char *argv[]) 533 { 534 int c, ret; 535 uint_t flags = 0; 536 const char *fields = NULL; 537 pcieadm_show_devs_t psd; 538 pcieadm_di_walk_t walk; 539 ofmt_status_t oferr; 540 boolean_t parse = B_FALSE; 541 boolean_t speeds = B_FALSE; 542 543 /* 544 * show-devs relies solely on the devinfo snapshot we already took. 545 * Formalize our privs immediately. 546 */ 547 pcieadm_init_privs(pcip); 548 549 bzero(&psd, sizeof (psd)); 550 psd.psd_pia = pcip; 551 psd.psd_funcs = B_TRUE; 552 553 while ((c = getopt(argc, argv, ":FHo:ps")) != -1) { 554 switch (c) { 555 case 'F': 556 psd.psd_funcs = B_FALSE; 557 break; 558 case 'p': 559 parse = B_TRUE; 560 flags |= OFMT_PARSABLE; 561 break; 562 case 'H': 563 flags |= OFMT_NOHEADER; 564 break; 565 case 's': 566 speeds = B_TRUE; 567 break; 568 case 'o': 569 fields = optarg; 570 break; 571 case ':': 572 pcieadm_show_devs_help("option -%c requires an " 573 "argument", optopt); 574 exit(EXIT_USAGE); 575 case '?': 576 pcieadm_show_devs_help("unknown option: -%c", optopt); 577 exit(EXIT_USAGE); 578 } 579 } 580 581 if (parse && fields == NULL) { 582 errx(EXIT_USAGE, "-p requires fields specified with -o"); 583 } 584 585 if (fields != NULL && speeds) { 586 errx(EXIT_USAGE, "-s cannot be used with with -o"); 587 } 588 589 if (fields == NULL) { 590 if (speeds) { 591 fields = pcieadm_show_dev_speeds; 592 } else { 593 fields = pcieadm_show_dev_fields; 594 } 595 } 596 597 argc -= optind; 598 argv += optind; 599 600 if (argc > 0) { 601 psd.psd_nfilts = argc; 602 psd.psd_filts = argv; 603 psd.psd_used = calloc(argc, sizeof (boolean_t)); 604 if (psd.psd_used == NULL) { 605 err(EXIT_FAILURE, "failed to allocate filter tracking " 606 "memory"); 607 } 608 } 609 610 oferr = ofmt_open(fields, pcieadm_show_dev_ofmt, flags, 0, 611 &psd.psd_ofmt); 612 ofmt_check(oferr, parse, psd.psd_ofmt, pcieadm_ofmt_errx, warnx); 613 614 walk.pdw_arg = &psd; 615 walk.pdw_func = pcieadm_show_devs_walk_cb; 616 617 pcieadm_di_walk(pcip, &walk); 618 619 ret = EXIT_SUCCESS; 620 for (int i = 0; i < psd.psd_nfilts; i++) { 621 if (!psd.psd_used[i]) { 622 warnx("filter '%s' did not match any devices", 623 psd.psd_filts[i]); 624 ret = EXIT_FAILURE; 625 } 626 } 627 628 if (psd.psd_nprint == 0) { 629 ret = EXIT_FAILURE; 630 } 631 632 return (ret); 633 } 634