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