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