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