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, "dropuntagged") == 0)
219 			f = ETHERSWITCH_PORT_DROPUNTAGGED;
220 		else if (strcasecmp(flag, "doubletag") == 0)
221 			f = ETHERSWITCH_PORT_DOUBLE_TAG;
222 		else if (strcasecmp(flag, "ingress") == 0)
223 			f = ETHERSWITCH_PORT_INGRESS;
224 	}
225 	bzero(&p, sizeof(p));
226 	p.es_port = cfg->unit;
227 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
228 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
229 	if (n)
230 		p.es_flags &= ~f;
231 	else
232 		p.es_flags |= f;
233 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
234 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
235 	return (0);
236 }
237 
238 static int
239 set_port_media(struct cfg *cfg, int argc, char *argv[])
240 {
241 	etherswitch_port_t p;
242 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
243 	int subtype;
244 
245 	if (argc < 2)
246 		return (-1);
247 
248 	bzero(&p, sizeof(p));
249 	p.es_port = cfg->unit;
250 	p.es_ifmr.ifm_ulist = ifm_ulist;
251 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
252 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
253 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
254 	if (p.es_ifmr.ifm_count == 0)
255 		return (0);
256 	subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
257 	p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
258 	        IFM_TYPE(ifm_ulist[0]) | subtype;
259 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
260 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
261 	return (0);
262 }
263 
264 static int
265 set_port_mediaopt(struct cfg *cfg, int argc, char *argv[])
266 {
267 	etherswitch_port_t p;
268 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
269 	int options;
270 
271 	if (argc < 2)
272 		return (-1);
273 
274 	bzero(&p, sizeof(p));
275 	p.es_port = cfg->unit;
276 	p.es_ifmr.ifm_ulist = ifm_ulist;
277 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
278 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
279 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
280 	options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
281 	if (options == -1)
282 		errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
283 	if (options & IFM_HDX) {
284 		p.es_ifr.ifr_media &= ~IFM_FDX;
285 		options &= ~IFM_HDX;
286 	}
287 	p.es_ifr.ifr_media |= options;
288 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
289 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
290 	return (0);
291 }
292 
293 static int
294 set_port_led(struct cfg *cfg, int argc, char *argv[])
295 {
296 	etherswitch_port_t p;
297 	int led;
298 	int i;
299 
300 	if (argc < 3)
301 		return (-1);
302 
303 	bzero(&p, sizeof(p));
304 	p.es_port = cfg->unit;
305 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
306 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
307 
308 	led = strtol(argv[1], NULL, 0);
309 	if (led < 1 || led > p.es_nleds)
310 		errx(EX_USAGE, "invalid led number %s; must be between 1 and %d",
311 			argv[1], p.es_nleds);
312 
313 	led--;
314 
315 	for (i=0; ledstyles[i] != NULL; i++) {
316 		if (strcmp(argv[2], ledstyles[i]) == 0) {
317 			p.es_led[led] = i;
318 			break;
319 		}
320 	}
321 	if (ledstyles[i] == NULL)
322 		errx(EX_USAGE, "invalid led style \"%s\"", argv[2]);
323 
324 	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
325 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
326 
327 	return (0);
328 }
329 
330 static int
331 set_vlangroup_vid(struct cfg *cfg, int argc, char *argv[])
332 {
333 	int v;
334 	etherswitch_vlangroup_t vg;
335 
336 	if (argc < 2)
337 		return (-1);
338 
339 	memset(&vg, 0, sizeof(vg));
340 	v = strtol(argv[1], NULL, 0);
341 	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
342 		errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
343 	vg.es_vlangroup = cfg->unit;
344 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
345 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
346 	vg.es_vid = v;
347 	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
348 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
349 	return (0);
350 }
351 
352 static int
353 set_vlangroup_members(struct cfg *cfg, int argc, char *argv[])
354 {
355 	etherswitch_vlangroup_t vg;
356 	int member, untagged;
357 	char *c, *d;
358 	int v;
359 
360 	if (argc < 2)
361 		return (-1);
362 
363 	member = untagged = 0;
364 	memset(&vg, 0, sizeof(vg));
365 	if (strcmp(argv[1], "none") != 0) {
366 		for (c=argv[1]; *c; c=d) {
367 			v = strtol(c, &d, 0);
368 			if (d == c)
369 				break;
370 			if (v < 0 || v >= cfg->info.es_nports)
371 				errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
372 			if (d[0] == ',' || d[0] == '\0' ||
373 				((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
374 				if (d[0] == 't' || d[0] == 'T') {
375 					untagged &= ~ETHERSWITCH_PORTMASK(v);
376 					d++;
377 				} else
378 					untagged |= ETHERSWITCH_PORTMASK(v);
379 				member |= ETHERSWITCH_PORTMASK(v);
380 				d++;
381 			} else
382 				errx(EX_USAGE, "Invalid members specification \"%s\"", d);
383 		}
384 	}
385 	vg.es_vlangroup = cfg->unit;
386 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
387 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
388 	vg.es_member_ports = member;
389 	vg.es_untagged_ports = untagged;
390 	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
391 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
392 	return (0);
393 }
394 
395 static int
396 set_register(struct cfg *cfg, char *arg)
397 {
398 	int a, v;
399 	char *c;
400 
401 	a = strtol(arg, &c, 0);
402 	if (c==arg)
403 		return (1);
404 	if (*c == '=') {
405 		v = strtoul(c+1, NULL, 0);
406 		write_register(cfg, a, v);
407 	}
408 	printf("\treg 0x%04x=0x%08x\n", a, read_register(cfg, a));
409 	return (0);
410 }
411 
412 static int
413 set_phyregister(struct cfg *cfg, char *arg)
414 {
415 	int phy, reg, val;
416 	char *c, *d;
417 
418 	phy = strtol(arg, &c, 0);
419 	if (c==arg)
420 		return (1);
421 	if (*c != '.')
422 		return (1);
423 	d = c+1;
424 	reg = strtol(d, &c, 0);
425 	if (d == c)
426 		return (1);
427 	if (*c == '=') {
428 		val = strtoul(c+1, NULL, 0);
429 		write_phyregister(cfg, phy, reg, val);
430 	}
431 	printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
432 	return (0);
433 }
434 
435 static int
436 set_vlan_mode(struct cfg *cfg, int argc, char *argv[])
437 {
438 	etherswitch_conf_t conf;
439 
440 	if (argc < 2)
441 		return (-1);
442 
443 	bzero(&conf, sizeof(conf));
444 	conf.cmd = ETHERSWITCH_CONF_VLAN_MODE;
445 	if (strcasecmp(argv[1], "isl") == 0)
446 		conf.vlan_mode = ETHERSWITCH_VLAN_ISL;
447 	else if (strcasecmp(argv[1], "port") == 0)
448 		conf.vlan_mode = ETHERSWITCH_VLAN_PORT;
449 	else if (strcasecmp(argv[1], "dot1q") == 0)
450 		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
451 	else if (strcasecmp(argv[1], "dot1q4k") == 0)
452 		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q_4K;
453 	else if (strcasecmp(argv[1], "qinq") == 0)
454 		conf.vlan_mode = ETHERSWITCH_VLAN_DOUBLE_TAG;
455 	else
456 		conf.vlan_mode = 0;
457 	if (ioctl(cfg->fd, IOETHERSWITCHSETCONF, &conf) != 0)
458 		err(EX_OSERR, "ioctl(IOETHERSWITCHSETCONF)");
459 
460 	return (0);
461 }
462 
463 static int
464 atu_flush(struct cfg *cfg, int argc, char *argv[])
465 {
466 	etherswitch_portid_t p;
467 	int i, r;
468 
469 	bzero(&p, sizeof(p));
470 
471 	/* note: argv[0] is "flush" */
472 	if (argc > 2 && strcasecmp(argv[1], "port") == 0) {
473 		p.es_port = atoi(argv[2]);
474 		i = IOETHERSWITCHFLUSHPORT;
475 		r = 3;
476 	} else if (argc > 1 && strcasecmp(argv[1], "all") == 0) {
477 		p.es_port = 0;
478 		r = 2;
479 		i = IOETHERSWITCHFLUSHALL;
480 	} else {
481 		fprintf(stderr,
482 		    "%s: invalid verb (port <x> or all) (got %s)\n",
483 		    __func__, argv[1]);
484 		return (-1);
485 	}
486 
487 	if (ioctl(cfg->fd, i, &p) != 0)
488 		err(EX_OSERR, "ioctl(ATU flush (ioctl %d, port %d))",
489 		    i, p.es_port);
490 	return (r);
491 }
492 
493 static int
494 atu_dump(struct cfg *cfg, int argc, char *argv[])
495 {
496 	etherswitch_atu_table_t p;
497 	etherswitch_atu_entry_t e;
498 	uint32_t i;
499 
500 	(void) argc;
501 	(void) argv;
502 
503 	/* Note: argv[0] is "dump" */
504 	bzero(&p, sizeof(p));
505 
506 	if (ioctl(cfg->fd, IOETHERSWITCHGETTABLE, &p) != 0)
507 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETTABLE)");
508 
509 	/* And now, iterate to get entries */
510 	for (i = 0; i < p.es_nitems; i++) {
511 		bzero(&e, sizeof(e));
512 		e.id = i;
513 		if (ioctl(cfg->fd, IOETHERSWITCHGETTABLEENTRY, &e) != 0)
514 			break;
515 
516 		printf(" [%d] %s: portmask 0x%08x\n", i,
517 		    ether_ntoa((void *) &e.es_macaddr),
518 		    e.es_portmask);
519 	}
520 
521 	return (1);
522 }
523 
524 static void
525 print_config(struct cfg *cfg)
526 {
527 	const char *c;
528 
529 	/* Get the device name. */
530 	c = strrchr(cfg->controlfile, '/');
531 	if (c != NULL)
532 		c = c + 1;
533 	else
534 		c = cfg->controlfile;
535 
536 	/* Print VLAN mode. */
537 	if (cfg->conf.cmd & ETHERSWITCH_CONF_VLAN_MODE) {
538 		printf("%s: VLAN mode: ", c);
539 		switch (cfg->conf.vlan_mode) {
540 		case ETHERSWITCH_VLAN_ISL:
541 			printf("ISL\n");
542 			break;
543 		case ETHERSWITCH_VLAN_PORT:
544 			printf("PORT\n");
545 			break;
546 		case ETHERSWITCH_VLAN_DOT1Q:
547 			printf("DOT1Q\n");
548 			break;
549 		case ETHERSWITCH_VLAN_DOT1Q_4K:
550 			printf("DOT1Q4K\n");
551 			break;
552 		case ETHERSWITCH_VLAN_DOUBLE_TAG:
553 			printf("QinQ\n");
554 			break;
555 		default:
556 			printf("none\n");
557 		}
558 	}
559 
560 	/* Print switch MAC address. */
561 	if (cfg->conf.cmd & ETHERSWITCH_CONF_SWITCH_MACADDR) {
562 		printf("%s: Switch MAC address: %s\n",
563 		    c,
564 		    ether_ntoa(&cfg->conf.switch_macaddr));
565 	}
566 }
567 
568 static void
569 print_port(struct cfg *cfg, int port)
570 {
571 	etherswitch_port_t p;
572 	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
573 	int i;
574 
575 	bzero(&p, sizeof(p));
576 	p.es_port = port;
577 	p.es_ifmr.ifm_ulist = ifm_ulist;
578 	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
579 	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
580 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
581 	printf("port%d:\n", port);
582 	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
583 		printf("\tpvid: %d\n", p.es_pvid);
584 	printb("\tflags", p.es_flags, ETHERSWITCH_PORT_FLAGS_BITS);
585 	printf("\n");
586 	if (p.es_nleds) {
587 		printf("\tled: ");
588 		for (i = 0; i < p.es_nleds; i++) {
589 			printf("%d:%s%s", i+1, ledstyles[p.es_led[i]], (i==p.es_nleds-1)?"":" ");
590 		}
591 		printf("\n");
592 	}
593 	printf("\tmedia: ");
594 	print_media_word(p.es_ifmr.ifm_current, 1);
595 	if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
596 		putchar(' ');
597 		putchar('(');
598 		print_media_word(p.es_ifmr.ifm_active, 0);
599 		putchar(')');
600 	}
601 	putchar('\n');
602 	printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
603 	if (cfg->mediatypes) {
604 		printf("\tsupported media:\n");
605 		if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
606 			p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
607 		for (i=0; i<p.es_ifmr.ifm_count; i++) {
608 			printf("\t\tmedia ");
609 			print_media_word(ifm_ulist[i], 0);
610 			putchar('\n');
611 		}
612 	}
613 }
614 
615 static void
616 print_vlangroup(struct cfg *cfg, int vlangroup)
617 {
618 	etherswitch_vlangroup_t vg;
619 	int i, comma;
620 
621 	vg.es_vlangroup = vlangroup;
622 	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
623 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
624 	if ((vg.es_vid & ETHERSWITCH_VID_VALID) == 0)
625 		return;
626 	vg.es_vid &= ETHERSWITCH_VID_MASK;
627 	printf("vlangroup%d:\n", vlangroup);
628 	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT)
629 		printf("\tport: %d\n", vg.es_vid);
630 	else
631 		printf("\tvlan: %d\n", vg.es_vid);
632 	printf("\tmembers ");
633 	comma = 0;
634 	if (vg.es_member_ports != 0)
635 		for (i=0; i<cfg->info.es_nports; i++) {
636 			if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
637 				if (comma)
638 					printf(",");
639 				printf("%d", i);
640 				if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
641 					printf("t");
642 				comma = 1;
643 			}
644 		}
645 	else
646 		printf("none");
647 	printf("\n");
648 }
649 
650 static void
651 print_info(struct cfg *cfg)
652 {
653 	const char *c;
654 	int i;
655 
656 	c = strrchr(cfg->controlfile, '/');
657 	if (c != NULL)
658 		c = c + 1;
659 	else
660 		c = cfg->controlfile;
661 	if (cfg->verbose) {
662 		printf("%s: %s with %d ports and %d VLAN groups\n", c,
663 		    cfg->info.es_name, cfg->info.es_nports,
664 		    cfg->info.es_nvlangroups);
665 		printf("%s: ", c);
666 		printb("VLAN capabilities",  cfg->info.es_vlan_caps,
667 		    ETHERSWITCH_VLAN_CAPS_BITS);
668 		printf("\n");
669 	}
670 	print_config(cfg);
671 	for (i=0; i<cfg->info.es_nports; i++) {
672 		print_port(cfg, i);
673 	}
674 	for (i=0; i<cfg->info.es_nvlangroups; i++) {
675 		print_vlangroup(cfg, i);
676 	}
677 }
678 
679 static void
680 usage(struct cfg *cfg __unused, char *argv[] __unused)
681 {
682 	fprintf(stderr, "usage: etherswitchctl\n");
683 	fprintf(stderr, "\tetherswitchcfg [-f control file] info\n");
684 	fprintf(stderr, "\tetherswitchcfg [-f control file] config "
685 	    "command parameter\n");
686 	fprintf(stderr, "\t\tconfig commands: vlan_mode\n");
687 	fprintf(stderr, "\tetherswitchcfg [-f control file] phy "
688 	    "phy.register[=value]\n");
689 	fprintf(stderr, "\tetherswitchcfg [-f control file] portX "
690 	    "[flags] command parameter\n");
691 	fprintf(stderr, "\t\tport commands: pvid, media, mediaopt, led\n");
692 	fprintf(stderr, "\tetherswitchcfg [-f control file] reg "
693 	    "register[=value]\n");
694 	fprintf(stderr, "\tetherswitchcfg [-f control file] vlangroupX "
695 	    "command parameter\n");
696 	fprintf(stderr, "\t\tvlangroup commands: vlan, members\n");
697 	exit(EX_USAGE);
698 }
699 
700 static void
701 newmode(struct cfg *cfg, enum cmdmode mode)
702 {
703 	if (mode == cfg->mode)
704 		return;
705 	switch (cfg->mode) {
706 	case MODE_NONE:
707 		break;
708 	case MODE_CONFIG:
709 		/*
710 		 * Read the updated the configuration (it can be different
711 		 * from the last time we read it).
712 		 */
713 		if (ioctl(cfg->fd, IOETHERSWITCHGETCONF, &cfg->conf) != 0)
714 			err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
715 		print_config(cfg);
716 		break;
717 	case MODE_PORT:
718 		print_port(cfg, cfg->unit);
719 		break;
720 	case MODE_VLANGROUP:
721 		print_vlangroup(cfg, cfg->unit);
722 		break;
723 	case MODE_REGISTER:
724 	case MODE_PHYREG:
725 	case MODE_ATU:
726 		break;
727 	}
728 	cfg->mode = mode;
729 }
730 
731 int
732 main(int argc, char *argv[])
733 {
734 	int ch;
735 	struct cfg cfg;
736 	int i;
737 
738 	bzero(&cfg, sizeof(cfg));
739 	cfg.controlfile = "/dev/etherswitch0";
740 	while ((ch = getopt(argc, argv, "f:mv?")) != -1)
741 		switch(ch) {
742 		case 'f':
743 			cfg.controlfile = optarg;
744 			break;
745 		case 'm':
746 			cfg.mediatypes++;
747 			break;
748 		case 'v':
749 			cfg.verbose++;
750 			break;
751 		case '?':
752 			/* FALLTHROUGH */
753 		default:
754 			usage(&cfg, argv);
755 		}
756 	argc -= optind;
757 	argv += optind;
758 	cfg.fd = open(cfg.controlfile, O_RDONLY);
759 	if (cfg.fd < 0)
760 		err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
761 	if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
762 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
763 	if (ioctl(cfg.fd, IOETHERSWITCHGETCONF, &cfg.conf) != 0)
764 		err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
765 	if (argc == 0) {
766 		print_info(&cfg);
767 		return (0);
768 	}
769 	cfg.mode = MODE_NONE;
770 	while (argc > 0) {
771 		switch(cfg.mode) {
772 		case MODE_NONE:
773 			if (strcmp(argv[0], "info") == 0) {
774 				print_info(&cfg);
775 			} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
776 				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
777 					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports - 1);
778 				newmode(&cfg, MODE_PORT);
779 			} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
780 				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
781 					errx(EX_USAGE,
782 					    "vlangroup unit must be between 0 and %d",
783 					    cfg.info.es_nvlangroups - 1);
784 				newmode(&cfg, MODE_VLANGROUP);
785 			} else if (strcmp(argv[0], "config") == 0) {
786 				newmode(&cfg, MODE_CONFIG);
787 			} else if (strcmp(argv[0], "phy") == 0) {
788 				newmode(&cfg, MODE_PHYREG);
789 			} else if (strcmp(argv[0], "reg") == 0) {
790 				newmode(&cfg, MODE_REGISTER);
791 			} else if (strcmp(argv[0], "help") == 0) {
792 				usage(&cfg, argv);
793 			} else if (strcmp(argv[0], "atu") == 0) {
794 				newmode(&cfg, MODE_ATU);
795 			} else {
796 				errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
797 			}
798 			break;
799 		case MODE_PORT:
800 		case MODE_CONFIG:
801 		case MODE_VLANGROUP:
802 		case MODE_ATU:
803 			for(i=0; cmds[i].name != NULL; i++) {
804 				int r;
805 				if (cfg.mode == cmds[i].mode &&
806 				    strcmp(argv[0], cmds[i].name) == 0) {
807 					if ((cmds[i].args != -1) &&
808 					    (argc < (cmds[i].args + 1))) {
809 						printf("%s needs %d argument%s\n",
810 						    cmds[i].name, cmds[i].args,
811 						    (cmds[i].args==1)?"":",");
812 						break;
813 					}
814 
815 					r = (cmds[i].f)(&cfg, argc, argv);
816 
817 					/* -1 here means "error" */
818 					if (r == -1) {
819 						argc = 0;
820 						break;
821 					}
822 
823 					/* Legacy return value */
824 					if (r == 0)
825 						r = cmds[i].args;
826 
827 					argc -= r;
828 					argv += r;
829 					break;
830 				}
831 			}
832 			if (cmds[i].name == NULL) {
833 				newmode(&cfg, MODE_NONE);
834 				continue;
835 			}
836 			break;
837 		case MODE_REGISTER:
838 			if (set_register(&cfg, argv[0]) != 0) {
839 				newmode(&cfg, MODE_NONE);
840 				continue;
841 			}
842 			break;
843 		case MODE_PHYREG:
844 			if (set_phyregister(&cfg, argv[0]) != 0) {
845 				newmode(&cfg, MODE_NONE);
846 				continue;
847 			}
848 			break;
849 		}
850 		argc--;
851 		argv++;
852 	}
853 	/* switch back to command mode to print configuration for last command */
854 	newmode(&cfg, MODE_NONE);
855 	close(cfg.fd);
856 	return (0);
857 }
858 
859 static struct cmds cmds[] = {
860 	{ MODE_PORT, "pvid", 1, set_port_vid },
861 	{ MODE_PORT, "media", 1, set_port_media },
862 	{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
863 	{ MODE_PORT, "led", 2, set_port_led },
864 	{ MODE_PORT, "addtag", 0, set_port_flag },
865 	{ MODE_PORT, "-addtag", 0, set_port_flag },
866 	{ MODE_PORT, "ingress", 0, set_port_flag },
867 	{ MODE_PORT, "-ingress", 0, set_port_flag },
868 	{ MODE_PORT, "striptag", 0, set_port_flag },
869 	{ MODE_PORT, "-striptag", 0, set_port_flag },
870 	{ MODE_PORT, "doubletag", 0, set_port_flag },
871 	{ MODE_PORT, "-doubletag", 0, set_port_flag },
872 	{ MODE_PORT, "firstlock", 0, set_port_flag },
873 	{ MODE_PORT, "-firstlock", 0, set_port_flag },
874 	{ MODE_PORT, "dropuntagged", 0, set_port_flag },
875 	{ MODE_PORT, "-dropuntagged", 0, set_port_flag },
876 	{ MODE_CONFIG, "vlan_mode", 1, set_vlan_mode },
877 	{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
878 	{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
879 	{ MODE_ATU, "flush", -1, atu_flush },
880 	{ MODE_ATU, "dump", -1, atu_dump },
881 	{ 0, NULL, 0, NULL }
882 };
883