xref: /freebsd/usr.sbin/mlxcontrol/command.c (revision b9f654b163bce26de79705e77b872427c9f2afa1)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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