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
44static int	cmd_status(int argc, char *argv[]);
45static int	cmd_rescan(int argc, char *argv[]);
46static int	cmd_detach(int argc, char *argv[]);
47static int	cmd_check(int argc, char *argv[]);
48static int	cmd_rebuild(int argc, char *argv[]);
49#ifdef SUPPORT_PAUSE
50static int	cmd_pause(int argc, char *argv[]);
51#endif
52static int	cmd_help(int argc, char *argv[]);
53
54extern int	cmd_config(int argc, char *argv[]);
55
56
57struct
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
120int
121main(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 */
148static int
149cmd_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 */
179static struct mlx_rebuild_status	rs;
180static int				rs_ctrlr = -1;
181static int				status_result = 0;
182
183/* XXX more verbosity! */
184static void
185status_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
272static 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
290static void
291controller_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
383static int
384cmd_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 */
425static void
426rescan_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
441static int
442cmd_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 */
483static void
484detach_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
499static int
500cmd_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 */
548static int
549cmd_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 */
592static int
593cmd_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 */
649static int
650cmd_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