1 /* $NetBSD: amrctl.c,v 1.13 2024/11/03 10:43:26 rillig Exp $ */ 2 3 /*- 4 * Copyright (c) 2002, Pierre David <Pierre.David@crc.u-strasbg.fr> 5 * Copyright (c) 2006, Jung-uk Kim <jkim@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice unmodified, this list of conditions, and the following 13 * disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 #ifndef lint 32 __RCSID("$NetBSD: amrctl.c,v 1.13 2024/11/03 10:43:26 rillig Exp $"); 33 #endif 34 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <fcntl.h> 39 #include <err.h> 40 #include <errno.h> 41 #include <unistd.h> 42 43 #include <sys/ioctl.h> 44 45 #include <machine/param.h> 46 47 #include <dev/pci/amrio.h> 48 #include <dev/pci/amrreg.h> 49 50 #define NATTEMPTS 5 51 #define SLEEPTIME 100000 /* microseconds */ 52 53 static int nattempts = NATTEMPTS; /* # of attempts before giving up */ 54 static int sleeptime = SLEEPTIME; /* between attempts, in ms */ 55 56 #define AMR_BUFSIZE 1024 57 58 static int enq_result = AMR_STATUS_FAILED; 59 static char enq_buffer[AMR_BUFSIZE]; 60 61 #define AMR_MAX_NCTRLS 16 62 #define AMR_MAX_NSDEVS 16 63 64 static uint8_t nschan = 0; 65 66 /* 67 * Include lookup tables, and a function to match a code to a string. 68 * 69 * XXX Lookup tables cannot be included, since they require symbols from 70 * amrreg.h which need in turn the _KERNEL define. 71 */ 72 73 /* #define AMR_DEFINE_TABLES */ 74 /* #include "amr_tables.h" */ 75 76 static int amr_ioctl_enquiry(int, uint8_t, uint8_t, uint8_t); 77 __dead static void usage(const char *); 78 static int describe_card(int, int, int); 79 static char * describe_property(uint8_t, char *); 80 static const char * describe_state(int, uint8_t); 81 static void describe_battery(int, int, int, int, int); 82 static void describe_one_volume(int, int, uint32_t, uint8_t, uint8_t); 83 static void describe_one_drive(int, int, uint8_t); 84 static void describe_drive(int, int, int, int, int); 85 86 /* 87 * Offsets in an amr_user_ioctl.au_cmd [] array See amrio.h 88 */ 89 90 #define MB_COMMAND 0 91 #define MB_CHANNEL 1 92 #define MB_PARAM 2 93 #define MB_PAD 3 94 #define MB_DRIVE 4 95 96 #define FIRMWARE_40LD 1 97 #define FIRMWARE_8LD 2 98 99 static const struct { 100 const char *product; 101 const uint32_t signature; 102 } prodtable[] = { 103 { "Series 431", AMR_SIG_431 }, 104 { "Series 438", AMR_SIG_438 }, 105 { "Series 762", AMR_SIG_762 }, 106 { "Integrated HP NetRAID (T5)", AMR_SIG_T5 }, 107 { "Series 466", AMR_SIG_466 }, 108 { "Series 467", AMR_SIG_467 }, 109 { "Integrated HP NetRAID (T7)", AMR_SIG_T7 }, 110 { "Series 490", AMR_SIG_490 } 111 }; 112 113 static const struct { 114 const int code; 115 const char *ifyes, *ifno; 116 } proptable[] = { 117 { AMR_DRV_WRITEBACK, 118 "writeback", "write-through" }, 119 { AMR_DRV_READHEAD, 120 "read-ahead", "no-read-ahead" }, 121 { AMR_DRV_ADAPTIVE, 122 "adaptative-io", "no-adaptative-io" } 123 }; 124 125 static const struct { 126 const int code; 127 const char *status; 128 } statetable[] = { 129 { AMR_DRV_OFFLINE, "offline" }, 130 { AMR_DRV_DEGRADED, "degraded" }, 131 { AMR_DRV_OPTIMAL, "optimal" }, 132 { AMR_DRV_ONLINE, "online" }, 133 { AMR_DRV_FAILED, "failed" }, 134 { AMR_DRV_REBUILD, "rebuild" }, 135 { AMR_DRV_HOTSPARE, "hotspare" } 136 }; 137 138 static const struct { 139 const uint8_t code; 140 const char *status; 141 } battable[] = { 142 { AMR_BATT_MODULE_MISSING, "not present" }, 143 { AMR_BATT_LOW_VOLTAGE, "low voltage" }, 144 { AMR_BATT_TEMP_HIGH, "high temperature" }, 145 { AMR_BATT_PACK_MISSING, "pack missing" }, 146 { AMR_BATT_CYCLES_EXCEEDED, "cycle exceeded" } 147 }; 148 149 static const struct { 150 const uint8_t code; 151 const char *status; 152 } bcstatble[] = { 153 { AMR_BATT_CHARGE_DONE, "charge done" }, 154 { AMR_BATT_CHARGE_INPROG, "charge in progress" }, 155 { AMR_BATT_CHARGE_FAIL, "charge failed" } 156 }; 157 158 static int 159 amr_ioctl_enquiry(int fd, uint8_t cmd, uint8_t cmdsub, uint8_t cmdqual) 160 { 161 struct amr_user_ioctl am; 162 int r, i; 163 164 am.au_cmd[MB_COMMAND] = cmd; 165 am.au_cmd[MB_CHANNEL] = cmdsub; 166 am.au_cmd[MB_PARAM] = cmdqual; 167 am.au_cmd[MB_PAD] = 0; 168 am.au_cmd[MB_DRIVE] = 0; 169 170 am.au_buffer = enq_buffer; 171 am.au_length = AMR_BUFSIZE; 172 am.au_direction = AMR_IO_READ; 173 am.au_status = 0; 174 175 i = 0; 176 r = -1; 177 while (i < nattempts && r == -1) { 178 r = ioctl(fd, AMR_IO_COMMAND, &am); 179 if (r == -1) { 180 if (errno != EBUSY) { 181 warn("ioctl enquiry"); 182 return -1; 183 } else 184 usleep(sleeptime); 185 } 186 i++; 187 } 188 return am.au_status; 189 } 190 191 static void 192 usage(const char *prog) 193 { 194 fprintf(stderr, "usage: %s stat [-a num] [-b] " 195 "[-f dev] [-g] [-l vol]\n\t\t" 196 "[-p drive|-s bus[:target]] [-t usec] [-v]\n\n\t" 197 "-a num\t\tnumber of retries\n\t" 198 "-b\t\tbattery status\n\t" 199 "-f dev\t\tdevice path\n\t" 200 "-g\t\tprint global parameters\n\t" 201 "-l vol\t\tlogical volume ID\n\t" 202 "-p drive\tphysical drive ID\n\t" 203 "-s bus[:target]\tSCSI bus (and optinal target)\n\t" 204 "-t usec\t\tsleep time between retries\n\t" 205 "-v\t\tverbose output\n", 206 prog); 207 exit(1); 208 } 209 210 /****************************************************************************** 211 * Card description 212 */ 213 214 static int 215 describe_card(int fd, int verbosity, int globalparam) 216 { 217 struct amr_enquiry *ae; 218 uint32_t cardtype; 219 220 /* 221 * Try the 40LD firmware interface 222 */ 223 224 enq_result = amr_ioctl_enquiry(fd, AMR_CMD_CONFIG, 225 AMR_CONFIG_PRODUCT_INFO, 0); 226 if (enq_result == AMR_STATUS_SUCCESS) { 227 struct amr_prodinfo *ap; 228 229 ap = (struct amr_prodinfo *)enq_buffer; 230 nschan = ap->ap_nschan; 231 if (globalparam) { 232 printf("Product\t\t\t<%.80s>\n", ap->ap_product); 233 printf("Firmware\t\t%.16s\n", ap->ap_firmware); 234 printf("BIOS\t\t\t%.16s\n", ap->ap_bios); 235 printf("SCSI channels\t\t%d\n", ap->ap_nschan); 236 printf("Fibre loops\t\t%d\n", ap->ap_fcloops); 237 printf("Memory size\t\t%d MB\n", ap->ap_memsize); 238 if (verbosity >= 1) { 239 printf("Ioctl\t\t\t%d (%s)\n", FIRMWARE_40LD, 240 "40LD"); 241 printf("Signature\t\t0x%08x\n", 242 ap->ap_signature); 243 printf("Configsig\t\t0x%08x\n", 244 ap->ap_configsig); 245 printf("Subsystem\t\t0x%04x\n", 246 ap->ap_subsystem); 247 printf("Subvendor\t\t0x%04x\n", 248 ap->ap_subvendor); 249 printf("Notify counters\t\t%d\n", 250 ap->ap_numnotifyctr); 251 } 252 } 253 return FIRMWARE_40LD; 254 } 255 /* 256 * Try the 8LD firmware interface 257 */ 258 259 enq_result = amr_ioctl_enquiry(fd, AMR_CMD_EXT_ENQUIRY2, 0, 0); 260 ae = (struct amr_enquiry *)enq_buffer; 261 if (enq_result == AMR_STATUS_SUCCESS) { 262 cardtype = ae->ae_signature; 263 } else { 264 enq_result = amr_ioctl_enquiry(fd, AMR_CMD_ENQUIRY, 0, 0); 265 cardtype = 0; 266 } 267 268 if (enq_result == AMR_STATUS_SUCCESS) { 269 270 if (globalparam) { 271 const char *product = NULL; 272 char bios[100], firmware[100]; 273 size_t i; 274 275 for (i = 0; i < __arraycount(prodtable); i++) { 276 if (cardtype == prodtable[i].signature) { 277 product = prodtable[i].product; 278 break; 279 } 280 } 281 if (product == NULL) 282 product = "unknown card signature"; 283 284 /* 285 * HP NetRaid controllers have a special encoding of 286 * the firmware and BIOS versions. The AMI version 287 * seems to have it as strings whereas the HP version 288 * does it with a leading uppercase character and two 289 * binary numbers. 290 */ 291 292 if (ae->ae_adapter.aa_firmware[2] >= 'A' && 293 ae->ae_adapter.aa_firmware[2] <= 'Z' && 294 ae->ae_adapter.aa_firmware[1] < ' ' && 295 ae->ae_adapter.aa_firmware[0] < ' ' && 296 ae->ae_adapter.aa_bios[2] >= 'A' && 297 ae->ae_adapter.aa_bios[2] <= 'Z' && 298 ae->ae_adapter.aa_bios[1] < ' ' && 299 ae->ae_adapter.aa_bios[0] < ' ') { 300 301 /* 302 * looks like we have an HP NetRaid version 303 * of the MegaRaid 304 */ 305 306 if (cardtype == AMR_SIG_438) { 307 /* 308 * the AMI 438 is a NetRaid 3si in 309 * HP-land 310 */ 311 product = "HP NetRaid 3si"; 312 } 313 snprintf(firmware, sizeof(firmware), 314 "%c.%02d.%02d", 315 ae->ae_adapter.aa_firmware[2], 316 ae->ae_adapter.aa_firmware[1], 317 ae->ae_adapter.aa_firmware[0]); 318 snprintf(bios, sizeof(bios), 319 "%c.%02d.%02d", 320 ae->ae_adapter.aa_bios[2], 321 ae->ae_adapter.aa_bios[1], 322 ae->ae_adapter.aa_bios[0]); 323 } else { 324 snprintf(firmware, sizeof(firmware), "%.4s", 325 ae->ae_adapter.aa_firmware); 326 snprintf(bios, sizeof(bios), "%.4s", 327 ae->ae_adapter.aa_bios); 328 } 329 330 printf("Ioctl = %d (%s)\n", FIRMWARE_8LD, "8LD"); 331 printf("Product =\t<%s>\n", product); 332 printf("Firmware =\t%s\n", firmware); 333 printf("BIOS =\t%s\n", bios); 334 /* printf ("SCSI Channels =\t%d\n", ae->ae_nschan); */ 335 /* printf ("Fibre Loops =\t%d\n", ae->ae_fcloops); */ 336 printf("Memory size =\t%d MB\n", 337 ae->ae_adapter.aa_memorysize); 338 /* 339 * printf ("Notify counters =\t%d\n", 340 * ae->ae_numnotifyctr) ; 341 */ 342 } 343 return FIRMWARE_8LD; 344 } 345 /* 346 * Neither firmware interface succeeded. Abort. 347 */ 348 349 fprintf(stderr, "Firmware interface not supported\n"); 350 exit(1); 351 352 } 353 354 static char * 355 describe_property(uint8_t prop, char *buffer) 356 { 357 size_t i; 358 359 strcpy(buffer, "<"); 360 for (i = 0; i < __arraycount(proptable); i++) { 361 if (i > 0) 362 strcat(buffer, ","); 363 if (prop & proptable[i].code) 364 strcat(buffer, proptable[i].ifyes); 365 else 366 strcat(buffer, proptable[i].ifno); 367 } 368 strcat(buffer, ">"); 369 370 return buffer; 371 } 372 373 static const char * 374 describe_state(int verbosity, uint8_t state) 375 { 376 size_t i; 377 378 if ((AMR_DRV_PREVSTATE(state) == AMR_DRV_CURSTATE(state)) && 379 (AMR_DRV_CURSTATE(state) == AMR_DRV_OFFLINE) && verbosity == 0) 380 return NULL; 381 382 for (i = 0; i < __arraycount(statetable); i++) 383 if (AMR_DRV_CURSTATE(state) == statetable[i].code) 384 return (statetable[i].status); 385 386 return NULL; 387 } 388 389 /****************************************************************************** 390 * Battery status 391 */ 392 static void 393 describe_battery(int fd, int verbosity, int fwint, int bflags, int globalparam) 394 { 395 uint8_t batt_status; 396 size_t i; 397 398 if (fwint == FIRMWARE_40LD) { 399 enq_result = amr_ioctl_enquiry(fd, AMR_CMD_CONFIG, 400 AMR_CONFIG_ENQ3, AMR_CONFIG_ENQ3_SOLICITED_FULL); 401 if (enq_result == AMR_STATUS_SUCCESS) { 402 struct amr_enquiry3 *ae3; 403 404 ae3 = (struct amr_enquiry3 *)enq_buffer; 405 if (bflags || globalparam) { 406 batt_status = ae3->ae_batterystatus; 407 printf("Battery status\t\t"); 408 for (i = 0; i < __arraycount(battable); i++) { 409 if (batt_status & battable[i].code) 410 printf("%s, ", battable[i].status); 411 } 412 if (!(batt_status & 413 (AMR_BATT_MODULE_MISSING|AMR_BATT_PACK_MISSING))) { 414 for (i = 0; 415 i < __arraycount(bcstatble); i++) 416 if (bcstatble[i].code == 417 (batt_status & AMR_BATT_CHARGE_MASK)) 418 printf("%s", bcstatble[i].status); 419 } else 420 printf("charge unknown"); 421 if (verbosity) 422 printf(" (0x%02x)", batt_status); 423 printf("\n"); 424 } 425 } 426 } else if (fwint == FIRMWARE_8LD) { 427 /* Nothing to do here. */ 428 return; 429 } else { 430 fprintf(stderr, "Firmware interface not supported.\n"); 431 exit(1); 432 } 433 434 return; 435 } 436 437 /****************************************************************************** 438 * Logical volumes 439 */ 440 441 static void 442 describe_one_volume(int ldrv, int verbosity, 443 uint32_t size, uint8_t state, uint8_t prop) 444 { 445 float szgb; 446 int raid_level; 447 char propstr[MAXPATHLEN]; 448 const char *statestr; 449 450 szgb = ((float)size) / (1024 * 1024 * 2); /* size in GB */ 451 452 raid_level = prop & AMR_DRV_RAID_MASK; 453 454 printf("Logical volume %d\t", ldrv); 455 statestr = describe_state(verbosity, state); 456 if (statestr) 457 printf("%s ", statestr); 458 printf("(%.2f GB, RAID%d", szgb, raid_level); 459 if (verbosity >= 1) { 460 describe_property(prop, propstr); 461 printf(" %s", propstr); 462 } 463 printf(")\n"); 464 } 465 466 /****************************************************************************** 467 * Physical drives 468 */ 469 470 static void 471 describe_one_drive(int pdrv, int verbosity, uint8_t state) 472 { 473 const char *statestr; 474 475 statestr = describe_state(verbosity, state); 476 if (statestr) { 477 if (nschan > 0) 478 printf("Physical drive %d:%d\t%s\n", 479 pdrv / AMR_MAX_NSDEVS, pdrv % AMR_MAX_NSDEVS, 480 statestr); 481 else 482 printf("Physical drive %d:\t%s\n", pdrv, statestr); 483 } 484 } 485 486 static void 487 describe_drive(int verbosity, int fwint, int ldrv, int sbus, int sdev) 488 { 489 int drv, pdrv = -1; 490 491 if (sbus > -1 && sdev > -1) 492 pdrv = (sbus * AMR_MAX_NSDEVS) + sdev; 493 if (nschan != 0) { 494 if (sbus > -1 && sbus >= nschan) { 495 fprintf(stderr, "SCSI channel %d does not exist.\n", sbus); 496 exit(1); 497 } else if (sdev > -1 && sdev >= AMR_MAX_NSDEVS) { 498 fprintf(stderr, "SCSI device %d:%d does not exist.\n", 499 sbus, sdev); 500 exit(1); 501 } 502 } 503 if (fwint == FIRMWARE_40LD) { 504 if (enq_result == AMR_STATUS_SUCCESS) { 505 struct amr_enquiry3 *ae3; 506 507 ae3 = (struct amr_enquiry3 *)enq_buffer; 508 if ((ldrv < 0 && sbus < 0) || ldrv >= 0) { 509 if (ldrv >= ae3->ae_numldrives) { 510 fprintf(stderr, "Logical volume %d " 511 "does not exist.\n", ldrv); 512 exit(1); 513 } 514 if (ldrv < 0) { 515 for (drv = 0; 516 drv < ae3->ae_numldrives; 517 drv++) 518 describe_one_volume(drv, 519 verbosity, 520 ae3->ae_drivesize[drv], 521 ae3->ae_drivestate[drv], 522 ae3->ae_driveprop[drv]); 523 } else { 524 describe_one_volume(ldrv, 525 verbosity, 526 ae3->ae_drivesize[ldrv], 527 ae3->ae_drivestate[ldrv], 528 ae3->ae_driveprop[ldrv]); 529 } 530 } 531 if ((ldrv < 0 && sbus < 0) || sbus >= 0) { 532 if (pdrv >= AMR_40LD_MAXPHYSDRIVES || 533 (nschan != 0 && pdrv >= (nschan * AMR_MAX_NSDEVS))) { 534 fprintf(stderr, "Physical drive %d " 535 "is out of range.\n", pdrv); 536 exit(1); 537 } 538 if (sbus < 0) { 539 for (drv = 0; 540 drv < AMR_40LD_MAXPHYSDRIVES; 541 drv++) { 542 if (nschan != 0 && 543 drv >= (nschan * AMR_MAX_NSDEVS)) 544 break; 545 describe_one_drive(drv, 546 verbosity, 547 ae3->ae_pdrivestate[drv]); 548 } 549 } else if (sdev < 0) { 550 for (drv = sbus * AMR_MAX_NSDEVS; 551 drv < ((sbus + 1) * AMR_MAX_NSDEVS); 552 drv++) { 553 if (nschan != 0 && 554 drv >= (nschan * AMR_MAX_NSDEVS)) 555 break; 556 describe_one_drive(drv, 557 verbosity, 558 ae3->ae_pdrivestate[drv]); 559 } 560 } else { 561 if (nschan != 0 && 562 pdrv < (nschan * AMR_MAX_NSDEVS)) 563 describe_one_drive(pdrv, 1, 564 ae3->ae_pdrivestate[pdrv]); 565 } 566 } 567 } 568 } else if (fwint == FIRMWARE_8LD) { 569 /* Nothing to do here. */ 570 return; 571 } else { 572 fprintf(stderr, "Firmware interface not supported.\n"); 573 exit(1); 574 } 575 } 576 577 /****************************************************************************** 578 * Main function 579 */ 580 581 int 582 main(int argc, char *argv[]) 583 { 584 int i; 585 int fd = -1; 586 int globalparam = 0, verbosity = 0; 587 int bflags = 0, fflags = 0, sflags = 0; 588 int lvolno = -1, physno = -1; 589 int sbusno = -1, targetno = -1; 590 char filename[MAXPATHLEN]; 591 char sdev[MAXPATHLEN]; 592 char *pdev; 593 594 /* 595 * Parse arguments 596 */ 597 if (argc < 2) 598 usage(argv[0]); 599 if (strcmp(argv[1], "stat") != 0) /* only stat implemented for now */ 600 usage(argv[0]); 601 602 optind = 2; 603 while ((i = getopt(argc, argv, "a:b:f:gl:p:s:t:v")) != -1) 604 switch (i) { 605 case 'a': 606 nattempts = atoi(optarg); 607 break; 608 case 'b': 609 bflags++; 610 break; 611 case 'f': 612 snprintf(filename, MAXPATHLEN, "%s", optarg); 613 filename[MAXPATHLEN - 1] = '\0'; 614 fflags++; 615 break; 616 case 'g': 617 globalparam = 1; 618 break; 619 case 'l': 620 lvolno = atoi(optarg); 621 break; 622 case 'p': 623 physno = atoi(optarg); 624 break; 625 case 's': 626 snprintf(sdev, MAXPATHLEN, "%s", optarg); 627 sdev[MAXPATHLEN - 1] = '\0'; 628 sflags++; 629 break; 630 case 't': 631 sleeptime = atoi(optarg); 632 break; 633 case 'v': 634 verbosity++; 635 break; 636 case '?': 637 default: 638 usage(argv[0]); 639 } 640 argc -= optind; 641 argv += optind; 642 643 if (argc != 0) 644 usage(argv[0]); 645 646 if (!fflags) { 647 snprintf(filename, MAXPATHLEN, "/dev/amr0"); 648 } 649 650 fd = open(filename, O_RDONLY); 651 if (fd == -1) { 652 err(EXIT_FAILURE, "open"); 653 } 654 if (ioctl(fd, AMR_IO_VERSION, &i) == -1) { 655 err(EXIT_FAILURE, "ioctl version"); 656 } 657 658 if (sflags) { 659 if(physno > -1) 660 usage(argv[0]); 661 else { 662 sbusno = atoi(sdev); 663 if ((pdev = index(sdev, ':'))) 664 targetno = atoi(++pdev); 665 } 666 } else if (physno > -1) { 667 sbusno = physno / AMR_MAX_NSDEVS; 668 targetno = physno % AMR_MAX_NSDEVS; 669 } 670 671 if (globalparam && verbosity >= 1) 672 printf("Version\t\t\t%d\n", i); 673 #if 0 674 if (i != 1) { 675 fprintf(stderr, "Driver version (%d) not supported\n", i); 676 exit(1); 677 } 678 #endif 679 680 i = describe_card(fd, verbosity, globalparam); 681 describe_battery(fd, verbosity, i, bflags, globalparam); 682 if (!bflags || lvolno > -1 || physno > -1 || sbusno > -1 || targetno > -1) 683 describe_drive(verbosity, i, lvolno, sbusno, targetno); 684 685 return 0; 686 } 687