1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
29#include <sys/cdefs.h>
30#include <ctype.h>
31#include <err.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <sysexits.h>
38#include <unistd.h>
39#include <sys/types.h>
40#include <sys/ioctl.h>
41#include <net/if.h>
42#include <net/if_media.h>
43#include <dev/etherswitch/etherswitch.h>
44
45int	get_media_subtype(int, const char *);
46int	get_media_mode(int, const char *);
47int	get_media_options(int, const char *);
48int	lookup_media_word(struct ifmedia_description *, const char *);
49void    print_media_word(int, int);
50void    print_media_word_ifconfig(int);
51
52/* some constants */
53#define IEEE802DOT1Q_VID_MAX	4094
54#define IFMEDIAREQ_NULISTENTRIES	256
55
56enum cmdmode {
57	MODE_NONE = 0,
58	MODE_PORT,
59	MODE_CONFIG,
60	MODE_VLANGROUP,
61	MODE_REGISTER,
62	MODE_PHYREG,
63	MODE_ATU
64};
65
66struct cfg {
67	int					fd;
68	int					verbose;
69	int					mediatypes;
70	const char			*controlfile;
71	etherswitch_conf_t	conf;
72	etherswitch_info_t	info;
73	enum cmdmode		mode;
74	int					unit;
75};
76
77struct cmds {
78	enum cmdmode	mode;
79	const char	*name;
80	int		args;
81	int		(*f)(struct cfg *, int argc, char *argv[]);
82};
83static struct cmds cmds[];
84
85/* Must match the ETHERSWITCH_PORT_LED_* enum order */
86static const char *ledstyles[] = { "default", "on", "off", "blink", NULL };
87
88/*
89 * Print a value a la the %b format of the kernel's printf.
90 * Stolen from ifconfig.c.
91 */
92static void
93printb(const char *s, unsigned v, const char *bits)
94{
95	int i, any = 0;
96	char c;
97
98	if (bits && *bits == 8)
99		printf("%s=%o", s, v);
100	else
101		printf("%s=%x", s, v);
102	bits++;
103	if (bits) {
104		putchar('<');
105		while ((i = *bits++) != '\0') {
106			if (v & (1 << (i-1))) {
107				if (any)
108					putchar(',');
109				any = 1;
110				for (; (c = *bits) > 32; bits++)
111					putchar(c);
112			} else
113				for (; *bits > 32; bits++)
114					;
115		}
116		putchar('>');
117	}
118}
119
120static int
121read_register(struct cfg *cfg, int r)
122{
123	struct etherswitch_reg er;
124
125	er.reg = r;
126	if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
127		err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
128	return (er.val);
129}
130
131static void
132write_register(struct cfg *cfg, int r, int v)
133{
134	struct etherswitch_reg er;
135
136	er.reg = r;
137	er.val = v;
138	if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
139		err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
140}
141
142static int
143read_phyregister(struct cfg *cfg, int phy, int reg)
144{
145	struct etherswitch_phyreg er;
146
147	er.phy = phy;
148	er.reg = reg;
149	if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
150		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
151	return (er.val);
152}
153
154static void
155write_phyregister(struct cfg *cfg, int phy, int reg, int val)
156{
157	struct etherswitch_phyreg er;
158
159	er.phy = phy;
160	er.reg = reg;
161	er.val = val;
162	if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
163		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
164}
165
166static int
167set_port_vid(struct cfg *cfg, int argc, char *argv[])
168{
169	int v;
170	etherswitch_port_t p;
171
172	if (argc < 2)
173		return (-1);
174
175	v = strtol(argv[1], NULL, 0);
176	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
177		errx(EX_USAGE, "pvid must be between 0 and %d",
178		    IEEE802DOT1Q_VID_MAX);
179	bzero(&p, sizeof(p));
180	p.es_port = cfg->unit;
181	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
182		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
183	p.es_pvid = v;
184	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
185		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
186	return (0);
187}
188
189static int
190set_port_flag(struct cfg *cfg, int argc, char *argv[])
191{
192	char *flag;
193	int n;
194	uint32_t f;
195	etherswitch_port_t p;
196
197	if (argc < 1)
198		return (-1);
199
200	n = 0;
201	f = 0;
202	flag = argv[0];
203	if (strcmp(flag, "none") != 0) {
204		if (*flag == '-') {
205			n++;
206			flag++;
207		}
208		if (strcasecmp(flag, "striptag") == 0)
209			f = ETHERSWITCH_PORT_STRIPTAG;
210		else if (strcasecmp(flag, "addtag") == 0)
211			f = ETHERSWITCH_PORT_ADDTAG;
212		else if (strcasecmp(flag, "firstlock") == 0)
213			f = ETHERSWITCH_PORT_FIRSTLOCK;
214		else if (strcasecmp(flag, "droptagged") == 0)
215			f = ETHERSWITCH_PORT_DROPTAGGED;
216		else if (strcasecmp(flag, "dropuntagged") == 0)
217			f = ETHERSWITCH_PORT_DROPUNTAGGED;
218		else if (strcasecmp(flag, "doubletag") == 0)
219			f = ETHERSWITCH_PORT_DOUBLE_TAG;
220		else if (strcasecmp(flag, "ingress") == 0)
221			f = ETHERSWITCH_PORT_INGRESS;
222		else if (strcasecmp(flag, "striptagingress") == 0)
223			f = ETHERSWITCH_PORT_STRIPTAGINGRESS;
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
238static int
239set_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
264static int
265set_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
293static int
294set_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
330static int
331set_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
352static int
353set_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
395static int
396set_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
412static int
413set_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
435static int
436set_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
463static int
464atu_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
493static int
494atu_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
524static void
525print_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
568static void
569print_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
615static void
616print_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
650static void
651print_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
679static void
680usage(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
700static void
701newmode(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
731int
732main(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
859static 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, "striptagingress", 0, set_port_flag },
871	{ MODE_PORT, "-striptagingress", 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