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
main(int argc,char * argv[])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
cmd_help(int argc,char * argv[])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
status_print(int unit,void * arg)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
controller_print(int unit,void * arg)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
cmd_status(int argc,char * argv[])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
rescan_ctrlr(int unit,void * junk)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
cmd_rescan(int argc,char * argv[])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
detach_drive(int unit,void * arg)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
cmd_detach(int argc,char * argv[])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
cmd_check(int argc,char * argv[])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
cmd_rebuild(int argc,char * argv[])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
cmd_pause(int argc,char * argv[])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