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: stable/11/sbin/etherswitchcfg/etherswitchcfg.c 330449 2018-03-05 07:26:05Z eadler $
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: stable/11/sbin/etherswitchcfg/etherswitchcfg.c 330449 2018-03-05 07:26:05Z eadler $");
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
49int	get_media_subtype(int, const char *);
50int	get_media_mode(int, const char *);
51int	get_media_options(int, const char *);
52int	lookup_media_word(struct ifmedia_description *, const char *);
53void    print_media_word(int, int);
54void    print_media_word_ifconfig(int);
55
56/* some constants */
57#define IEEE802DOT1Q_VID_MAX	4094
58#define IFMEDIAREQ_NULISTENTRIES	256
59
60enum cmdmode {
61	MODE_NONE = 0,
62	MODE_PORT,
63	MODE_CONFIG,
64	MODE_VLANGROUP,
65	MODE_REGISTER,
66	MODE_PHYREG
67};
68
69struct cfg {
70	int					fd;
71	int					verbose;
72	int					mediatypes;
73	const char			*controlfile;
74	etherswitch_conf_t	conf;
75	etherswitch_info_t	info;
76	enum cmdmode		mode;
77	int					unit;
78};
79
80struct cmds {
81	enum cmdmode	mode;
82	const char		*name;
83	int				args;
84	void 			(*f)(struct cfg *, char *argv[]);
85};
86static struct cmds cmds[];
87
88
89/*
90 * Print a value a la the %b format of the kernel's printf.
91 * Stolen from ifconfig.c.
92 */
93static void
94printb(const char *s, unsigned v, const char *bits)
95{
96	int i, any = 0;
97	char c;
98
99	if (bits && *bits == 8)
100		printf("%s=%o", s, v);
101	else
102		printf("%s=%x", s, v);
103	bits++;
104	if (bits) {
105		putchar('<');
106		while ((i = *bits++) != '\0') {
107			if (v & (1 << (i-1))) {
108				if (any)
109					putchar(',');
110				any = 1;
111				for (; (c = *bits) > 32; bits++)
112					putchar(c);
113			} else
114				for (; *bits > 32; bits++)
115					;
116		}
117		putchar('>');
118	}
119}
120
121static int
122read_register(struct cfg *cfg, int r)
123{
124	struct etherswitch_reg er;
125
126	er.reg = r;
127	if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
128		err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
129	return (er.val);
130}
131
132static void
133write_register(struct cfg *cfg, int r, int v)
134{
135	struct etherswitch_reg er;
136
137	er.reg = r;
138	er.val = v;
139	if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
140		err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
141}
142
143static int
144read_phyregister(struct cfg *cfg, int phy, int reg)
145{
146	struct etherswitch_phyreg er;
147
148	er.phy = phy;
149	er.reg = reg;
150	if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
151		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
152	return (er.val);
153}
154
155static void
156write_phyregister(struct cfg *cfg, int phy, int reg, int val)
157{
158	struct etherswitch_phyreg er;
159
160	er.phy = phy;
161	er.reg = reg;
162	er.val = val;
163	if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
164		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
165}
166
167static void
168set_port_vid(struct cfg *cfg, char *argv[])
169{
170	int v;
171	etherswitch_port_t p;
172
173	v = strtol(argv[1], NULL, 0);
174	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
175		errx(EX_USAGE, "pvid must be between 0 and %d",
176		    IEEE802DOT1Q_VID_MAX);
177	bzero(&p, sizeof(p));
178	p.es_port = cfg->unit;
179	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
180		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
181	p.es_pvid = v;
182	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
183		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
184}
185
186static void
187set_port_flag(struct cfg *cfg, char *argv[])
188{
189	char *flag;
190	int n;
191	uint32_t f;
192	etherswitch_port_t p;
193
194	n = 0;
195	f = 0;
196	flag = argv[0];
197	if (strcmp(flag, "none") != 0) {
198		if (*flag == '-') {
199			n++;
200			flag++;
201		}
202		if (strcasecmp(flag, "striptag") == 0)
203			f = ETHERSWITCH_PORT_STRIPTAG;
204		else if (strcasecmp(flag, "addtag") == 0)
205			f = ETHERSWITCH_PORT_ADDTAG;
206		else if (strcasecmp(flag, "firstlock") == 0)
207			f = ETHERSWITCH_PORT_FIRSTLOCK;
208		else if (strcasecmp(flag, "dropuntagged") == 0)
209			f = ETHERSWITCH_PORT_DROPUNTAGGED;
210		else if (strcasecmp(flag, "doubletag") == 0)
211			f = ETHERSWITCH_PORT_DOUBLE_TAG;
212		else if (strcasecmp(flag, "ingress") == 0)
213			f = ETHERSWITCH_PORT_INGRESS;
214	}
215	bzero(&p, sizeof(p));
216	p.es_port = cfg->unit;
217	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
218		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
219	if (n)
220		p.es_flags &= ~f;
221	else
222		p.es_flags |= f;
223	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
224		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
225}
226
227static void
228set_port_media(struct cfg *cfg, char *argv[])
229{
230	etherswitch_port_t p;
231	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
232	int subtype;
233
234	bzero(&p, sizeof(p));
235	p.es_port = cfg->unit;
236	p.es_ifmr.ifm_ulist = ifm_ulist;
237	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
238	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
239		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
240	if (p.es_ifmr.ifm_count == 0)
241		return;
242	subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
243	p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
244	        IFM_TYPE(ifm_ulist[0]) | subtype;
245	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
246		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
247}
248
249static void
250set_port_mediaopt(struct cfg *cfg, char *argv[])
251{
252	etherswitch_port_t p;
253	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
254	int options;
255
256	bzero(&p, sizeof(p));
257	p.es_port = cfg->unit;
258	p.es_ifmr.ifm_ulist = ifm_ulist;
259	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
260	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
261		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
262	options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
263	if (options == -1)
264		errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
265	if (options & IFM_HDX) {
266		p.es_ifr.ifr_media &= ~IFM_FDX;
267		options &= ~IFM_HDX;
268	}
269	p.es_ifr.ifr_media |= options;
270	if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
271		err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
272}
273
274static void
275set_vlangroup_vid(struct cfg *cfg, char *argv[])
276{
277	int v;
278	etherswitch_vlangroup_t vg;
279
280	memset(&vg, 0, sizeof(vg));
281	v = strtol(argv[1], NULL, 0);
282	if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
283		errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
284	vg.es_vlangroup = cfg->unit;
285	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
286		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
287	vg.es_vid = v;
288	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
289		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
290}
291
292static void
293set_vlangroup_members(struct cfg *cfg, char *argv[])
294{
295	etherswitch_vlangroup_t vg;
296	int member, untagged;
297	char *c, *d;
298	int v;
299
300	member = untagged = 0;
301	memset(&vg, 0, sizeof(vg));
302	if (strcmp(argv[1], "none") != 0) {
303		for (c=argv[1]; *c; c=d) {
304			v = strtol(c, &d, 0);
305			if (d == c)
306				break;
307			if (v < 0 || v >= cfg->info.es_nports)
308				errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
309			if (d[0] == ',' || d[0] == '\0' ||
310				((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
311				if (d[0] == 't' || d[0] == 'T') {
312					untagged &= ~ETHERSWITCH_PORTMASK(v);
313					d++;
314				} else
315					untagged |= ETHERSWITCH_PORTMASK(v);
316				member |= ETHERSWITCH_PORTMASK(v);
317				d++;
318			} else
319				errx(EX_USAGE, "Invalid members specification \"%s\"", d);
320		}
321	}
322	vg.es_vlangroup = cfg->unit;
323	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
324		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
325	vg.es_member_ports = member;
326	vg.es_untagged_ports = untagged;
327	if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
328		err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
329}
330
331static int
332set_register(struct cfg *cfg, char *arg)
333{
334	int a, v;
335	char *c;
336
337	a = strtol(arg, &c, 0);
338	if (c==arg)
339		return (1);
340	if (*c == '=') {
341		v = strtol(c+1, NULL, 0);
342		write_register(cfg, a, v);
343	}
344	printf("\treg 0x%04x=0x%04x\n", a, read_register(cfg, a));
345	return (0);
346}
347
348static int
349set_phyregister(struct cfg *cfg, char *arg)
350{
351	int phy, reg, val;
352	char *c, *d;
353
354	phy = strtol(arg, &c, 0);
355	if (c==arg)
356		return (1);
357	if (*c != '.')
358		return (1);
359	d = c+1;
360	reg = strtol(d, &c, 0);
361	if (d == c)
362		return (1);
363	if (*c == '=') {
364		val = strtol(c+1, NULL, 0);
365		write_phyregister(cfg, phy, reg, val);
366	}
367	printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
368	return (0);
369}
370
371static void
372set_vlan_mode(struct cfg *cfg, char *argv[])
373{
374	etherswitch_conf_t conf;
375
376	bzero(&conf, sizeof(conf));
377	conf.cmd = ETHERSWITCH_CONF_VLAN_MODE;
378	if (strcasecmp(argv[1], "isl") == 0)
379		conf.vlan_mode = ETHERSWITCH_VLAN_ISL;
380	else if (strcasecmp(argv[1], "port") == 0)
381		conf.vlan_mode = ETHERSWITCH_VLAN_PORT;
382	else if (strcasecmp(argv[1], "dot1q") == 0)
383		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
384	else if (strcasecmp(argv[1], "dot1q4k") == 0)
385		conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q_4K;
386	else if (strcasecmp(argv[1], "qinq") == 0)
387		conf.vlan_mode = ETHERSWITCH_VLAN_DOUBLE_TAG;
388	else
389		conf.vlan_mode = 0;
390	if (ioctl(cfg->fd, IOETHERSWITCHSETCONF, &conf) != 0)
391		err(EX_OSERR, "ioctl(IOETHERSWITCHSETCONF)");
392}
393
394static void
395print_config(struct cfg *cfg)
396{
397	const char *c;
398
399	/* Get the device name. */
400	c = strrchr(cfg->controlfile, '/');
401	if (c != NULL)
402		c = c + 1;
403	else
404		c = cfg->controlfile;
405
406	/* Print VLAN mode. */
407	if (cfg->conf.cmd & ETHERSWITCH_CONF_VLAN_MODE) {
408		printf("%s: VLAN mode: ", c);
409		switch (cfg->conf.vlan_mode) {
410		case ETHERSWITCH_VLAN_ISL:
411			printf("ISL\n");
412			break;
413		case ETHERSWITCH_VLAN_PORT:
414			printf("PORT\n");
415			break;
416		case ETHERSWITCH_VLAN_DOT1Q:
417			printf("DOT1Q\n");
418			break;
419		case ETHERSWITCH_VLAN_DOT1Q_4K:
420			printf("DOT1Q4K\n");
421			break;
422		case ETHERSWITCH_VLAN_DOUBLE_TAG:
423			printf("QinQ\n");
424			break;
425		default:
426			printf("none\n");
427		}
428	}
429}
430
431static void
432print_port(struct cfg *cfg, int port)
433{
434	etherswitch_port_t p;
435	int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
436	int i;
437
438	bzero(&p, sizeof(p));
439	p.es_port = port;
440	p.es_ifmr.ifm_ulist = ifm_ulist;
441	p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
442	if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
443		err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
444	printf("port%d:\n", port);
445	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
446		printf("\tpvid: %d\n", p.es_pvid);
447	printb("\tflags", p.es_flags, ETHERSWITCH_PORT_FLAGS_BITS);
448	printf("\n");
449	printf("\tmedia: ");
450	print_media_word(p.es_ifmr.ifm_current, 1);
451	if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
452		putchar(' ');
453		putchar('(');
454		print_media_word(p.es_ifmr.ifm_active, 0);
455		putchar(')');
456	}
457	putchar('\n');
458	printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
459	if (cfg->mediatypes) {
460		printf("\tsupported media:\n");
461		if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
462			p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
463		for (i=0; i<p.es_ifmr.ifm_count; i++) {
464			printf("\t\tmedia ");
465			print_media_word(ifm_ulist[i], 0);
466			putchar('\n');
467		}
468	}
469}
470
471static void
472print_vlangroup(struct cfg *cfg, int vlangroup)
473{
474	etherswitch_vlangroup_t vg;
475	int i, comma;
476
477	vg.es_vlangroup = vlangroup;
478	if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
479		err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
480	if ((vg.es_vid & ETHERSWITCH_VID_VALID) == 0)
481		return;
482	vg.es_vid &= ETHERSWITCH_VID_MASK;
483	printf("vlangroup%d:\n", vlangroup);
484	if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT)
485		printf("\tport: %d\n", vg.es_vid);
486	else
487		printf("\tvlan: %d\n", vg.es_vid);
488	printf("\tmembers ");
489	comma = 0;
490	if (vg.es_member_ports != 0)
491		for (i=0; i<cfg->info.es_nports; i++) {
492			if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
493				if (comma)
494					printf(",");
495				printf("%d", i);
496				if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
497					printf("t");
498				comma = 1;
499			}
500		}
501	else
502		printf("none");
503	printf("\n");
504}
505
506static void
507print_info(struct cfg *cfg)
508{
509	const char *c;
510	int i;
511
512	c = strrchr(cfg->controlfile, '/');
513	if (c != NULL)
514		c = c + 1;
515	else
516		c = cfg->controlfile;
517	if (cfg->verbose) {
518		printf("%s: %s with %d ports and %d VLAN groups\n", c,
519		    cfg->info.es_name, cfg->info.es_nports,
520		    cfg->info.es_nvlangroups);
521		printf("%s: ", c);
522		printb("VLAN capabilities",  cfg->info.es_vlan_caps,
523		    ETHERSWITCH_VLAN_CAPS_BITS);
524		printf("\n");
525	}
526	print_config(cfg);
527	for (i=0; i<cfg->info.es_nports; i++) {
528		print_port(cfg, i);
529	}
530	for (i=0; i<cfg->info.es_nvlangroups; i++) {
531		print_vlangroup(cfg, i);
532	}
533}
534
535static void
536usage(struct cfg *cfg __unused, char *argv[] __unused)
537{
538	fprintf(stderr, "usage: etherswitchctl\n");
539	fprintf(stderr, "\tetherswitchcfg [-f control file] info\n");
540	fprintf(stderr, "\tetherswitchcfg [-f control file] config "
541	    "command parameter\n");
542	fprintf(stderr, "\t\tconfig commands: vlan_mode\n");
543	fprintf(stderr, "\tetherswitchcfg [-f control file] phy "
544	    "phy.register[=value]\n");
545	fprintf(stderr, "\tetherswitchcfg [-f control file] portX "
546	    "[flags] command parameter\n");
547	fprintf(stderr, "\t\tport commands: pvid, media, mediaopt\n");
548	fprintf(stderr, "\tetherswitchcfg [-f control file] reg "
549	    "register[=value]\n");
550	fprintf(stderr, "\tetherswitchcfg [-f control file] vlangroupX "
551	    "command parameter\n");
552	fprintf(stderr, "\t\tvlangroup commands: vlan, members\n");
553	exit(EX_USAGE);
554}
555
556static void
557newmode(struct cfg *cfg, enum cmdmode mode)
558{
559	if (mode == cfg->mode)
560		return;
561	switch (cfg->mode) {
562	case MODE_NONE:
563		break;
564	case MODE_CONFIG:
565		/*
566		 * Read the updated the configuration (it can be different
567		 * from the last time we read it).
568		 */
569		if (ioctl(cfg->fd, IOETHERSWITCHGETCONF, &cfg->conf) != 0)
570			err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
571		print_config(cfg);
572		break;
573	case MODE_PORT:
574		print_port(cfg, cfg->unit);
575		break;
576	case MODE_VLANGROUP:
577		print_vlangroup(cfg, cfg->unit);
578		break;
579	case MODE_REGISTER:
580	case MODE_PHYREG:
581		break;
582	}
583	cfg->mode = mode;
584}
585
586int
587main(int argc, char *argv[])
588{
589	int ch;
590	struct cfg cfg;
591	int i;
592
593	bzero(&cfg, sizeof(cfg));
594	cfg.controlfile = "/dev/etherswitch0";
595	while ((ch = getopt(argc, argv, "f:mv?")) != -1)
596		switch(ch) {
597		case 'f':
598			cfg.controlfile = optarg;
599			break;
600		case 'm':
601			cfg.mediatypes++;
602			break;
603		case 'v':
604			cfg.verbose++;
605			break;
606		case '?':
607			/* FALLTHROUGH */
608		default:
609			usage(&cfg, argv);
610		}
611	argc -= optind;
612	argv += optind;
613	cfg.fd = open(cfg.controlfile, O_RDONLY);
614	if (cfg.fd < 0)
615		err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
616	if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
617		err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
618	if (ioctl(cfg.fd, IOETHERSWITCHGETCONF, &cfg.conf) != 0)
619		err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
620	if (argc == 0) {
621		print_info(&cfg);
622		return (0);
623	}
624	cfg.mode = MODE_NONE;
625	while (argc > 0) {
626		switch(cfg.mode) {
627		case MODE_NONE:
628			if (strcmp(argv[0], "info") == 0) {
629				print_info(&cfg);
630			} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
631				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
632					errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports - 1);
633				newmode(&cfg, MODE_PORT);
634			} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
635				if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
636					errx(EX_USAGE,
637					    "vlangroup unit must be between 0 and %d",
638					    cfg.info.es_nvlangroups - 1);
639				newmode(&cfg, MODE_VLANGROUP);
640			} else if (strcmp(argv[0], "config") == 0) {
641				newmode(&cfg, MODE_CONFIG);
642			} else if (strcmp(argv[0], "phy") == 0) {
643				newmode(&cfg, MODE_PHYREG);
644			} else if (strcmp(argv[0], "reg") == 0) {
645				newmode(&cfg, MODE_REGISTER);
646			} else if (strcmp(argv[0], "help") == 0) {
647				usage(&cfg, argv);
648			} else {
649				errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
650			}
651			break;
652		case MODE_PORT:
653		case MODE_CONFIG:
654		case MODE_VLANGROUP:
655			for(i=0; cmds[i].name != NULL; i++) {
656				if (cfg.mode == cmds[i].mode && strcmp(argv[0], cmds[i].name) == 0) {
657					if (argc < (cmds[i].args + 1)) {
658						printf("%s needs an argument\n", cmds[i].name);
659						break;
660					}
661					(cmds[i].f)(&cfg, argv);
662					argc -= cmds[i].args;
663					argv += cmds[i].args;
664					break;
665				}
666			}
667			if (cmds[i].name == NULL) {
668				newmode(&cfg, MODE_NONE);
669				continue;
670			}
671			break;
672		case MODE_REGISTER:
673			if (set_register(&cfg, argv[0]) != 0) {
674				newmode(&cfg, MODE_NONE);
675				continue;
676			}
677			break;
678		case MODE_PHYREG:
679			if (set_phyregister(&cfg, argv[0]) != 0) {
680				newmode(&cfg, MODE_NONE);
681				continue;
682			}
683			break;
684		}
685		argc--;
686		argv++;
687	}
688	/* switch back to command mode to print configuration for last command */
689	newmode(&cfg, MODE_NONE);
690	close(cfg.fd);
691	return (0);
692}
693
694static struct cmds cmds[] = {
695	{ MODE_PORT, "pvid", 1, set_port_vid },
696	{ MODE_PORT, "media", 1, set_port_media },
697	{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
698	{ MODE_PORT, "addtag", 0, set_port_flag },
699	{ MODE_PORT, "-addtag", 0, set_port_flag },
700	{ MODE_PORT, "ingress", 0, set_port_flag },
701	{ MODE_PORT, "-ingress", 0, set_port_flag },
702	{ MODE_PORT, "striptag", 0, set_port_flag },
703	{ MODE_PORT, "-striptag", 0, set_port_flag },
704	{ MODE_PORT, "doubletag", 0, set_port_flag },
705	{ MODE_PORT, "-doubletag", 0, set_port_flag },
706	{ MODE_PORT, "firstlock", 0, set_port_flag },
707	{ MODE_PORT, "-firstlock", 0, set_port_flag },
708	{ MODE_PORT, "dropuntagged", 0, set_port_flag },
709	{ MODE_PORT, "-dropuntagged", 0, set_port_flag },
710	{ MODE_CONFIG, "vlan_mode", 1, set_vlan_mode },
711	{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
712	{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
713	{ 0, NULL, 0, NULL }
714};
715