/*-
 * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
 * 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,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 * 3. Neither the names of the above-listed copyright holders nor the names
 *    of any contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
 *
 * $Id: athstats.c 3978 2009-04-07 01:26:53Z proski $
 */

/*
 * Simple Atheros-specific tool to inspect and monitor network traffic
 * statistics.
 *	athstats [-i interface] [interval]
 * (default interface is wifi0).  If interval is specified a rolling output
 * is displayed every interval seconds.
 */
#include <sys/types.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>
#include <unistd.h>
#include <err.h>

#include "ah_desc.h"

#include "wireless_copy.h"
#include "if_athioctl.h"

#undef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

static const struct {
	u_int		phyerr;
	const char	*desc;
} phyerrdescriptions[] = {
	{ HAL_PHYERR_UNDERRUN,		"transmit underrun" },
	{ HAL_PHYERR_TIMING,		"timing error" },
	{ HAL_PHYERR_PARITY,		"illegal parity" },
	{ HAL_PHYERR_RATE,		"illegal rate" },
	{ HAL_PHYERR_LENGTH,		"illegal length" },
	{ HAL_PHYERR_RADAR,		"radar detect" },
	{ HAL_PHYERR_SERVICE,		"illegal service" },
	{ HAL_PHYERR_TOR,		"transmit override receive" },
	{ HAL_PHYERR_OFDM_TIMING,	"OFDM timing" },
	{ HAL_PHYERR_OFDM_SIGNAL_PARITY,"OFDM illegal parity" },
	{ HAL_PHYERR_OFDM_RATE_ILLEGAL,	"OFDM illegal rate" },
	{ HAL_PHYERR_OFDM_POWER_DROP,	"OFDM power drop" },
	{ HAL_PHYERR_OFDM_SERVICE,	"OFDM illegal service" },
	{ HAL_PHYERR_OFDM_RESTART,	"OFDM restart" },
	{ HAL_PHYERR_CCK_TIMING,	"CCK timing" },
	{ HAL_PHYERR_CCK_HEADER_CRC,	"CCK header crc" },
	{ HAL_PHYERR_CCK_RATE_ILLEGAL,	"CCK illegal rate" },
	{ HAL_PHYERR_CCK_SERVICE,	"CCK illegal service" },
	{ HAL_PHYERR_CCK_RESTART,	"CCK restart" },
};

static void
printstats(FILE *fd, const struct ath_stats *stats)
{
#define	STAT(x,fmt) \
	if (stats->ast_##x) fprintf(fd, "%u " fmt "\n", stats->ast_##x)
#define	STATI(x,fmt) \
	if (stats->ast_##x) fprintf(fd, "%d " fmt "\n", stats->ast_##x)
	int i, j;

	STAT(watchdog, "watchdog timeouts");
	STAT(hardware, "hardware error interrupts");
	STAT(bmiss, "beacon miss interrupts");
	STAT(rxorn, "recv overrun interrupts");
	STAT(rxeol, "recv eol interrupts");
	STAT(txurn, "txmit underrun interrupts");
	STAT(tx_mgmt, "tx management frames");
	STAT(tx_discard, "tx frames discarded due to queue depth");
	STAT(tx_invalid, "tx frames discarded due to device gone");
	STAT(tx_qstop, "tx queue stopped because full");
	STAT(tx_encap, "tx encapsulation failed");
	STAT(tx_nonode, "tx failed due to no node");
	STAT(tx_nobuf, "tx failed due to no tx buffer (data)");
	STAT(tx_nobufmgt, "tx failed due to no tx buffer (mgt)");
	STAT(tx_xretries, "tx failed due to too many retries");
	STAT(tx_fifoerr, "tx failed due to FIFO underrun");
	STAT(tx_filtered, "tx failed due to xmit filtered");
	STAT(tx_shortretry, "short on-chip tx retries");
	STAT(tx_longretry, "long on-chip tx retries");
	STAT(tx_badrate, "tx failed due to bogus xmit rate");
	STAT(tx_noack, "tx frames with no ack marked");
	STAT(tx_rts, "tx frames with rts enabled");
	STAT(tx_cts, "tx frames with cts enabled");
	STAT(tx_shortpre, "tx frames with short preamble");
	STAT(tx_altrate, "tx frames with an alternate rate");
	STAT(tx_protect, "tx frames with 11g protection");
	STAT(rx_orn, "rx failed due to desc overrun");
	STAT(rx_crcerr, "rx failed due to bad CRC");
	STAT(rx_fifoerr, "rx failed due to FIFO overrun");
	STAT(rx_badcrypt, "rx failed due to decryption");
	STAT(rx_badmic, "rx failed due to MIC failure");
	STAT(rx_tooshort, "rx failed due to frame too short");
	STAT(rx_nobuf, "rx setup failed due to no skbuff");
	STAT(rx_mgt, "rx management frames");
	STAT(rx_ctl, "rx control frames");
	STAT(rx_phyerr, "PHY errors");
	if (stats->ast_rx_phyerr != 0) {
		for (i = 0; i < 32; i++) {
			if (stats->ast_rx_phy[i] == 0)
				continue;
			for (j = 0; j < ARRAY_SIZE(phyerrdescriptions); j++)
				if (phyerrdescriptions[j].phyerr == i)
					break;
			if (j == ARRAY_SIZE(phyerrdescriptions))
				fprintf(fd,
					"    %u (unknown phy error code %u)\n",
					stats->ast_rx_phy[i], i);
			else
				fprintf(fd, "    %u %s\n",
					stats->ast_rx_phy[i],
					phyerrdescriptions[j].desc);
		}
	}
	STAT(be_nobuf, "no skbuff available for beacon");
	STAT(be_xmit,	"beacons transmitted");
	STAT(per_cal, "periodic calibrations");
	STAT(per_calfail, "periodic calibration failures");
	STAT(per_rfgain, "rfgain value change");
	STAT(rate_calls, "rate control checks");
	STAT(rate_raise, "rate control raised xmit rate");
	STAT(rate_drop, "rate control dropped xmit rate");
	if (stats->ast_tx_rssi)
		fprintf(fd, "rssi of last ack: %u\n", stats->ast_tx_rssi);
	if (stats->ast_rx_rssi)
		fprintf(fd, "rssi of last rcv: %u\n", stats->ast_rx_rssi);
	STAT(ant_defswitch, "switched default/rx antenna");
	STAT(ant_txswitch, "tx used alternate antenna");
	fprintf(fd, "Antenna profile:\n");
	for (i = 0; i < 8; i++)
		if (stats->ast_ant_rx[i] || stats->ast_ant_tx[i])
			fprintf(fd, "[%u] tx %8u rx %8u\n", i,
				stats->ast_ant_tx[i], stats->ast_ant_rx[i]);
#undef STAT
#undef STATI
}

static u_int
getifrate(int s, const char *ifname)
{
	struct iwreq wrq;

	(void) memset(&wrq, 0, sizeof(wrq));
	strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(s, SIOCGIWRATE, &wrq) < 0)
		return 0;
	else
		return wrq.u.bitrate.value / 1000000;
}

static int
getifstats(const char *ifname, u_long *iframes, u_long *oframes)
{
	FILE * fd = fopen("/proc/net/dev", "r");
	if (fd != NULL) {
		char line[256];
		while (fgets(line, sizeof(line), fd)) {
			char *cp, *tp;

			for (cp = line; isspace(*cp); cp++)
				;
			if (cp[0] != ifname[0])
				continue;
			for (tp = cp; *tp != ':' && *tp; tp++)
				;
			if (*tp == ':') {
				*tp++ = '\0';
				if (strcmp(cp, ifname) != 0)
					continue;
				sscanf(tp, "%*u %lu %*u %*u %*u %*u %*u %*u %*u %lu",
					iframes, oframes);
				fclose(fd);
				return 1;
			}
		}
		fclose(fd);
	}
	return 0;
}

static int signalled;

static void
catchalarm(int signo)
{
	signalled = 1;
}

int
main(int argc, char *argv[])
{
#ifdef __linux__
	const char *ifname = "wifi0";
#else
	const char *ifname = "ath0";
#endif
	int s;
	struct ifreq ifr;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket");
	if (argc > 1 && strcmp(argv[1], "-i") == 0) {
		if (argc < 2) {
			fprintf(stderr, "%s: missing interface name for -i\n",
				argv[0]);
			exit(-1);
		}
		ifname = argv[2];
		argc -= 2, argv += 2;
	}
	strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
	if (argc > 1) {
		u_long interval = strtoul(argv[1], NULL, 0);
		int line;
		sigset_t omask, nmask;
		u_int rate;
		struct ath_stats cur, total;
		u_long icur, ocur;
		u_long itot, otot;

		if (interval < 1)
			interval = 1;
		signal(SIGALRM, catchalarm);
		signalled = 0;
		alarm(interval);
	banner:
		printf("%8s %8s %7s %7s %7s %6s %6s %6s %7s %4s %4s",
			"input",
			"output",
			"altrate",
			"short",
			"long",
			"xretry",
			"crcerr",
			"crypt",
			"phyerr",
			"rssi",
			"rate");
		putchar('\n');
		fflush(stdout);
		line = 0;
	loop:
		rate = getifrate(s, ifr.ifr_name);
		if (line != 0) {
			ifr.ifr_data = (caddr_t) &cur;
			if (ioctl(s, SIOCGATHSTATS, &ifr) < 0)
				err(1, ifr.ifr_name);
			if (!getifstats(ifr.ifr_name, &icur, &ocur))
				err(1, ifr.ifr_name);
			printf("%8lu %8lu %7u %7u %7u %6u %6u %6u %7u %4u %3uM\n",
				(icur - itot) -
					(cur.ast_rx_mgt - total.ast_rx_mgt),
				ocur - otot,
				cur.ast_tx_altrate - total.ast_tx_altrate,
				cur.ast_tx_shortretry - total.ast_tx_shortretry,
				cur.ast_tx_longretry - total.ast_tx_longretry,
				cur.ast_tx_xretries - total.ast_tx_xretries,
				cur.ast_rx_crcerr - total.ast_rx_crcerr,
				cur.ast_rx_badcrypt - total.ast_rx_badcrypt,
				cur.ast_rx_phyerr - total.ast_rx_phyerr,
				cur.ast_rx_rssi,
				rate);
			total = cur;
			itot = icur;
			otot = ocur;
		} else {
			ifr.ifr_data = (caddr_t) &total;
			if (ioctl(s, SIOCGATHSTATS, &ifr) < 0)
				err(1, ifr.ifr_name);
			if (!getifstats(ifr.ifr_name, &itot, &otot))
				err(1, ifr.ifr_name);
			printf("%8lu %8lu %7u %7u %7u %6u %6u %6u %7u %4u %3uM\n",
				itot - total.ast_rx_mgt,
				otot,
				total.ast_tx_altrate,
				total.ast_tx_shortretry,
				total.ast_tx_longretry,
				total.ast_tx_xretries,
				total.ast_rx_crcerr,
				total.ast_rx_badcrypt,
				total.ast_rx_phyerr,
				total.ast_rx_rssi,
				rate);
		}
		fflush(stdout);
		sigemptyset(&nmask);
		sigaddset(&nmask, SIGALRM);
		sigprocmask(SIG_BLOCK, &nmask, &omask);
		sigemptyset(&nmask);
		if (!signalled)
			sigsuspend(&nmask);
		sigprocmask(SIG_SETMASK, &omask, NULL);
		signalled = 0;
		alarm(interval);
		line++;
		if (line == 21)		/* XXX tty line count */
			goto banner;
		else
			goto loop;
		/*NOTREACHED*/
	} else {
		struct ath_stats stats;

		ifr.ifr_data = (caddr_t) &stats;
		if (ioctl(s, SIOCGATHSTATS, &ifr) < 0)
			err(1, ifr.ifr_name);
		printstats(stdout, &stats);
	}
	return 0;
}
