/*
 * Copyright (c) 2001-2003
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 * 	All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Hartmut Brandt <harti@freebsd.org>
 */
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.1.0/sbin/atm/atmconfig/diag.c 229175 2012-01-01 17:27:31Z dim $");

#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <net/if.h>
#include <net/if_mib.h>
#include <net/if_types.h>
#include <net/if_atm.h>
#include <net/if_media.h>
#include <netnatm/natm.h>
#include <dev/utopia/utopia.h>
#include <dev/utopia/suni.h>
#include <dev/utopia/idtphy.h>

#include "atmconfig.h"
#include "private.h"
#include "diag.h"

static void diag_list(int, char *[]);
static void diag_config(int, char *[]);
static void diag_vcc(int, char *[]);
static void diag_phy_show(int, char *[]);
static void diag_phy_set(int, char *[]);
static void diag_phy_print(int, char *[]);
static void diag_phy_stats(int, char *[]);
static void diag_stats(int, char *[]);

const struct cmdtab diag_phy_tab[] = {
	{ "show",	NULL, 		diag_phy_show },
	{ "set",	NULL, 		diag_phy_set },
	{ "stats",	NULL,		diag_phy_stats },
	{ "print",	NULL,		diag_phy_print },
	{ NULL,		NULL,		NULL },
};

const struct cmdtab diag_tab[] = {
	{ "list",	NULL,		diag_list },
	{ "config",	NULL,		diag_config },
	{ "phy",	diag_phy_tab,	NULL },
	{ "stats",	NULL,		diag_stats },
	{ "vcc",	NULL,		diag_vcc },
	{ NULL, 	NULL, 		NULL }
};

static const struct utopia_print suni_lite[] = { SUNI_PRINT_LITE };
static const struct utopia_print suni_ultra[] = { SUNI_PRINT_ULTRA };
static const struct utopia_print suni_622[] = { SUNI_PRINT_622 };
static const struct utopia_print idt77105[] = { IDTPHY_PRINT_77105 };
static const struct utopia_print idt77155[] = { IDTPHY_PRINT_77155 };

static const struct {
	const struct utopia_print *tab;
	u_int len;
	u_int type;
} phy_print[] = {
	{ suni_lite, sizeof(suni_lite) / sizeof(suni_lite[0]),
	  UTP_TYPE_SUNI_LITE },
	{ suni_ultra, sizeof(suni_ultra) / sizeof(suni_ultra[0]),
	  UTP_TYPE_SUNI_ULTRA },
	{ suni_622, sizeof(suni_622) / sizeof(suni_622[0]),
	  UTP_TYPE_SUNI_622 },
	{ idt77105, sizeof(idt77105) / sizeof(idt77105[0]),
	  UTP_TYPE_IDT77105 },
	{ idt77155, sizeof(idt77155) / sizeof(idt77155[0]),
	  UTP_TYPE_IDT77155 },
};

static const u_int utopia_addreg[] = { UTP_REG_ADD };

/*
 * Driver statistics printing
 */
static const char *const print_stats_pca200e[] = {
	"cmd_queue_full:",
	"get_stat_errors:",
	"clr_stat_errors:",
	"get_prom_errors:",
	"suni_reg_errors:",
	"tx_queue_full:",
	"tx_queue_almost_full:",
	"tx_pdu2big:",
	"tx_too_many_segs:",
	"tx_retry:",
	"fix_empty:",
	"fix_addr_copy:",
	"fix_addr_noext:",
	"fix_addr_ext:",
	"fix_len_noext:",
	"fix_len_copy:",
	"fix_len:",
	"rx_badvc:",
	"rx_closed:",
	NULL
};
static const char *const print_stats_he[] = {
	"tdprq_full:",
	"hbuf_error:",
	"crc_error:",
	"len_error:",
	"flow_closed:",
	"flow_drop:",
	"tpd_no_mem:",
	"rx_seg:",
	"empty_hbuf:",
	"short_aal5:",
	"badlen_aal5:",
	"bug_bad_isw:",
	"bug_no_irq_upd:",
	"itype_tbrq:",
	"itype_tpd:",
	"itype_rbps:",
	"itype_rbpl:",
	"itype_rbrq:",
	"itype_rbrqt:",
	"itype_unknown:",
	"itype_phys:",
	"itype_err:",
	"defrag:",
	"mcc:",
	"oec:",
	"dcc:",
	"cec:",
	"no_rcv_mbuf:",
	NULL
};
static const char *const print_stats_eni[] = {
	"ttrash:",
	"mfixaddr:",
	"mfixlen:",
	"mfixfail:",
	"txmbovr:",
	"dmaovr:",
	"txoutspace:",
	"txdtqout:",
	"launch:",
	"hwpull:",
	"swadd:",
	"rxqnotus:",
	"rxqus:",
	"rxdrqout:",
	"rxmbufout:",
	"txnomap:",
	"vtrash:",
	"otrash:",
	NULL
};

static const char *const print_stats_idt77211[] = {
	"need_copy:",
	"copy_failed:",
	"out_of_tbds:",
	"no_txmaps:",
	"tx_load_err:",
	"tx_qfull:",
	NULL
};
static const char *const print_stats_idt77252[] = {
	"raw_cells:",
	"raw_no_vcc:",
	"raw_no_buf:",
	"tx_qfull:",
	"tx_out_of_tbds:",
	"tx_out_of_maps:",
	"tx_load_err:",
	NULL
};
static const char *const print_stats_virtual[] = {
	"dummy:",
	NULL
};
static const char *const *const print_stats[] = {
	[ATM_DEVICE_UNKNOWN] =		NULL,
	[ATM_DEVICE_PCA200E] =		print_stats_pca200e,
	[ATM_DEVICE_HE155] =		print_stats_he,
	[ATM_DEVICE_HE622] =		print_stats_he,
	[ATM_DEVICE_ENI155P] =		print_stats_eni,
	[ATM_DEVICE_ADP155P] =		print_stats_eni,
	[ATM_DEVICE_FORELE25] =		print_stats_idt77211,
	[ATM_DEVICE_FORELE155] =	print_stats_idt77211,
	[ATM_DEVICE_NICSTAR25] =	print_stats_idt77211,
	[ATM_DEVICE_NICSTAR155] =	print_stats_idt77211,
	[ATM_DEVICE_IDTABR25] =		print_stats_idt77252,
	[ATM_DEVICE_IDTABR155] =	print_stats_idt77252,
	[ATM_DEVICE_PROATM25] =		print_stats_idt77252,
	[ATM_DEVICE_PROATM155] =	print_stats_idt77252,
	[ATM_DEVICE_VIRTUAL] =		print_stats_virtual,
};

struct diagif_list diagif_list = TAILQ_HEAD_INITIALIZER(diagif_list);

/*
 * Fetch a phy sysctl
 */
static int
phy_fetch(const char *ifname, const char *var, void *val, size_t len,
    int err_fatal)
{
	char *str;

	if (asprintf(&str, "hw.atm.%s.phy_%s", ifname, var) == -1)
		err(1, NULL);
	if (sysctlbyname(str, val, &len, NULL, 0) == -1) {
		if (err_fatal || errno != ENOENT)
			err(1, "%s", str);
		free(str);
		return (-1);
	}
	free(str);
	return (0);
}

/*
 * Fetch the list of all ATM network interfaces and their MIBs.
 */
void
diagif_fetch(void)
{
	size_t len;
	int count;
	int name[6];
	struct ifmibdata mib;
	struct ifatm_mib atm;
	int idx;
	struct diagif *d;

	while ((d = TAILQ_FIRST(&diagif_list)) != NULL) {
		if (d->vtab != NULL)
			free(d->vtab);
		TAILQ_REMOVE(&diagif_list, d, link);
		free(d);
	}

	len = sizeof(count);
	if (sysctlbyname("net.link.generic.system.ifcount", &count, &len,
	    NULL, 0) == -1)
		err(1, "ifcount");

	name[0] = CTL_NET;
	name[1] = PF_LINK;
	name[2] = NETLINK_GENERIC;
	name[3] = IFMIB_IFDATA;

	for (idx = 1; idx <= count; idx++) {
		name[4] = idx;
		name[5] = IFDATA_GENERAL;
		len = sizeof(mib);
		if (sysctl(name, 6, &mib, &len, NULL, 0) == -1)
			err(1, "interface %d: general mib", idx);
		if (mib.ifmd_data.ifi_type == IFT_ATM) {
			name[5] = IFDATA_LINKSPECIFIC;
			len = sizeof(atm);
			if (sysctl(name, 6, &atm, &len, NULL, 0) == -1)
				err(1, "interface %d: ATM mib", idx);

			d = malloc(sizeof(*d));
			if (d == NULL)
				err(1, NULL);
			bzero(d, sizeof(*d));
			d->mib = atm;
			d->index = idx;
			strcpy(d->ifname, mib.ifmd_name);
			TAILQ_INSERT_TAIL(&diagif_list, d, link);

			if (phy_fetch(d->ifname, "type", &d->phy_type,
			    sizeof(d->phy_type), 0) == 0) {
				d->phy_present = 1;
				phy_fetch(d->ifname, "loopback",
				    &d->phy_loopback,
				    sizeof(d->phy_loopback), 1);
				phy_fetch(d->ifname, "name", &d->phy_name,
				    sizeof(d->phy_name), 1);
				phy_fetch(d->ifname, "state", &d->phy_state,
				    sizeof(d->phy_state), 1);
				phy_fetch(d->ifname, "carrier", &d->phy_carrier,
				    sizeof(d->phy_carrier), 1);
			}
		}
	}
}

/*
 * "<radix><bit>STRING\011<mask><pattern>STRING\012<mask><radix>STRING"
 */
static char *
printb8(uint32_t val, const char *descr)
{
	static char buffer[1000];
	char *ptr;
	int tmp = 0;
	u_char mask, pattern;

	if (*descr++ == '\010')
		sprintf(buffer, "%#o", val);
	else
		sprintf(buffer, "%#x", val);
	ptr = buffer + strlen(buffer);

	*ptr++ = '<';
	while (*descr) {
		if (*descr == '\11') {
			descr++;
			mask = *descr++;
			pattern = *descr++;
			if ((val & mask) == pattern) {
				if (tmp++)
					*ptr++ = ',';
				while (*descr >= ' ')
					*ptr++ = *descr++;
			} else {
				while (*descr >= ' ')
					descr++;
			}
		} else if (*descr == '\12') {
			descr++;
			mask = *descr++;
			pattern = *descr++;
			if (tmp++)
				*ptr++ = ',';
			while (*descr >= ' ')
				*ptr++ = *descr++;
			*ptr++ = '=';
			if (pattern == 8)
				sprintf(ptr, "%#o",
				    (val & mask) >> (ffs(mask)-1));
			else if (pattern == 10)
				sprintf(ptr, "%u",
				    (val & mask) >> (ffs(mask)-1));
			else
				sprintf(ptr, "%#x",
				    (val & mask) >> (ffs(mask)-1));
			ptr += strlen(ptr);
		} else {
			if (val & (1 << (*descr++ - 1))) {
				if (tmp++)
					*ptr++ = ',';
				while (*descr >= ' ')
					*ptr++ = *descr++;
			} else {
				while (*descr >= ' ')
					descr++;
			}
		}
	}
	*ptr++ = '>';
	*ptr++ = '\0';

	return (buffer);
}

/*
 * "<radix><bit>STRING<bit>STRING"
 */
static char *
printb(uint32_t val, const char *descr)
{
	static char buffer[1000];
	char *ptr;
	int tmp = 0;

	if (*descr++ == '\010')
		sprintf(buffer, "%#o", val);
	else
		sprintf(buffer, "%#x", val);
	ptr = buffer + strlen(buffer);

	*ptr++ = '<';
	while (*descr) {
		if (val & (1 << (*descr++ - 1))) {
			if (tmp++)
				*ptr++ = ',';
			while (*descr > ' ')
				*ptr++ = *descr++;
		} else {
			while (*descr > ' ')
				descr++;
		}
	}
	*ptr++ = '>';
	*ptr++ = '\0';

	return (buffer);
}


static void
diag_loop(int argc, char *argv[], const char *text,
    void (*func)(const struct diagif *))
{
	int i;
	struct diagif *aif;

	heading_init();
	if (argc > 0) {
		for (i = 0; i < argc; i++) {
			TAILQ_FOREACH(aif, &diagif_list, link) {
				if (strcmp(argv[i], aif->ifname) == 0) {
					heading("%s", text);
					(*func)(aif);
					break;
				}
			}
			if (aif == NULL)
				warnx("%s: no such ATM interface", argv[i]);
		}
	} else {
		TAILQ_FOREACH(aif, &diagif_list, link) {
			heading("%s", text);
			(*func)(aif);
		}
	}
}

/*
 * Print the config line for the given interface
 */
static void
config_line1(const struct diagif *aif)
{
	printf("%-6u%-9s%-8u%-5u%-6u%-5u%-6u%02x:%02x:%02x:%02x:%02x:%02x\n",
	    aif->index, aif->ifname, aif->mib.pcr, (1 << aif->mib.vpi_bits) - 1,
	    (1 << aif->mib.vci_bits) - 1, aif->mib.max_vpcs, aif->mib.max_vccs,
	    aif->mib.esi[0], aif->mib.esi[1], aif->mib.esi[2],
	    aif->mib.esi[3], aif->mib.esi[4], aif->mib.esi[5]);
}

static void
config_line2(const struct diagif *aif)
{
	u_int d, i;

	static const struct {
		const char *dev;
		const char *vendor;
	} devs[] = {
		ATM_DEVICE_NAMES
	};
	static const struct {
		u_int	media;
		const char *const name;
	} medias[] = IFM_SUBTYPE_ATM_DESCRIPTIONS;

	for (i = 0; medias[i].name; i++)
		if (aif->mib.media == medias[i].media)
			break;

	if ((d = aif->mib.device) >= sizeof(devs) / sizeof(devs[0]))
		d = 0;

	printf("%-6u%-9s%-12.11s%-13.12s%-8u%-6x%-6x %s\n", aif->index,
	    aif->ifname, devs[d].vendor, devs[d].dev, aif->mib.serial,
	    aif->mib.hw_version, aif->mib.sw_version,
	    medias[i].name ? medias[i].name : "unknown");
}

static void
diag_config(int argc, char *argv[])
{
	int opt;

	static int hardware;
	static int atm;

	static const struct option opts[] = {
	    { "hardware", OPT_SIMPLE, &hardware },
	    { "atm", OPT_SIMPLE, &atm },
	    { NULL, 0, NULL }
	};

	static const char config_text1[] =
	    "Interface              Max        Max\n"
	    "Index Name     PCR     VPI  VCI   VPCs VCCs  ESI\n";
	static const char config_text2[] =
	    "Interface                                       Version\n"
	    "Index Name     Vendor      Card         "
	    "Serial  HW    SW     Media\n";

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	diagif_fetch();
	if (TAILQ_EMPTY(&diagif_list))
		errx(1, "no ATM interfaces found");

	if (!atm && !hardware)
		atm = 1;

	if (atm)
		diag_loop(argc, argv, config_text1, config_line1);
	if (hardware)
		diag_loop(argc, argv, config_text2, config_line2);

}

static void
diag_list(int argc, char *argv[])
{
	int opt;
	struct diagif *aif;

	static const struct option opts[] = {
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc > 0)
		errx(1, "no arguments required for 'diag list'");

	diagif_fetch();
	if (TAILQ_EMPTY(&diagif_list))
		errx(1, "no ATM interfaces found");

	TAILQ_FOREACH(aif, &diagif_list, link)
		printf("%s ", aif->ifname);
	printf("\n");
}

/*
 * Print the config line for the given interface
 */
static void
phy_show_line(const struct diagif *aif)
{
	printf("%-6u%-9s", aif->index, aif->ifname);
	if (aif->phy_present)
		printf("%-5u%-25s0x%-9x", aif->phy_type,
		    aif->phy_name, aif->phy_loopback);
	printf("\n");
}

static void
diag_phy_show(int argc, char *argv[])
{
	int opt;

	static const struct option opts[] = {
	    { NULL, 0, NULL }
	};

	static const char phy_show_text[] = 
	    "Interface      Phy\n"
	    "Index Name     Type Name                     Loopback State\n";

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	diagif_fetch();
	if (TAILQ_EMPTY(&diagif_list))
		errx(1, "no ATM interfaces found");

	diag_loop(argc, argv, phy_show_text, phy_show_line);
}

/*
 * Make sure the interface exists and has a phy
 */
static struct diagif *
diagif_get_phy(const char *arg)
{
	struct diagif *aif;

	diagif_fetch();
	TAILQ_FOREACH(aif, &diagif_list, link)
		if (strcmp(aif->ifname, arg) == 0)
			break;
	if (aif == NULL)
		errx(1, "no such interface: %s", arg);
	if (!aif->phy_present)
		errx(1, "interface %s has no phy", arg);

	return (aif);
}

static void
diag_phy_set(int argc, char *argv[])
{
	int opt;
	uint8_t reg[3];
	u_long res;
	char *end;
	char *str;

	static const struct option opts[] = {
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc != 4)
		errx(1, "missing arguments for 'diag phy set'");

	errno = 0;
	res = strtoul(argv[1], &end, 0);
	if (errno != 0)
		err(1, "register number");
	if (*end != '\0')
		errx(1, "malformed register number '%s'", argv[1]);
	if (res > 0xff)
		errx(1, "register number too large");
	reg[0] = res;

	errno = 0;
	res = strtoul(argv[2], &end, 0);
	if (errno != 0)
		err(1, "mask");
	if (*end != '\0')
		errx(1, "malformed mask '%s'", argv[1]);
	if (res > 0xff)
		errx(1, "mask too large");
	reg[1] = res;

	errno = 0;
	res = strtoul(argv[3], &end, 0);
	if (errno != 0)
		err(1, "value");
	if (*end != '\0')
		errx(1, "malformed value '%s'", argv[1]);
	if (res > 0xff)
		errx(1, "value too large");
	reg[2] = res;

	(void)diagif_get_phy(argv[0]);

	if (asprintf(&str, "hw.atm.%s.phy_regs", argv[0]) == -1)
		err(1, NULL);

	if (sysctlbyname(str, NULL, NULL, reg, 3 * sizeof(uint8_t)))
		err(1, "%s", str);

	free(str);
}

static void
diag_phy_print(int argc, char *argv[])
{
	int opt;
	char *str;
	size_t len, len1;
	uint8_t *regs;
	u_int type, i;
	const struct utopia_print *p;

	static int numeric;

	static const struct option opts[] = {
	    { "numeric", OPT_SIMPLE, &numeric },
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc != 1)
		errx(1, "need device name for 'diag phy print'");

	(void)diagif_get_phy(argv[0]);

	if (asprintf(&str, "hw.atm.%s.phy_regs", argv[0]) == -1)
		err(1, NULL);
	len = 0;
	if (sysctlbyname(str, NULL, &len, NULL, 0))
		err(1, "'%s' not found", str);

	regs = malloc(len);
	if (regs == NULL)
		err(1, NULL);

	if (sysctlbyname(str, regs, &len, NULL, 0))
		err(1, "'%s' not found", str);
	free(str);

	if (numeric) {
		for (i = 0; i < len; i++) {
			if (i % 16 == 0)
				printf("%02x: ", i);
			if (i % 16 == 8)
				printf(" ");
			printf(" %02x", regs[i]);
			if (i % 16 == 15)
				printf("\n");
		}
		if (i % 16 != 0)
			printf("\n");
	} else {
		if (asprintf(&str, "hw.atm.%s.phy_type", argv[0]) == -1)
			err(1, NULL);
		len1 = sizeof(type);
		if (sysctlbyname(str, &type, &len1, NULL, 0))
			err(1, "'%s' not found", str);
		free(str);

		for (i = 0; i < sizeof(phy_print) / sizeof(phy_print[0]); i++)
			if (type == phy_print[i].type)
				break;
		if (i == sizeof(phy_print) / sizeof(phy_print[0]))
			errx(1, "unknown PHY chip type %u\n", type);

		for (p = phy_print[i].tab;
		    p < phy_print[i].tab + phy_print[i].len;
		    p++) {
			if (p->reg + utopia_addreg[p->type] > len)
				/* don't have this register */
				continue;

			printf("%s:%*s", p->name, 40 - (int)strlen(p->name),"");

			switch (p->type) {

			  case UTP_REGT_BITS:
				printf("%s\n", printb8(regs[p->reg], p->fmt));
				break;
			
			  case UTP_REGT_INT8:
				printf("%#x\n", regs[p->reg]);
				break;

			  case UTP_REGT_INT10BITS:
				printf("%#x %s\n", regs[p->reg] |
				    ((regs[p->reg + 1] & 0x3) << 8),
				    printb8(regs[p->reg + 1], p->fmt));
				break;

			  case UTP_REGT_INT12:
				printf("%#x\n", regs[p->reg] |
				    ((regs[p->reg + 1] & 0xf) << 8));
				break;

			  case UTP_REGT_INT16:
				printf("%#x\n", regs[p->reg] |
				    (regs[p->reg + 1] << 8));
				break;

			  case UTP_REGT_INT19:
				printf("%#x\n", regs[p->reg] |
				    (regs[p->reg + 1] << 8) |
				    ((regs[p->reg + 2] & 0x7) << 16));
				break;

			  case UTP_REGT_INT20:
				printf("%#x\n", regs[p->reg] |
				    (regs[p->reg + 1] << 8) |
				    ((regs[p->reg + 2] & 0xf) << 16));
				break;

			  case UTP_REGT_INT21:
				printf("%#x\n", regs[p->reg] |
				    (regs[p->reg + 1] << 8) |
				    ((regs[p->reg + 2] & 0x1f) << 16));
				break;

			  default:
				abort();
			}
		}
	}
	free(regs);
}

static void
diag_phy_stats(int argc, char *argv[])
{
	int opt;
	size_t len;
	char *str;
	struct utopia_stats1 stats1;
	u_int foo;

	static int clear;

	static const struct option opts[] = {
	    { "clear", OPT_SIMPLE, &clear },
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc != 1)
		errx(1, "need device name for 'diag phy stats'");

	(void)diagif_get_phy(argv[0]);

	if (asprintf(&str, "hw.atm.%s.phy_stats", argv[0]) == -1)
		err(1, NULL);

	len = sizeof(stats1);
	if (sysctlbyname(str, &stats1, &len,
	    clear ? &foo : NULL, clear ? sizeof(foo) : 0))
		err(1, "'%s' not found", str);
	if (len < sizeof(stats1.version))
		errx(1, "phy statistics too short %zu", len);

	switch (stats1.version) {

	  case 1:
		if (len != sizeof(stats1))
			errx(1, "bad phy stats length %zu (expecting %zu)",
			    len, sizeof(stats1));
		break;

	  default:
		errx(1, "unknown phy stats version %u", stats1.version);
	}

	free(str);

	printf("rx_sbip:	%llu\n", (unsigned long long)stats1.rx_sbip);
	printf("rx_lbip:	%llu\n", (unsigned long long)stats1.rx_lbip);
	printf("rx_lfebe:	%llu\n", (unsigned long long)stats1.rx_lfebe);
	printf("rx_pbip:	%llu\n", (unsigned long long)stats1.rx_pbip);
	printf("rx_pfebe:	%llu\n", (unsigned long long)stats1.rx_pfebe);
	printf("rx_cells:	%llu\n", (unsigned long long)stats1.rx_cells);
	printf("rx_corr:	%llu\n", (unsigned long long)stats1.rx_corr);
	printf("rx_uncorr:	%llu\n", (unsigned long long)stats1.rx_uncorr);
	printf("rx_symerr:	%llu\n", (unsigned long long)stats1.rx_symerr);
	printf("tx_cells:	%llu\n", (unsigned long long)stats1.tx_cells);
}

/*
 * Fetch the table of open vccs
 */
void
diagif_fetch_vcc(struct diagif *aif, int fd)
{
	struct ifreq ifr;

	if (aif->vtab != NULL)
		return;

	strncpy(ifr.ifr_name, aif->ifname, IFNAMSIZ);
	ifr.ifr_name[IFNAMSIZ - 1] = '\0';

	aif->vtab = malloc(sizeof(*aif->vtab) + sizeof(aif->vtab->vccs[0]) *
	    aif->mib.max_vccs);
	if (aif->vtab == NULL)
		err(1, NULL);
	ifr.ifr_data = (caddr_t)aif->vtab;

	if (ioctl(fd, SIOCATMGVCCS, &ifr) == -1)
		err(1, "SIOCATMGVCCS");
}

/*
 * Print the VCC table for this interface.
 */
static void
print_channel(const struct diagif *aif)
{
	const struct atmio_vcc *v;

	static const char *const aal_tab[] = {
		[ATMIO_AAL_0] = "0",
		[ATMIO_AAL_34] = "3/4",
		[ATMIO_AAL_5] = "5",
		[ATMIO_AAL_RAW] = "raw",
	};
	static const char *const traffic_tab[] = {
		[ATMIO_TRAFFIC_UBR] = "ubr",
		[ATMIO_TRAFFIC_CBR] = "cbr",
		[ATMIO_TRAFFIC_ABR] = "abr",
		[ATMIO_TRAFFIC_VBR] = "vbr",
	};

	for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
		printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
		    v->vpi, v->vci);

		if (v->aal >= sizeof(aal_tab)/sizeof(aal_tab[0]) ||
		    aal_tab[v->aal] == NULL)
			printf("bad ");
		else
			printf("%-4s", aal_tab[v->aal]);

		if (v->traffic >= sizeof(traffic_tab)/sizeof(traffic_tab[0]) ||
		    traffic_tab[v->traffic] == NULL)
			printf("bad     ");
		else
			printf("%-8s", traffic_tab[v->traffic]);

		printf("%-6u%-6u%s\n", v->rmtu, v->tmtu,
		    printb(v->flags, ATMIO_FLAGS));
	}
}

/*
 * Print the VCC table for this interface, traffic parameters.
 */
static void
print_traffic(const struct diagif *aif)
{
	const struct atmio_vcc *v;

	for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
		printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
		    v->vpi, v->vci);

		switch (v->traffic) {

		  case ATMIO_TRAFFIC_CBR:
			printf("%u", v->tparam.pcr);
			break;

		  case ATMIO_TRAFFIC_UBR:
			printf("%-8u                %u", v->tparam.pcr,
			    v->tparam.mcr);
			break;

		  case ATMIO_TRAFFIC_VBR:
			printf("%-8u%-8u%-8u", v->tparam.pcr, v->tparam.scr,
			    v->tparam.mbs);
			break;

		  case ATMIO_TRAFFIC_ABR:
			printf("%-8u                %-8u",
			    v->tparam.pcr, v->tparam.mcr);
			break;
		}
		printf("\n");
	}
}

/*
 * Print the VCC table for this interface, ABR traffic parameters.
 */
static void
print_abr(const struct diagif *aif)
{
	const struct atmio_vcc *v;

	for (v = aif->vtab->vccs; v < &aif->vtab->vccs[aif->vtab->count]; v++) {
		printf("%-6u%-9s%-4u%-6u", aif->index, aif->ifname,
		    v->vpi, v->vci);

		if (v->traffic == ATMIO_TRAFFIC_ABR) {
			printf("%-8u%-8u%-4u%-4u%-5u%-5u%-5u%u",
			    v->tparam.icr, v->tparam.tbe, v->tparam.nrm,
			    v->tparam.trm, v->tparam.adtf, v->tparam.rif,
			    v->tparam.rdf, v->tparam.cdf);
		}
		printf("\n");
	}
}

static void
diag_vcc_loop(void (*func)(const struct diagif *), const char *text,
    int argc, char *argv[], int fd)
{
	struct diagif *aif;

	heading_init();
	if (argc == 0) {
		TAILQ_FOREACH(aif, &diagif_list, link) {
			diagif_fetch_vcc(aif, fd);
			if (aif->vtab->count != 0) {
				heading("%s", text);
				(*func)(aif);
			}
		}

	} else {
		for (optind = 0; optind < argc; optind++) {
			TAILQ_FOREACH(aif, &diagif_list, link)
				if (strcmp(aif->ifname, argv[optind]) == 0) {
					diagif_fetch_vcc(aif, fd);
					if (aif->vtab->count != 0) {
						heading("%s", text);
						(*func)(aif);
					}
					break;
				}
			if (aif == NULL)
				warnx("no such interface '%s'", argv[optind]);
		}
	}
}

static void
diag_vcc(int argc, char *argv[])
{
	int opt, fd;

	static int channel, traffic, abr;
	static const struct option opts[] = {
	    { "abr", OPT_SIMPLE, &abr },
	    { "channel", OPT_SIMPLE, &channel },
	    { "traffic", OPT_SIMPLE, &traffic },
	    { NULL, 0, NULL }
	};
	static const char head_channel[] =
	    "Interface\n"
	    "Index Name     VPI VCI   AAL Traffic RxMTU TxMTU Flags\n";
	static const char head_traffic[] =
	    "Interface                Traffic parameters\n"
	    "Index Name     VPI VCI   PCR     SCR     MBS     MCR\n";
	static const char head_abr[] =
	    "Interface                ABR traffic parameters\n"
	    "Index Name     VPI VCI   ICR     TBE     NRM TRM ADTF RIF  RDF  "
	    "CDF\n";

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	fd = socket(PF_NATM, SOCK_STREAM, PROTO_NATMAAL5);
	if (fd < 0)
		err(1, "socket");

	diagif_fetch();
	if (TAILQ_EMPTY(&diagif_list))
		errx(1, "no ATM interfaces found");

	if (!channel && !traffic && !abr)
		channel = 1;

	if (channel)
		diag_vcc_loop(print_channel, head_channel, argc, argv, fd);
	if (traffic)
		diag_vcc_loop(print_traffic, head_traffic, argc, argv, fd);
	if (abr)
		diag_vcc_loop(print_abr, head_abr, argc, argv, fd);
}

/*
 * Print driver-internal statistics
 */
static void
diag_stats(int argc, char *argv[])
{
	int opt;
	char *str;
	size_t len;
	uint32_t *stats;
	struct diagif *aif;
	u_int i;

	static const struct option opts[] = {
	    { NULL, 0, NULL }
	};

	while ((opt = parse_options(&argc, &argv, opts)) != -1)
		switch (opt) {
		}

	if (argc != 1)
		errx(1, "need one arg for 'diag stats'");

	diagif_fetch();
	TAILQ_FOREACH(aif, &diagif_list, link)
		if (strcmp(aif->ifname, argv[0]) == 0)
			break;

	if (aif == NULL)
		errx(1, "interface '%s' not found", argv[0]);

	if (asprintf(&str, "hw.atm.%s.istats", argv[0]) == -1)
		err(1, NULL);
	len = 0;
	if (sysctlbyname(str, NULL, &len, NULL, 0))
		err(1, "'%s' not found", str);

	stats = malloc(len);
	if (stats == NULL)
		err(1, NULL);

	if (sysctlbyname(str, stats, &len, NULL, 0))
		err(1, "'%s' not found", str);
	free(str);

	if (aif->mib.device >= sizeof(print_stats) / sizeof(print_stats[0]) ||
	    print_stats[aif->mib.device] == NULL)
		errx(1, "unknown stats format (%u)", aif->mib.device);

	for (i = 0; print_stats[aif->mib.device][i] != NULL; i++) {
		if (i * sizeof(uint32_t) >= len)
			errx(1, "debug info too short (version mismatch?)");
		printf("%-22s%u\n", print_stats[aif->mib.device][i], stats[i]);
	}
	free(stats);

	if (i != len / sizeof(uint32_t))
		errx(1, "debug info too long (version mismatch?)");
}
