xref: /freebsd/usr.sbin/mlxcontrol/command.c (revision f4b37ed0f8b307b1f3f0f630ca725d68f1dff30d)
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$
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