1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2011-2012 Stefan Bethke.
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 <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <ctype.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sysexits.h>
42 #include <unistd.h>
43 #include <sys/types.h>
44 #include <sys/ioctl.h>
45 #include <net/if.h>
46 #include <net/if_media.h>
47 #include <dev/etherswitch/etherswitch.h>
48 
49 int	get_media_subtype(int, const char *);
50 int	get_media_mode(int, const char *);
51 int	get_media_options(int, const char *);
52 int	lookup_media_word(struct ifmedia_description *, const char *);
53 void    print_media_word(int, int);
54 void    print_media_word_ifconfig(int);
55 
56 /* some constants */
57 #define IEEE802DOT1Q_VID_MAX	4094
58 #define IFMEDIAREQ_NULISTENTRIES	256
59 
60 enum cmdmode {
61 	MODE_NONE = 0,
62 	MODE_PORT,
63 	MODE_CONFIG,
64 	MODE_VLANGROUP,
65 	MODE_REGISTER,
66 	MODE_PHYREG,
67 	MODE_ATU
68 };
69 
70 struct cfg {
71 	int					fd;
72 	int					verbose;
73 	int					mediatypes;
74 	const char			*controlfile;
75 	etherswitch_conf_t	conf;
76 	etherswitch_info_t	info;
77 	enum cmdmode		mode;
78 	int					unit;
79 };
80 
81 struct cmds {
82 	enum cmdmode	mode;
83 	const char	*name;
84 	int		args;
85 	int		(*f)(struct cfg *, int argc, char *argv[]);
86 };
87 static struct cmds cmds[];
88 
89 /* Must match the ETHERSWITCH_PORT_LED_* enum order */
90 static const char *ledstyles[] = { "default", "on", "off", "blink", NULL };
91 
92 /*
93  * Print a value a la the %b format of the kernel's printf.
94  * Stolen from ifconfig.c.
95  */
96 static void
97 printb(const char *s, unsigned v, const char *bits)
98 {
99 	int i, any = 0;
100 	char c;
101 
102 	if (bits && *bits == 8)
103 		printf("%s=%o", s, v);
104 	else
105 		printf("%s=%x", s, v);
106 	bits++;
107 	if (bits) {
108 		putchar('<');
109 		while ((i = *bits++) != '\0') {
110 			if (v & (1 << (i-1))) {
111 				if (any)
112 					putchar(',');
113 				any = 1;
114 				for (; (c = *bits) > 32; bits++)
115 					putchar(c);
116 			} else
117 				for (; *bits > 32; bits++)
118 					;
119 		}
120 		putchar('>');
121 	}
122 }
123 
124 static int
125 read_register(struct cfg *cfg, int r)
126 {
127 	struct etherswitch_reg er;
128 
129 	er.reg = r;
130 	if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
131 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
132 	return (er.val);
133 }
134 
135 static void
136 write_register(struct cfg *cfg, int r, int v)
137 {
138 	struct etherswitch_reg er;
139 
140 	er.reg = r;
141 	er.val = v;
142 	if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
143 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
144 }
145 
146 static int
147 read_phyregister(struct cfg *cfg, int phy, int reg)
148 {
149 	struct etherswitch_phyreg er;
150 
151 	er.phy = phy;
152 	er.reg = reg;
153 	if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
154 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
155 	return (er.val);
156 }
157 
158 static void
159 write_phyregister(struct cfg *cfg, int phy, int reg, int val)
160 {
161 	struct etherswitch_phyreg er;
162 
163 	er.phy = phy;
164 	er.reg = reg;
165 	er.val = val;
166 	if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
167 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
168 }
169 
170 static int
171 set_port_vid(struct cfg *cfg, int argc, char *argv[])
172 {
173 	int v;
174 	etherswitch_port_t p;
175 
176 	if (argc < 2)
177 		return (-1);
178 
179 	v = strtol(argv[1], NULL, 0);
180 	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
181 		errx(EX_USAGE, "pvid must be between 0 and %d",
182 		    IEEE802DOT1Q_VID_MAX);
183 	bzero(&p, sizeof(p));
184 	p.es_port = cfg->unit;
185 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
186 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
187 	p.es_pvid = v;
188 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
189 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
190 	return (0);
191 }
192 
193 static int
194 set_port_flag(struct cfg *cfg, int argc, char *argv[])
195 {
196 	char *flag;
197 	int n;
198 	uint32_t f;
199 	etherswitch_port_t p;
200 
201 	if (argc < 1)
202 		return (-1);
203 
204 	n = 0;
205 	f = 0;
206 	flag = argv[0];
207 	if (strcmp(flag, "none") != 0) {
208 		if (*flag == '-') {
209 			n++;
210 			flag++;
211 		}
212 		if (strcasecmp(flag, "striptag") == 0)
213 			f = ETHERSWITCH_PORT_STRIPTAG;
214 		else if (strcasecmp(flag, "addtag") == 0)
215 			f = ETHERSWITCH_PORT_ADDTAG;
216 		else if (strcasecmp(flag, "firstlock") == 0)
217 			f = ETHERSWITCH_PORT_FIRSTLOCK;
218 		else if (strcasecmp(flag, "droptagged") == 0)
219 			f = ETHERSWITCH_PORT_DROPTAGGED;
220 		else if (strcasecmp(flag, "dropuntagged") == 0)
221 			f = ETHERSWITCH_PORT_DROPUNTAGGED;
222 		else if (strcasecmp(flag, "doubletag") == 0)
223 			f = ETHERSWITCH_PORT_DOUBLE_TAG;
224 		else if (strcasecmp(flag, "ingress") == 0)
225 			f = ETHERSWITCH_PORT_INGRESS;
226 	}
227 	bzero(&p, sizeof(p));
228 	p.es_port = cfg->unit;
229 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
230 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
231 	if (n)
232 		p.es_flags &= ~f;
233 	else
234 		p.es_flags |= f;
235 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
236 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
237 	return (0);
238 }
239 
240 static int
241 set_port_media(struct cfg *cfg, int argc, char *argv[])
242 {
243 	etherswitch_port_t p;
244 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
245 	int subtype;
246 
247 	if (argc < 2)
248 		return (-1);
249 
250 	bzero(&p, sizeof(p));
251 	p.es_port = cfg->unit;
252 	p.es_ifmr.ifm_ulist = ifm_ulist;
253 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
254 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
255 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
256 	if (p.es_ifmr.ifm_count == 0)
257 		return (0);
258 	subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
259 	p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
260 	        IFM_TYPE(ifm_ulist[0]) | subtype;
261 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
262 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
263 	return (0);
264 }
265 
266 static int
267 set_port_mediaopt(struct cfg *cfg, int argc, char *argv[])
268 {
269 	etherswitch_port_t p;
270 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
271 	int options;
272 
273 	if (argc < 2)
274 		return (-1);
275 
276 	bzero(&p, sizeof(p));
277 	p.es_port = cfg->unit;
278 	p.es_ifmr.ifm_ulist = ifm_ulist;
279 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
280 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
281 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
282 	options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
283 	if (options == -1)
284 		errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
285 	if (options & IFM_HDX) {
286 		p.es_ifr.ifr_media &= ~IFM_FDX;
287 		options &= ~IFM_HDX;
288 	}
289 	p.es_ifr.ifr_media |= options;
290 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
291 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
292 	return (0);
293 }
294 
295 static int
296 set_port_led(struct cfg *cfg, int argc, char *argv[])
297 {
298 	etherswitch_port_t p;
299 	int led;
300 	int i;
301 
302 	if (argc < 3)
303 		return (-1);
304 
305 	bzero(&p, sizeof(p));
306 	p.es_port = cfg->unit;
307 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
308 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
309 
310 	led = strtol(argv[1], NULL, 0);
311 	if (led < 1 || led > p.es_nleds)
312 		errx(EX_USAGE, "invalid led number %s; must be between 1 and %d",
313 			argv[1], p.es_nleds);
314 
315 	led--;
316 
317 	for (i=0; ledstyles[i] != NULL; i++) {
318 		if (strcmp(argv[2], ledstyles[i]) == 0) {
319 			p.es_led[led] = i;
320 			break;
321 		}
322 	}
323 	if (ledstyles[i] == NULL)
324 		errx(EX_USAGE, "invalid led style \"%s\"", argv[2]);
325 
326 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
327 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
328 
329 	return (0);
330 }
331 
332 static int
333 set_vlangroup_vid(struct cfg *cfg, int argc, char *argv[])
334 {
335 	int v;
336 	etherswitch_vlangroup_t vg;
337 
338 	if (argc < 2)
339 		return (-1);
340 
341 	memset(&vg, 0, sizeof(vg));
342 	v = strtol(argv[1], NULL, 0);
343 	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
344 		errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
345 	vg.es_vlangroup = cfg->unit;
346 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
347 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
348 	vg.es_vid = v;
349 	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
350 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
351 	return (0);
352 }
353 
354 static int
355 set_vlangroup_members(struct cfg *cfg, int argc, char *argv[])
356 {
357 	etherswitch_vlangroup_t vg;
358 	int member, untagged;
359 	char *c, *d;
360 	int v;
361 
362 	if (argc < 2)
363 		return (-1);
364 
365 	member = untagged = 0;
366 	memset(&vg, 0, sizeof(vg));
367 	if (strcmp(argv[1], "none") != 0) {
368 		for (c=argv[1]; *c; c=d) {
369 			v = strtol(c, &d, 0);
370 			if (d == c)
371 				break;
372 			if (v < 0 || v >= cfg->info.es_nports)
373 				errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
374 			if (d[0] == ',' || d[0] == '\0' ||
375 				((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
376 				if (d[0] == 't' || d[0] == 'T') {
377 					untagged &= ~ETHERSWITCH_PORTMASK(v);
378 					d++;
379 				} else
380 					untagged |= ETHERSWITCH_PORTMASK(v);
381 				member |= ETHERSWITCH_PORTMASK(v);
382 				d++;
383 			} else
384 				errx(EX_USAGE, "Invalid members specification \"%s\"", d);
385 		}
386 	}
387 	vg.es_vlangroup = cfg->unit;
388 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
389 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
390 	vg.es_member_ports = member;
391 	vg.es_untagged_ports = untagged;
392 	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
393 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
394 	return (0);
395 }
396 
397 static int
398 set_register(struct cfg *cfg, char *arg)
399 {
400 	int a, v;
401 	char *c;
402 
403 	a = strtol(arg, &c, 0);
404 	if (c==arg)
405 		return (1);
406 	if (*c == '=') {
407 		v = strtoul(c+1, NULL, 0);
408 		write_register(cfg, a, v);
409 	}
410 	printf("\treg 0x%04x=0x%08x\n", a, read_register(cfg, a));
411 	return (0);
412 }
413 
414 static int
415 set_phyregister(struct cfg *cfg, char *arg)
416 {
417 	int phy, reg, val;
418 	char *c, *d;
419 
420 	phy = strtol(arg, &c, 0);
421 	if (c==arg)
422 		return (1);
423 	if (*c != '.')
424 		return (1);
425 	d = c+1;
426 	reg = strtol(d, &c, 0);
427 	if (d == c)
428 		return (1);
429 	if (*c == '=') {
430 		val = strtoul(c+1, NULL, 0);
431 		write_phyregister(cfg, phy, reg, val);
432 	}
433 	printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
434 	return (0);
435 }
436 
437 static int
438 set_vlan_mode(struct cfg *cfg, int argc, char *argv[])
439 {
440 	etherswitch_conf_t conf;
441 
442 	if (argc < 2)
443 		return (-1);
444 
445 	bzero(&conf, sizeof(conf));
446 	conf.cmd = ETHERSWITCH_CONF_VLAN_MODE;
447 	if (strcasecmp(argv[1], "isl") == 0)
448 		conf.vlan_mode = ETHERSWITCH_VLAN_ISL;
449 	else if (strcasecmp(argv[1], "port") == 0)
450 		conf.vlan_mode = ETHERSWITCH_VLAN_PORT;
451 	else if (strcasecmp(argv[1], "dot1q") == 0)
452 		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
453 	else if (strcasecmp(argv[1], "dot1q4k") == 0)
454 		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q_4K;
455 	else if (strcasecmp(argv[1], "qinq") == 0)
456 		conf.vlan_mode = ETHERSWITCH_VLAN_DOUBLE_TAG;
457 	else
458 		conf.vlan_mode = 0;
459 	if (ioctl(cfg->fd, IOETHERSWITCHSETCONF, &conf) != 0)
460 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETCONF)");
461 
462 	return (0);
463 }
464 
465 static int
466 atu_flush(struct cfg *cfg, int argc, char *argv[])
467 {
468 	etherswitch_portid_t p;
469 	int i, r;
470 
471 	bzero(&p, sizeof(p));
472 
473 	/* note: argv[0] is "flush" */
474 	if (argc > 2 && strcasecmp(argv[1], "port") == 0) {
475 		p.es_port = atoi(argv[2]);
476 		i = IOETHERSWITCHFLUSHPORT;
477 		r = 3;
478 	} else if (argc > 1 && strcasecmp(argv[1], "all") == 0) {
479 		p.es_port = 0;
480 		r = 2;
481 		i = IOETHERSWITCHFLUSHALL;
482 	} else {
483 		fprintf(stderr,
484 		    "%s: invalid verb (port <x> or all) (got %s)\n",
485 		    __func__, argv[1]);
486 		return (-1);
487 	}
488 
489 	if (ioctl(cfg->fd, i, &p) != 0)
490 		err(EX_OSERR, "ioctl(ATU flush (ioctl %d, port %d))",
491 		    i, p.es_port);
492 	return (r);
493 }
494 
495 static int
496 atu_dump(struct cfg *cfg, int argc, char *argv[])
497 {
498 	etherswitch_atu_table_t p;
499 	etherswitch_atu_entry_t e;
500 	uint32_t i;
501 
502 	(void) argc;
503 	(void) argv;
504 
505 	/* Note: argv[0] is "dump" */
506 	bzero(&p, sizeof(p));
507 
508 	if (ioctl(cfg->fd, IOETHERSWITCHGETTABLE, &p) != 0)
509 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETTABLE)");
510 
511 	/* And now, iterate to get entries */
512 	for (i = 0; i < p.es_nitems; i++) {
513 		bzero(&e, sizeof(e));
514 		e.id = i;
515 		if (ioctl(cfg->fd, IOETHERSWITCHGETTABLEENTRY, &e) != 0)
516 			break;
517 
518 		printf(" [%d] %s: portmask 0x%08x\n", i,
519 		    ether_ntoa((void *) &e.es_macaddr),
520 		    e.es_portmask);
521 	}
522 
523 	return (1);
524 }
525 
526 static void
527 print_config(struct cfg *cfg)
528 {
529 	const char *c;
530 
531 	/* Get the device name. */
532 	c = strrchr(cfg->controlfile, '/');
533 	if (c != NULL)
534 		c = c + 1;
535 	else
536 		c = cfg->controlfile;
537 
538 	/* Print VLAN mode. */
539 	if (cfg->conf.cmd & ETHERSWITCH_CONF_VLAN_MODE) {
540 		printf("%s: VLAN mode: ", c);
541 		switch (cfg->conf.vlan_mode) {
542 		case ETHERSWITCH_VLAN_ISL:
543 			printf("ISL\n");
544 			break;
545 		case ETHERSWITCH_VLAN_PORT:
546 			printf("PORT\n");
547 			break;
548 		case ETHERSWITCH_VLAN_DOT1Q:
549 			printf("DOT1Q\n");
550 			break;
551 		case ETHERSWITCH_VLAN_DOT1Q_4K:
552 			printf("DOT1Q4K\n");
553 			break;
554 		case ETHERSWITCH_VLAN_DOUBLE_TAG:
555 			printf("QinQ\n");
556 			break;
557 		default:
558 			printf("none\n");
559 		}
560 	}
561 
562 	/* Print switch MAC address. */
563 	if (cfg->conf.cmd & ETHERSWITCH_CONF_SWITCH_MACADDR) {
564 		printf("%s: Switch MAC address: %s\n",
565 		    c,
566 		    ether_ntoa(&cfg->conf.switch_macaddr));
567 	}
568 }
569 
570 static void
571 print_port(struct cfg *cfg, int port)
572 {
573 	etherswitch_port_t p;
574 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
575 	int i;
576 
577 	bzero(&p, sizeof(p));
578 	p.es_port = port;
579 	p.es_ifmr.ifm_ulist = ifm_ulist;
580 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
581 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
582 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
583 	printf("port%d:\n", port);
584 	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
585 		printf("\tpvid: %d\n", p.es_pvid);
586 	printb("\tflags", p.es_flags, ETHERSWITCH_PORT_FLAGS_BITS);
587 	printf("\n");
588 	if (p.es_nleds) {
589 		printf("\tled: ");
590 		for (i = 0; i < p.es_nleds; i++) {
591 			printf("%d:%s%s", i+1, ledstyles[p.es_led[i]], (i==p.es_nleds-1)?"":" ");
592 		}
593 		printf("\n");
594 	}
595 	printf("\tmedia: ");
596 	print_media_word(p.es_ifmr.ifm_current, 1);
597 	if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
598 		putchar(' ');
599 		putchar('(');
600 		print_media_word(p.es_ifmr.ifm_active, 0);
601 		putchar(')');
602 	}
603 	putchar('\n');
604 	printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
605 	if (cfg->mediatypes) {
606 		printf("\tsupported media:\n");
607 		if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
608 			p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
609 		for (i=0; i<p.es_ifmr.ifm_count; i++) {
610 			printf("\t\tmedia ");
611 			print_media_word(ifm_ulist[i], 0);
612 			putchar('\n');
613 		}
614 	}
615 }
616 
617 static void
618 print_vlangroup(struct cfg *cfg, int vlangroup)
619 {
620 	etherswitch_vlangroup_t vg;
621 	int i, comma;
622 
623 	vg.es_vlangroup = vlangroup;
624 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
625 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
626 	if ((vg.es_vid & ETHERSWITCH_VID_VALID) == 0)
627 		return;
628 	vg.es_vid &= ETHERSWITCH_VID_MASK;
629 	printf("vlangroup%d:\n", vlangroup);
630 	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT)
631 		printf("\tport: %d\n", vg.es_vid);
632 	else
633 		printf("\tvlan: %d\n", vg.es_vid);
634 	printf("\tmembers ");
635 	comma = 0;
636 	if (vg.es_member_ports != 0)
637 		for (i=0; i<cfg->info.es_nports; i++) {
638 			if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
639 				if (comma)
640 					printf(",");
641 				printf("%d", i);
642 				if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
643 					printf("t");
644 				comma = 1;
645 			}
646 		}
647 	else
648 		printf("none");
649 	printf("\n");
650 }
651 
652 static void
653 print_info(struct cfg *cfg)
654 {
655 	const char *c;
656 	int i;
657 
658 	c = strrchr(cfg->controlfile, '/');
659 	if (c != NULL)
660 		c = c + 1;
661 	else
662 		c = cfg->controlfile;
663 	if (cfg->verbose) {
664 		printf("%s: %s with %d ports and %d VLAN groups\n", c,
665 		    cfg->info.es_name, cfg->info.es_nports,
666 		    cfg->info.es_nvlangroups);
667 		printf("%s: ", c);
668 		printb("VLAN capabilities",  cfg->info.es_vlan_caps,
669 		    ETHERSWITCH_VLAN_CAPS_BITS);
670 		printf("\n");
671 	}
672 	print_config(cfg);
673 	for (i=0; i<cfg->info.es_nports; i++) {
674 		print_port(cfg, i);
675 	}
676 	for (i=0; i<cfg->info.es_nvlangroups; i++) {
677 		print_vlangroup(cfg, i);
678 	}
679 }
680 
681 static void
682 usage(struct cfg *cfg __unused, char *argv[] __unused)
683 {
684 	fprintf(stderr, "usage: etherswitchctl\n");
685 	fprintf(stderr, "\tetherswitchcfg [-f control file] info\n");
686 	fprintf(stderr, "\tetherswitchcfg [-f control file] config "
687 	    "command parameter\n");
688 	fprintf(stderr, "\t\tconfig commands: vlan_mode\n");
689 	fprintf(stderr, "\tetherswitchcfg [-f control file] phy "
690 	    "phy.register[=value]\n");
691 	fprintf(stderr, "\tetherswitchcfg [-f control file] portX "
692 	    "[flags] command parameter\n");
693 	fprintf(stderr, "\t\tport commands: pvid, media, mediaopt, led\n");
694 	fprintf(stderr, "\tetherswitchcfg [-f control file] reg "
695 	    "register[=value]\n");
696 	fprintf(stderr, "\tetherswitchcfg [-f control file] vlangroupX "
697 	    "command parameter\n");
698 	fprintf(stderr, "\t\tvlangroup commands: vlan, members\n");
699 	exit(EX_USAGE);
700 }
701 
702 static void
703 newmode(struct cfg *cfg, enum cmdmode mode)
704 {
705 	if (mode == cfg->mode)
706 		return;
707 	switch (cfg->mode) {
708 	case MODE_NONE:
709 		break;
710 	case MODE_CONFIG:
711 		/*
712 		 * Read the updated the configuration (it can be different
713 		 * from the last time we read it).
714 		 */
715 		if (ioctl(cfg->fd, IOETHERSWITCHGETCONF, &cfg->conf) != 0)
716 			err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
717 		print_config(cfg);
718 		break;
719 	case MODE_PORT:
720 		print_port(cfg, cfg->unit);
721 		break;
722 	case MODE_VLANGROUP:
723 		print_vlangroup(cfg, cfg->unit);
724 		break;
725 	case MODE_REGISTER:
726 	case MODE_PHYREG:
727 	case MODE_ATU:
728 		break;
729 	}
730 	cfg->mode = mode;
731 }
732 
733 int
734 main(int argc, char *argv[])
735 {
736 	int ch;
737 	struct cfg cfg;
738 	int i;
739 
740 	bzero(&cfg, sizeof(cfg));
741 	cfg.controlfile = "/dev/etherswitch0";
742 	while ((ch = getopt(argc, argv, "f:mv?")) != -1)
743 		switch(ch) {
744 		case 'f':
745 			cfg.controlfile = optarg;
746 			break;
747 		case 'm':
748 			cfg.mediatypes++;
749 			break;
750 		case 'v':
751 			cfg.verbose++;
752 			break;
753 		case '?':
754 			/* FALLTHROUGH */
755 		default:
756 			usage(&cfg, argv);
757 		}
758 	argc -= optind;
759 	argv += optind;
760 	cfg.fd = open(cfg.controlfile, O_RDONLY);
761 	if (cfg.fd < 0)
762 		err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
763 	if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
764 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
765 	if (ioctl(cfg.fd, IOETHERSWITCHGETCONF, &cfg.conf) != 0)
766 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
767 	if (argc == 0) {
768 		print_info(&cfg);
769 		return (0);
770 	}
771 	cfg.mode = MODE_NONE;
772 	while (argc > 0) {
773 		switch(cfg.mode) {
774 		case MODE_NONE:
775 			if (strcmp(argv[0], "info") == 0) {
776 				print_info(&cfg);
777 			} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
778 				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
779 					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports - 1);
780 				newmode(&cfg, MODE_PORT);
781 			} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
782 				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
783 					errx(EX_USAGE,
784 					    "vlangroup unit must be between 0 and %d",
785 					    cfg.info.es_nvlangroups - 1);
786 				newmode(&cfg, MODE_VLANGROUP);
787 			} else if (strcmp(argv[0], "config") == 0) {
788 				newmode(&cfg, MODE_CONFIG);
789 			} else if (strcmp(argv[0], "phy") == 0) {
790 				newmode(&cfg, MODE_PHYREG);
791 			} else if (strcmp(argv[0], "reg") == 0) {
792 				newmode(&cfg, MODE_REGISTER);
793 			} else if (strcmp(argv[0], "help") == 0) {
794 				usage(&cfg, argv);
795 			} else if (strcmp(argv[0], "atu") == 0) {
796 				newmode(&cfg, MODE_ATU);
797 			} else {
798 				errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
799 			}
800 			break;
801 		case MODE_PORT:
802 		case MODE_CONFIG:
803 		case MODE_VLANGROUP:
804 		case MODE_ATU:
805 			for(i=0; cmds[i].name != NULL; i++) {
806 				int r;
807 				if (cfg.mode == cmds[i].mode &&
808 				    strcmp(argv[0], cmds[i].name) == 0) {
809 					if ((cmds[i].args != -1) &&
810 					    (argc < (cmds[i].args + 1))) {
811 						printf("%s needs %d argument%s\n",
812 						    cmds[i].name, cmds[i].args,
813 						    (cmds[i].args==1)?"":",");
814 						break;
815 					}
816 
817 					r = (cmds[i].f)(&cfg, argc, argv);
818 
819 					/* -1 here means "error" */
820 					if (r == -1) {
821 						argc = 0;
822 						break;
823 					}
824 
825 					/* Legacy return value */
826 					if (r == 0)
827 						r = cmds[i].args;
828 
829 					argc -= r;
830 					argv += r;
831 					break;
832 				}
833 			}
834 			if (cmds[i].name == NULL) {
835 				newmode(&cfg, MODE_NONE);
836 				continue;
837 			}
838 			break;
839 		case MODE_REGISTER:
840 			if (set_register(&cfg, argv[0]) != 0) {
841 				newmode(&cfg, MODE_NONE);
842 				continue;
843 			}
844 			break;
845 		case MODE_PHYREG:
846 			if (set_phyregister(&cfg, argv[0]) != 0) {
847 				newmode(&cfg, MODE_NONE);
848 				continue;
849 			}
850 			break;
851 		}
852 		argc--;
853 		argv++;
854 	}
855 	/* switch back to command mode to print configuration for last command */
856 	newmode(&cfg, MODE_NONE);
857 	close(cfg.fd);
858 	return (0);
859 }
860 
861 static struct cmds cmds[] = {
862 	{ MODE_PORT, "pvid", 1, set_port_vid },
863 	{ MODE_PORT, "media", 1, set_port_media },
864 	{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
865 	{ MODE_PORT, "led", 2, set_port_led },
866 	{ MODE_PORT, "addtag", 0, set_port_flag },
867 	{ MODE_PORT, "-addtag", 0, set_port_flag },
868 	{ MODE_PORT, "ingress", 0, set_port_flag },
869 	{ MODE_PORT, "-ingress", 0, set_port_flag },
870 	{ MODE_PORT, "striptag", 0, set_port_flag },
871 	{ MODE_PORT, "-striptag", 0, set_port_flag },
872 	{ MODE_PORT, "doubletag", 0, set_port_flag },
873 	{ MODE_PORT, "-doubletag", 0, set_port_flag },
874 	{ MODE_PORT, "firstlock", 0, set_port_flag },
875 	{ MODE_PORT, "-firstlock", 0, set_port_flag },
876 	{ MODE_PORT, "droptagged", 0, set_port_flag },
877 	{ MODE_PORT, "-droptagged", 0, set_port_flag },
878 	{ MODE_PORT, "dropuntagged", 0, set_port_flag },
879 	{ MODE_PORT, "-dropuntagged", 0, set_port_flag },
880 	{ MODE_CONFIG, "vlan_mode", 1, set_vlan_mode },
881 	{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
882 	{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
883 	{ MODE_ATU, "flush", -1, atu_flush },
884 	{ MODE_ATU, "dump", -1, atu_dump },
885 	{ 0, NULL, 0, NULL }
886 };
887