1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 1999 Michael Smith 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <fcntl.h> 30 #include <paths.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 #include <err.h> 36 37 #include <dev/mlx/mlxio.h> 38 #include <dev/mlx/mlxreg.h> 39 40 #include "mlxcontrol.h" 41 42 static int cmd_status(int argc, char *argv[]); 43 static int cmd_rescan(int argc, char *argv[]); 44 static int cmd_detach(int argc, char *argv[]); 45 static int cmd_check(int argc, char *argv[]); 46 static int cmd_rebuild(int argc, char *argv[]); 47 #ifdef SUPPORT_PAUSE 48 static int cmd_pause(int argc, char *argv[]); 49 #endif 50 static int cmd_help(int argc, char *argv[]); 51 52 extern int cmd_config(int argc, char *argv[]); 53 54 55 struct 56 { 57 char *cmd; 58 int (*func)(int argc, char *argv[]); 59 char *desc; 60 char *text; 61 } commands[] = { 62 {"status", cmd_status, 63 "displays device status", 64 " status [-qv] [<drive>...]\n" 65 " Display status for <drive> or all drives if none is listed\n" 66 " -q Suppress output.\n" 67 " -v Display verbose information.\n" 68 " Returns 0 if all drives tested are online, 1 if one or more are\n" 69 " critical, and 2 if one or more are offline."}, 70 {"rescan", cmd_rescan, 71 "scan for new system drives", 72 " rescan <controller> [<controller>...]\n" 73 " Rescan <controller> for system drives.\n" 74 " rescan -a\n" 75 " Rescan all controllers for system drives."}, 76 {"detach", cmd_detach, 77 "detach system drives", 78 " detach <drive> [<drive>...]\n" 79 " Detaches <drive> from the controller.\n" 80 " detach -a <controller>\n" 81 " Detaches all drives on <controller>."}, 82 {"check", cmd_check, 83 "consistency-check a system drive", 84 " check <drive>\n" 85 " Requests a check and rebuild of the parity information on <drive>.\n" 86 " Note that each controller can only check one system drive at a time."}, 87 {"rebuild", cmd_rebuild, 88 "initiate a rebuild of a dead physical drive", 89 " rebuild <controller> <physdrive>\n" 90 " All system drives using space on the physical drive <physdrive>\n" 91 " are rebuilt, reconstructing all data on the drive.\n" 92 " Note that each controller can only perform one rebuild at a time."}, 93 #ifdef SUPPORT_PAUSE 94 {"pause", cmd_pause, 95 "pauses controller channels", 96 " pause [-t <howlong>] [-d <delay>] <controller> [<channel>...]\n" 97 " Pauses SCSI I/O on <channel> and <controller>. If no channel is specified,\n" 98 " all channels are paused.\n" 99 " <howlong> How long (seconds) to pause for (default 30).\n" 100 " <delay> How long (seconds) to wait before pausing (default 30).\n" 101 " pause <controller> -c\n" 102 " Cancels any pending pause operation on <controller>."}, 103 #endif 104 {"config", cmd_config, 105 "examine and update controller configuration", 106 " config <controller>\n" 107 " Print configuration for <controller>."}, 108 {"help", cmd_help, 109 "give help on usage", 110 ""}, 111 {NULL, NULL, NULL, NULL} 112 }; 113 114 /******************************************************************************** 115 * Command dispatch and global options parsing. 116 */ 117 118 int 119 main(int argc, char *argv[]) 120 { 121 int ch, i, oargc; 122 char **oargv; 123 124 oargc = argc; 125 oargv = argv; 126 while ((ch = getopt(argc, argv, "")) != -1) 127 switch(ch) { 128 default: 129 return(cmd_help(0, NULL)); 130 } 131 132 argc -= optind; 133 argv += optind; 134 135 if (argc > 0) 136 for (i = 0; commands[i].cmd != NULL; i++) 137 if (!strcmp(argv[0], commands[i].cmd)) 138 return(commands[i].func(argc, argv)); 139 140 return(cmd_help(oargc, oargv)); 141 } 142 143 /******************************************************************************** 144 * Helptext output 145 */ 146 static int 147 cmd_help(int argc, char *argv[]) 148 { 149 int i; 150 151 if (argc > 1) 152 for (i = 0; commands[i].cmd != NULL; i++) 153 if (!strcmp(argv[1], commands[i].cmd)) { 154 fprintf(stderr, "%s\n", commands[i].text); 155 fflush(stderr); 156 return(0); 157 } 158 159 if (argv != NULL) 160 fprintf(stderr, "Unknown command '%s'.\n", argv[1]); 161 fprintf(stderr, "Valid commands are:\n"); 162 for (i = 0; commands[i].cmd != NULL; i++) 163 fprintf(stderr, " %-20s %s\n", commands[i].cmd, commands[i].desc); 164 fflush(stderr); 165 return(0); 166 } 167 168 /******************************************************************************** 169 * Status output 170 * 171 * status [-qv] [<device> ...] 172 * Prints status for <device>, or all if none listed. 173 * 174 * -q Suppresses output, command returns 0 if devices are OK, 1 if one or 175 * more devices are critical, 2 if one or more devices are offline. 176 */ 177 static struct mlx_rebuild_status rs; 178 static int rs_ctrlr = -1; 179 static int status_result = 0; 180 181 /* XXX more verbosity! */ 182 static void 183 status_print(int unit, void *arg) 184 { 185 int verbosity = *(int *)arg; 186 int fd, result, ctrlr, sysdrive, statvalid; 187 188 /* Find which controller and what system drive we are */ 189 statvalid = 0; 190 if (mlxd_find_ctrlr(unit, &ctrlr, &sysdrive)) { 191 warnx("couldn't get controller/drive for %s", drivepath(unit)); 192 } else { 193 /* If we don't have rebuild stats for this controller, get them */ 194 if (rs_ctrlr == ctrlr) { 195 statvalid = 1; 196 } else { 197 if ((fd = open(ctrlrpath(ctrlr), 0)) < 0) { 198 warn("can't open %s", ctrlrpath(ctrlr)); 199 } else { 200 if (ioctl(fd, MLX_REBUILDSTAT, &rs) < 0) { 201 warn("ioctl MLX_REBUILDSTAT"); 202 } else { 203 rs_ctrlr = ctrlr; 204 statvalid = 1; 205 } 206 close(fd); 207 } 208 } 209 } 210 211 /* Get the device */ 212 if ((fd = open(drivepath(unit), 0)) < 0) { 213 warn("can't open %s", drivepath(unit)); 214 return; 215 } 216 217 /* Get its status */ 218 if (ioctl(fd, MLXD_STATUS, &result) < 0) { 219 warn("ioctl MLXD_STATUS"); 220 } else { 221 switch(result) { 222 case MLX_SYSD_ONLINE: 223 if (verbosity > 0) 224 printf("%s: online", drivename(unit)); 225 break; 226 case MLX_SYSD_CRITICAL: 227 if (verbosity > 0) 228 printf("%s: critical", drivename(unit)); 229 if (status_result < 1) 230 status_result = 1; 231 break; 232 case MLX_SYSD_OFFLINE: 233 if (verbosity > 0) 234 printf("%s: offline", drivename(unit)); 235 if (status_result < 2) 236 status_result = 2; 237 break; 238 default: 239 if (verbosity > 0) { 240 printf("%s: unknown status 0x%x", drivename(unit), result); 241 } 242 } 243 if (verbosity > 0) { 244 /* rebuild/check in progress on this drive? */ 245 if (statvalid && (rs_ctrlr == ctrlr) && 246 (rs.rs_drive == sysdrive) && (rs.rs_code != MLX_REBUILDSTAT_IDLE)) { 247 switch(rs.rs_code) { 248 case MLX_REBUILDSTAT_REBUILDCHECK: 249 printf(" [consistency check"); 250 break; 251 case MLX_REBUILDSTAT_ADDCAPACITY: 252 printf(" [add capacity"); 253 break; 254 case MLX_REBUILDSTAT_ADDCAPACITYINIT: 255 printf(" [add capacity init"); 256 break; 257 default: 258 printf(" [unknown operation"); 259 } 260 printf(": %d/%d, %d%% complete]", 261 rs.rs_remaining, rs.rs_size, 262 ((rs.rs_size - rs.rs_remaining) / (rs.rs_size / 100))); 263 } 264 printf("\n"); 265 } 266 } 267 close(fd); 268 } 269 270 static struct 271 { 272 int hwid; 273 char *name; 274 } mlx_controller_names[] = { 275 {0x01, "960P/PD"}, 276 {0x02, "960PL"}, 277 {0x10, "960PG"}, 278 {0x11, "960PJ"}, 279 {0x12, "960PR"}, 280 {0x13, "960PT"}, 281 {0x14, "960PTL0"}, 282 {0x15, "960PRL"}, 283 {0x16, "960PTL1"}, 284 {0x20, "1100PVX"}, 285 {-1, NULL} 286 }; 287 288 static void 289 controller_print(int unit, void *arg) 290 { 291 struct mlx_enquiry2 enq; 292 struct mlx_phys_drv pd; 293 int verbosity = *(int *)arg; 294 static char buf[80]; 295 char *model; 296 int i, channel, target; 297 298 if (verbosity == 0) 299 return; 300 301 /* fetch and print controller data */ 302 if (mlx_enquiry(unit, &enq)) { 303 printf("mlx%d: error submitting ENQUIRY2\n", unit); 304 } else { 305 306 for (i = 0, model = NULL; mlx_controller_names[i].name != NULL; i++) { 307 if ((enq.me_hardware_id & 0xff) == mlx_controller_names[i].hwid) { 308 model = mlx_controller_names[i].name; 309 break; 310 } 311 } 312 if (model == NULL) { 313 sprintf(buf, " model 0x%x", enq.me_hardware_id & 0xff); 314 model = buf; 315 } 316 317 printf("mlx%d: DAC%s, %d channel%s, firmware %d.%02d-%c-%02d, %dMB RAM\n", 318 unit, model, 319 enq.me_actual_channels, 320 enq.me_actual_channels > 1 ? "s" : "", 321 enq.me_firmware_id & 0xff, 322 (enq.me_firmware_id >> 8) & 0xff, 323 (enq.me_firmware_id >> 16), 324 (enq.me_firmware_id >> 24) & 0xff, 325 enq.me_mem_size / (1024 * 1024)); 326 327 if (verbosity > 1) { 328 printf(" Hardware ID 0x%08x\n", enq.me_hardware_id); 329 printf(" Firmware ID 0x%08x\n", enq.me_firmware_id); 330 printf(" Configured/Actual channels %d/%d\n", enq.me_configured_channels, 331 enq.me_actual_channels); 332 printf(" Max Targets %d\n", enq.me_max_targets); 333 printf(" Max Tags %d\n", enq.me_max_tags); 334 printf(" Max System Drives %d\n", enq.me_max_sys_drives); 335 printf(" Max Arms %d\n", enq.me_max_arms); 336 printf(" Max Spans %d\n", enq.me_max_spans); 337 printf(" DRAM/cache/flash/NVRAM size %d/%d/%d/%d\n", enq.me_mem_size, 338 enq.me_cache_size, enq.me_flash_size, enq.me_nvram_size); 339 printf(" DRAM type %d\n", enq.me_mem_type); 340 printf(" Clock Speed %dns\n", enq.me_clock_speed); 341 printf(" Hardware Speed %dns\n", enq.me_hardware_speed); 342 printf(" Max Commands %d\n", enq.me_max_commands); 343 printf(" Max SG Entries %d\n", enq.me_max_sg); 344 printf(" Max DP %d\n", enq.me_max_dp); 345 printf(" Max IOD %d\n", enq.me_max_iod); 346 printf(" Max Comb %d\n", enq.me_max_comb); 347 printf(" Latency %ds\n", enq.me_latency); 348 printf(" SCSI Timeout %ds\n", enq.me_scsi_timeout); 349 printf(" Min Free Lines %d\n", enq.me_min_freelines); 350 printf(" Rate Constant %d\n", enq.me_rate_const); 351 printf(" MAXBLK %d\n", enq.me_maxblk); 352 printf(" Blocking Factor %d sectors\n", enq.me_blocking_factor); 353 printf(" Cache Line Size %d blocks\n", enq.me_cacheline); 354 printf(" SCSI Capability %s%dMHz, %d bit\n", 355 enq.me_scsi_cap & (1<<4) ? "differential " : "", 356 (1 << ((enq.me_scsi_cap >> 2) & 3)) * 10, 357 8 << (enq.me_scsi_cap & 0x3)); 358 printf(" Firmware Build Number %d\n", enq.me_firmware_build); 359 printf(" Fault Management Type %d\n", enq.me_fault_mgmt_type); 360 #if 0 361 printf(" Features %b\n", enq.me_firmware_features, 362 "\20\4Background Init\3Read Ahead\2MORE\1Cluster\n"); 363 #endif 364 } 365 366 /* fetch and print physical drive data */ 367 for (channel = 0; channel < enq.me_configured_channels; channel++) { 368 for (target = 0; target < enq.me_max_targets; target++) { 369 if ((mlx_get_device_state(unit, channel, target, &pd) == 0) && 370 (pd.pd_flags1 & MLX_PHYS_DRV_PRESENT)) { 371 mlx_print_phys_drv(&pd, channel, target, " ", verbosity - 1); 372 if (verbosity > 1) { 373 /* XXX print device statistics? */ 374 } 375 } 376 } 377 } 378 } 379 } 380 381 static int 382 cmd_status(int argc, char *argv[]) 383 { 384 int ch, verbosity = 1, i, unit; 385 386 optreset = 1; 387 optind = 1; 388 while ((ch = getopt(argc, argv, "qv")) != -1) 389 switch(ch) { 390 case 'q': 391 verbosity = 0; 392 break; 393 case 'v': 394 verbosity = 2; 395 break; 396 default: 397 return(cmd_help(argc, argv)); 398 } 399 argc -= optind; 400 argv += optind; 401 402 if (argc < 1) { 403 mlx_foreach(controller_print, &verbosity); 404 mlxd_foreach(status_print, &verbosity); 405 } else { 406 for (i = 0; i < argc; i++) { 407 if ((unit = driveunit(argv[i])) == -1) { 408 warnx("'%s' is not a valid drive", argv[i]); 409 } else { 410 status_print(unit, &verbosity); 411 } 412 } 413 } 414 return(status_result); 415 } 416 417 /******************************************************************************** 418 * Recscan for system drives on one or more controllers. 419 * 420 * rescan <controller> [<controller>...] 421 * rescan -a 422 */ 423 static void 424 rescan_ctrlr(int unit, void *junk) 425 { 426 int fd; 427 428 /* Get the device */ 429 if ((fd = open(ctrlrpath(unit), 0)) < 0) { 430 warn("can't open %s", ctrlrpath(unit)); 431 return; 432 } 433 434 if (ioctl(fd, MLX_RESCAN_DRIVES) < 0) 435 warn("can't rescan %s", ctrlrname(unit)); 436 close(fd); 437 } 438 439 static int 440 cmd_rescan(int argc, char *argv[]) 441 { 442 int all = 0, i, ch, unit; 443 444 optreset = 1; 445 optind = 1; 446 while ((ch = getopt(argc, argv, "a")) != -1) 447 switch(ch) { 448 case 'a': 449 all = 1; 450 break; 451 default: 452 return(cmd_help(argc, argv)); 453 } 454 argc -= optind; 455 argv += optind; 456 457 if (all) { 458 mlx_foreach(rescan_ctrlr, NULL); 459 } else { 460 for (i = 0; i < argc; i++) { 461 if ((unit = ctrlrunit(argv[i])) == -1) { 462 warnx("'%s' is not a valid controller", argv[i]); 463 } else { 464 rescan_ctrlr(unit, NULL); 465 } 466 } 467 } 468 return(0); 469 } 470 471 /******************************************************************************** 472 * Detach one or more system drives from a controller. 473 * 474 * detach <drive> [<drive>...] 475 * Detach <drive>. 476 * 477 * detach -a <controller> [<controller>...] 478 * Detach all drives on <controller>. 479 * 480 */ 481 static void 482 detach_drive(int unit, void *arg) 483 { 484 int fd; 485 486 /* Get the device */ 487 if ((fd = open(ctrlrpath(unit), 0)) < 0) { 488 warn("can't open %s", ctrlrpath(unit)); 489 return; 490 } 491 492 if (ioctl(fd, MLX_DETACH_DRIVE, &unit) < 0) 493 warn("can't detach %s", drivename(unit)); 494 close(fd); 495 } 496 497 static int 498 cmd_detach(int argc, char *argv[]) 499 { 500 struct mlxd_foreach_action ma; 501 int all = 0, i, ch, unit; 502 503 optreset = 1; 504 optind = 1; 505 while ((ch = getopt(argc, argv, "a")) != -1) 506 switch(ch) { 507 case 'a': 508 all = 1; 509 break; 510 default: 511 return(cmd_help(argc, argv)); 512 } 513 argc -= optind; 514 argv += optind; 515 516 if (all) { 517 ma.func = detach_drive; 518 ma.arg = &unit; 519 for (i = 0; i < argc; i++) { 520 if ((unit = ctrlrunit(argv[i])) == -1) { 521 warnx("'%s' is not a valid controller", argv[i]); 522 } else { 523 mlxd_foreach_ctrlr(unit, &ma); 524 } 525 } 526 } else { 527 for (i = 0; i < argc; i++) { 528 if ((unit = driveunit(argv[i])) == -1) { 529 warnx("'%s' is not a valid drive", argv[i]); 530 } else { 531 /* run across all controllers to find this drive */ 532 mlx_foreach(detach_drive, &unit); 533 } 534 } 535 } 536 return(0); 537 } 538 539 /******************************************************************************** 540 * Initiate a consistency check on a system drive. 541 * 542 * check [<drive>] 543 * Start a check of <drive> 544 * 545 */ 546 static int 547 cmd_check(int argc, char *argv[]) 548 { 549 int unit, fd, result; 550 551 if (argc != 2) 552 return(cmd_help(argc, argv)); 553 554 if ((unit = driveunit(argv[1])) == -1) { 555 warnx("'%s' is not a valid drive", argv[1]); 556 } else { 557 558 /* Get the device */ 559 if ((fd = open(drivepath(unit), 0)) < 0) { 560 warn("can't open %s", drivepath(unit)); 561 } else { 562 /* Try to start the check */ 563 if ((ioctl(fd, MLXD_CHECKASYNC, &result)) < 0) { 564 switch(result) { 565 case 0x0002: 566 warnx("one or more of the SCSI disks on which the drive '%s' depends is DEAD", argv[1]); 567 break; 568 case 0x0105: 569 warnx("drive %s is invalid, or not a drive which can be checked", argv[1]); 570 break; 571 case 0x0106: 572 warnx("drive rebuild or consistency check is already in progress on this controller"); 573 break; 574 default: 575 warn("ioctl MLXD_CHECKASYNC"); 576 } 577 } 578 } 579 } 580 return(0); 581 } 582 583 /******************************************************************************** 584 * Initiate a physical drive rebuild 585 * 586 * rebuild <controller> <channel>:<target> 587 * Start a rebuild of <controller>:<channel>:<target> 588 * 589 */ 590 static int 591 cmd_rebuild(int argc, char *argv[]) 592 { 593 struct mlx_rebuild_request rb; 594 int unit, fd; 595 596 if (argc != 3) 597 return(cmd_help(argc, argv)); 598 599 /* parse arguments */ 600 if ((unit = ctrlrunit(argv[1])) == -1) { 601 warnx("'%s' is not a valid controller", argv[1]); 602 return(1); 603 } 604 /* try diskXXXX and unknownXXXX as we report the latter for a dead drive ... */ 605 if ((sscanf(argv[2], "disk%2d%2d", &rb.rr_channel, &rb.rr_target) != 2) && 606 (sscanf(argv[2], "unknown%2d%2d", &rb.rr_channel, &rb.rr_target) != 2)) { 607 warnx("'%s' is not a valid physical drive", argv[2]); 608 return(1); 609 } 610 /* get the device */ 611 if ((fd = open(ctrlrpath(unit), 0)) < 0) { 612 warn("can't open %s", ctrlrpath(unit)); 613 return(1); 614 } 615 /* try to start the rebuild */ 616 if ((ioctl(fd, MLX_REBUILDASYNC, &rb)) < 0) { 617 switch(rb.rr_status) { 618 case 0x0002: 619 warnx("the drive at %d:%d is already ONLINE", rb.rr_channel, rb.rr_target); 620 break; 621 case 0x0004: 622 warnx("drive failed during rebuild"); 623 break; 624 case 0x0105: 625 warnx("there is no drive at channel %d, target %d", rb.rr_channel, rb.rr_target); 626 break; 627 case 0x0106: 628 warnx("drive rebuild or consistency check is already in progress on this controller"); 629 break; 630 default: 631 warn("ioctl MLXD_REBUILDASYNC"); 632 } 633 } 634 return(0); 635 } 636 637 #ifdef SUPPORT_PAUSE 638 /******************************************************************************** 639 * Pause one or more channels on a controller 640 * 641 * pause [-d <delay>] [-t <time>] <controller> [<channel>...] 642 * Pauses <channel> (or all channels) for <time> seconds after a 643 * delay of <delay> seconds. 644 * pause <controller> -c 645 * Cancels pending pause 646 */ 647 static int 648 cmd_pause(int argc, char *argv[]) 649 { 650 struct mlx_pause mp; 651 int unit, i, ch, fd, cancel = 0; 652 char *cp; 653 int oargc = argc; 654 char **oargv = argv; 655 656 mp.mp_which = 0; 657 mp.mp_when = 30; 658 mp.mp_howlong = 30; 659 optreset = 1; 660 optind = 1; 661 while ((ch = getopt(argc, argv, "cd:t:")) != -1) 662 switch(ch) { 663 case 'c': 664 cancel = 1; 665 break; 666 case 'd': 667 mp.mp_when = strtol(optarg, &cp, 0); 668 if (*cp != 0) 669 return(cmd_help(argc, argv)); 670 break; 671 case 't': 672 mp.mp_howlong = strtol(optarg, &cp, 0); 673 if (*cp != 0) 674 return(cmd_help(argc, argv)); 675 break; 676 default: 677 return(cmd_help(argc, argv)); 678 } 679 argc -= optind; 680 argv += optind; 681 682 /* get controller unit number that we're working on */ 683 if ((argc < 1) || ((unit = ctrlrunit(argv[0])) == -1)) 684 return(cmd_help(oargc, oargv)); 685 686 /* Get the device */ 687 if ((fd = open(ctrlrpath(unit), 0)) < 0) { 688 warn("can't open %s", ctrlrpath(unit)); 689 return(1); 690 } 691 692 if (argc == 1) { 693 /* controller-wide pause/cancel */ 694 mp.mp_which = cancel ? MLX_PAUSE_CANCEL : MLX_PAUSE_ALL; 695 } else { 696 for (i = 1; i < argc; i++) { 697 ch = strtol(argv[i], &cp, 0); 698 if (*cp != 0) { 699 warnx("bad channel number '%s'", argv[i]); 700 continue; 701 } else { 702 mp.mp_which |= (1 << ch); 703 } 704 } 705 } 706 if ((ioctl(fd, MLX_PAUSE_CHANNEL, &mp)) < 0) 707 warn("couldn't %s %s", cancel ? "cancel pause on" : "pause", ctrlrname(unit)); 708 close(fd); 709 return(0); 710 } 711 #endif /* SUPPORT_PAUSE */ 712 713