1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
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
42static int	cmd_status(int argc, char *argv[]);
43static int	cmd_rescan(int argc, char *argv[]);
44static int	cmd_detach(int argc, char *argv[]);
45static int	cmd_check(int argc, char *argv[]);
46static int	cmd_rebuild(int argc, char *argv[]);
47#ifdef SUPPORT_PAUSE
48static int	cmd_pause(int argc, char *argv[]);
49#endif
50static int	cmd_help(int argc, char *argv[]);
51
52extern int	cmd_config(int argc, char *argv[]);
53
54
55struct
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
118int
119main(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 */
146static int
147cmd_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 */
177static struct mlx_rebuild_status	rs;
178static int				rs_ctrlr = -1;
179static int				status_result = 0;
180
181/* XXX more verbosity! */
182static void
183status_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
270static 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
288static void
289controller_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
381static int
382cmd_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 */
423static void
424rescan_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
439static int
440cmd_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 */
481static void
482detach_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
497static int
498cmd_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 */
546static int
547cmd_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 */
590static int
591cmd_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 */
647static int
648cmd_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