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