/*
 * dm_bb:
 *	Pretty print blackbox events.
 *
 * TODO:
 *	* Add code to set mode
 *	* Add code to filter events based on channel number
 *	  (use a bit in event to indicate which events are per channel,
 *	   and then assume channel number is stored in data[0])
 *
 * $Id: dm_bb.c,v 1.22 2000/04/20 18:18:58 troyd Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>

#ifdef _WIN32
	#include "windows.h"
	#include "w32util.h"
    #include "buildctrl.h"
	#define sleep(s) w32_sleep(s * 1000)
	#define getopt w32_getopt
	#define BB_CDECL  __cdecl
	typedef __int64	BIGINT;

    #if !NT5_BUILD
    // NT5 and newer DDKs define these types.  They float (at compile time) from 32 to 64
    // bit width depending on the size of pointers in the system being compiled for.
    // Define it for NT4.
    typedef ULONG        ULONG_PTR, *PULONG_PTR;
    typedef LONG         LONG_PTR,  *PLONG_PTR;
    typedef int          INT_PTR,   *PINT_PTR;
    typedef unsigned int UINT_PTR,  *PUINT_PTR;
    #endif
#else
	typedef unsigned long ULONG_PTR;
	#include <unistd.h>
	#include <sys/mman.h>
	#define BB_CDECL
	typedef long long BIGINT;
#endif
#include "feptypes.h"
#include "triglbl.h"
#include "dxbglbl.h"
#include "fepglbl.h"
#include "hou2mmap.h"
#define DENSE 1
#include "events.h"			/* From ../../libcsc/csc/include */
#include "status.h"			/* From ../../libcsc/csc/include */
typedef int int32;
#include "blackbox.h"
#include "evtbl.h"			/* From ../vocal/ */
#include "bbevents.h" 
#include "libtools.h" 

extern int host_endian;
extern int global_endian;

/*
 *	Convert a MIPS virt address in a physical address on the board
 */
#define	PHYSADDR(X)	(host_swap4 ((int)(ULONG_PTR)(X)) & 0x7fffFFFF)

/*
 *	Options
 */
char	*Argv0;
uint32	Offset = 0;
char	Device[128];
uint32	Channel = 0;
int	Chanopt = 0;
int	Follow = 0;
int	Print = 1;
uint32	EvMask = 0;
char	*ReadFilename = NULL;
char	*WriteFilename = NULL;
FILE	*WriteFp;
int	Noheader;
uint32	Timestamphz = 0x2faf080;

/*
 *	Usage
 */
void
usage(void)
{
	fprintf(stderr,
"Usage:\n"
"	%s [options]\n"
"\n"
"	Print blackbox events from live FEP, core image, or logfile\n"
"\n"
"Options:\n"
#ifdef _WIN32
"-d device	Specify device ["WANLINKS_DMBB_RAS_DEVICE_NAME"7]\n"
#else
"-b boardnum	Specify board number of live FEP\n"
"-d device	Specify device or core image file [/dev/dm/ram0]\n"
#endif
"-o offset	Offset in device or file to map (use with -d /dev/kmem)\n"
"-c channel	For events that can be captured on a per channel basis,\n"
"		the channel number to be captured.  Multiple -c's are OK.\n"
"-C chanmask	Use in place of -c for multiple channels at once.\n"
"		Each bit in the mask represents a CSC channel\n"
"		0x1=CSC chan 0, 0x2=chan1, 0x4=chan2, 0x8=chan3, ...\n"
"-f		Run forever (like tail -f).  Else print buffer and stop.\n"
"-H timestamphz  Override timestamp HZ for use with the -R option.\n"
"-q		Quiet.  Do not print anything to stdout.  Used with -w.\n"
"-r file  	Read (replay) events from a file\n"
"-R file  	Read (replay) raw events from a file (no header info in file).\n"
"        		Warning:  HZ defaults to 0x2faf080 with the -R option.\n"
"         		Use -H to use a different timestamp HZ with the -R option.\n"	
"-w file  	Write (log) events to a file\n"
"-e evmask	Set event mask [0xC0000001]:\n"
"			0x1     	All normal events\n"
"			0x2     	Miscellaneous events\n"
"			0x4     	Arbiter subsystem\n"
"			0x8     	IL3 State Machine Debug Events\n"
"			0x8     	IL3 events\n"
"			0x10    	LAPD DLPI Events\n"
"			0x20    	SDXB Events\n"
"			0x40    	MGMT Events\n"
"			0x80    	D-chan controller events.\n"
"			0x100   	Signalling (csc<->arbiter) events\n"
"			0x200   	Pump aviator events\n"
"			0x400   	CT1 and E1R2 signalling events\n"
"			0xC0000000	CSC\n"
	,Argv0
	);
	exit(1);
}



char *
digi_ev2str(unsigned long event)
{
	static char	retstr[128];
	EVMAP		*eptr;
	int		i = 0;

	while (i < sizeof(DigiEvMap)/sizeof(EVMAP)) {
		eptr = &DigiEvMap[i];

		if ((unsigned long)eptr->num == event)
			return(eptr->name);

		++i;
	}		

	sprintf(retstr, "(0x%x)", (uint32)event);
	return(retstr);
}


char *
vocal_ev2str(unsigned long event)
{
	static char	retstr[128];
	EVMAP		*eptr;
	int		i = 0;

	while (i < sizeof(EvMap)/sizeof(EVMAP)) {
		eptr = &EvMap[i];

		if ((unsigned long)eptr->num == event)
			return(eptr->name);

		++i;
	}		

	sprintf(retstr, "(0x%x)", (uint32)event);
	return(retstr);
}

char *
ev2str(unsigned long event)
{
	if (event & BB_DIGI_EVENT) 
		return(digi_ev2str(event));
	else
		return(vocal_ev2str(event));
}

/*
 *	Return true if an event is a per-channel event
 */
int
perchan(uint32 event)
{
	if ( (event & BB_DIGI_EVENT) == 0)
		return (1);

	/*
	 *	We really ought to encode whether an event is per-channel
	 *	or not in the event number to make this easy.
	 *
	 *	TODO: these probably are not the entire list of events
	 *	that are per-channel.
	 */
	switch (event)
	{
	case BBE_SDXB_PROCESSED_SDXB_MSTAT:	return 1;
	case BBE_SDXB_PROCESSED_SDXB_EVENT:	return 1;
	case BBE_SDXB_PROCESSED_SDXB_FLUSH:	return 1;
	case BBE_SDXB_PROCESSED_SDXB_LERR:	return 1;
	case BBE_SDXB_PROCESSED_SDXB_PARAM:	return 1;
	case BBE_SDXB_PROCESSED_SDXB_SYNC:	return 1;
	case BBE_SDXB_CLOSE:			return 1;
	case BBE_SDXB_CTS_OFF:			return 1;
	case BBE_SDXB_CTS_ON:			return 1;
	case BBE_SDXB_DCD_OFF:			return 1;
	case BBE_SDXB_DCD_ON:			return 1;
	case BBE_SDXB_DSR_OFF:			return 1;
	case BBE_SDXB_DSR_ON:			return 1;
	case BBE_SDXB_OPEN:			return 1;

	case BBE_CSC_OVERLAY_LOAD:		return 1;
	case BBE_CSC_OVERLAY_LOADED:		return 1;
	case BBE_CSC_BAD_HOOKSTATE:		return 1;
	case BBE_CSC_DSP_READ:			return 1;
	case BBE_CSC_DSP_WRITE:			return 1;
	case BBE_CSC_DSP_DEBUG1:		return 1;
	case BBE_CSC_DSP_DEBUG2:		return 1;
	case BBE_CSC_DSP_DEBUG3:		return 1;
	case BBE_CSC_DSP_DEBUG4:		return 1;
	case BBE_CSC_FAX_FHS:			return 1;
	case BBE_CSC_FAX_FPS_LINE_CNT:		return 1;
	case BBE_CSC_FAX_FPS_BAD_LINE_CNT:	return 1;
	case BBE_CSC_FOOBUF_PUT:		return 1;
	case BBE_CSC_DSP_BAD_STATUS_PTRS:	return 1;
	case BBE_CSC_OVERLAY_LATE:		return 1;

	default:
		return 0;
	}
}

/*
 *	Pretty print one BB sample to a file
 */
uint32		LastTimestamp;
uint32		TimestampHz;
BIGINT		Now;
int		FirstSample;
int		LastCscChannel = -1;

void
pretty_print_epoch(int hz)
{
	TimestampHz = hz;
	LastTimestamp = 0;
	Now = 0;
	FirstSample = 1;
}

void
pretty_print(FILE *fp, BLACKBOX_DATA *bbd)
{
	uint32		delta;
	uint32		event = host_swap4(bbd->event);
	uint32		timestamp = host_swap4(bbd->timestamp);
	uint32		data0, data1;
	int		channel;
	char		chanstr[16];

	if (FirstSample)
	{
		LastTimestamp = timestamp;
		FirstSample = 0;
	}

	delta = timestamp - LastTimestamp;
	LastTimestamp = timestamp;

	Now += delta;

 
 	if(BB_IS_PER_CHANNEL(event)) {
 		channel = BB_GET_CHANNEL(event);
 		event = BB_STRIP_CHANNEL_INFO(event);
		sprintf(chanstr, "%2d-", channel);
 	} else {
		strcpy(chanstr, "   ");
	}


	switch (event)
	{
	case BBE_CSC_CHANNEL:
		data0 = host_swap4(bbd->data[0]);
		LastCscChannel = data0;
		break;
	case BBE_CSC_ATCMD:
	case BBE_CSC_ATRESP:
		if (Chanopt && LastCscChannel >= 0 &&
			(Channel & (1<<LastCscChannel)) == 0)
			return;
		printf("%8.3f %s%-45s 0x%08x %-8.8s\n",
			(double) Now/TimestampHz, 
			chanstr, ev2str(event), 
			LastCscChannel,
			(char *)bbd->data);
		break;
	default:
		data0 = host_swap4(bbd->data[0]);

		if (Chanopt && perchan(event) && (Channel & (1<<data0)) == 0)
			return;

		data1 = host_swap4(bbd->data[1]);

		printf("%8.3f %s%-45s 0x%08x 0x%08x\n",
			(double) Now/TimestampHz, 
			chanstr, ev2str(event),
			data0, data1);
		break;
	}
}

/*
 *	Process (replay) samples from a file
 */
int
do_fromfile(void)
{
	BLACKBOX_CTRL	bb;
	BLACKBOX_DATA	bbd;
	FILE		*fp;
	int		rc;

	fp = fopen(ReadFilename, "rb");
	if (!fp)
		syserror(1, "Can't open '%s'\n", ReadFilename);

	if(!Noheader) {
		if (fread(&bb, sizeof(bb), 1, fp) != 1)
			syserror(1, "Can't read '%s'\n", ReadFilename);
	}

	if (WriteFp && fwrite(&bb, sizeof(bb), 1, WriteFp) != 1)
		syserror(1, "Can't write control structure\n");

	if(Noheader)
		pretty_print_epoch(Timestamphz);
	else
		pretty_print_epoch(bb.timestamp_hz);
	

	while (fread(&bbd, sizeof(bbd), 1, fp) == 1)
	{
		if (Print)
			pretty_print(stdout, &bbd);

		if (WriteFp)
		{
			rc = fwrite(&bbd, sizeof(bbd), 1, WriteFp);
			if (rc != 1)
				error(1, "Can't write sample!\n");
		}
	}

	fclose(fp);

	return (0);
}

uchar *my_fep_map_ram(char *Device)
{
	uchar *memva;
        int memlen ;

        memlen = 0x800000 ;
	memva = fep_internal_map(Device, Offset, memlen);

        return (memva) ;
}

void my_fep_unmap_mem(char *Device, void *memva)
{
	fep_internal_unmap(Device, memva);
}

/*
 *	Process samples from a device
 */
int
do_fromdevice(void)
{
	int		rc;
	int		iscore = 0;
	uchar		*va;
	ulong		gco;
	GLOBAL_FEP_DATA	*gfd;
	BLACKBOX_CTRL	*bb;
	BLACKBOX_DATA	*bb_beg, *bb_end;
	BLACKBOX_DATA	*bb_in;
	BLACKBOX_DATA	*bb_out;

	/*
	 *	Figure out if we are dealing with a live FEP or a
	 *	dead core file.
	 *
	 *	This test is simplistic and should be changed to
	 *	do a stat() or something thats 100% reliable.
	 */
#if _WIN32
	if (_strnicmp(Device, WANLINKS_DMBB_RAS_DEVICE_NAME, 7) != 0)
#else
	if (strncmp(Device, "/dev/", 5) != 0)
#endif
		iscore = 1;

	/*
	 *	Map in the device or file
	 */
	va = (uchar *) my_fep_map_ram(Device);
	if (!va)
		syserror(1, "Can't mmap '%s'\n", Device);

	fep_get_global_endian(va);

	/*
	 *	Get pointer to global common area and BB control structure
	 */
	gco = * (uint32 *) (va + GLOBAL_DATA_OFFSET);

	if (gco == 0)
		error(1, "Invalid Global Data Offset value!\n");

	gfd = (GLOBAL_FEP_DATA *) (va + host_swap4(gco));

	if (host_swap4(gfd->common.magic) != MAGIC_FEP)
		error(1, "Global FEP Data Magic incorrect! %x != %x\n",
			host_swap4(gfd->common.magic) , MAGIC_FEP);

	bb = (BLACKBOX_CTRL *) (va + global_swap4(gfd->blackbox));

	/*
	 *	If we are recording to a file, save the control
	 *	structure (so we know the HZ value)
	 */
	if (WriteFp && fwrite(bb, sizeof(*bb), 1, WriteFp) != 1)
		syserror(1, "Can't write control structure\n");

	/*
	 *	Select channel to capture
	 */
	if (iscore && Print)
	{
		printf("*** This trace was taken from a core dump\n");
		printf("*** The channel mask was 0x%08x\n", bb->csc_channel);
	}
	else if (Chanopt > 0)
	{
		Channel = host_swap4(Channel);
		rc = fep_write(va,
			global_swap4(gfd->blackbox) + 	
				offsetof(BLACKBOX_CTRL, csc_channel),
				&Channel, sizeof(Channel));
		Channel = host_swap4(Channel);
		if (rc != sizeof(Channel))
			error(1, "Couldn't write channel #\n");
	}

	/*
	 *	Select Event mask
	 */
	if (iscore && Print)
	{
		printf("*** The event mask was 0x%08x\n", bb->event_mask);
	}
	else if (EvMask)
	{
		EvMask = host_swap4(EvMask);
		rc = fep_write(va, global_swap4(gfd->blackbox) 
			+ offsetof(BLACKBOX_CTRL, event_mask),
				&EvMask, sizeof(EvMask));
		EvMask = host_swap4(EvMask);
		if (rc != sizeof(EvMask))
			error(1, "Couldn't write event mask\n");
	}

	/*
	 *	Get current event buffer limits
	 */
	bb = (BLACKBOX_CTRL *) (va + global_swap4(gfd->blackbox));
	bb_beg = (BLACKBOX_DATA *) (va + PHYSADDR(bb->buf));
	bb_end = (BLACKBOX_DATA *) (va + PHYSADDR(bb->bufend));
	bb_in = (BLACKBOX_DATA *) (va + PHYSADDR(bb->in));

	/*
	 *	Locate the oldest sample
	 */
	if (Follow)
		bb_out = bb_in;
	else
	{
		for (bb_out = bb_in + 1; bb_out != bb_in; ++bb_out)
		{
			if (bb_out >= bb_end)
				bb_out = bb_beg;

			if (bb_out->event)
				break;
		}
	}

	#if 0
		printf("bb_beg=%x\n", bb_beg);
		printf("bb_end=%x\n", bb_end);
		printf("bb_in=%x\n", bb_in);
	#endif

	pretty_print_epoch(host_swap4(bb->timestamp_hz));

	for (;;)
	{
		if (bb_out == bb_in)
		{
			if (!iscore && Follow)
			{
				fflush(stdout);
				sleep(1);
				bb_in = (BLACKBOX_DATA *) 
					(va+PHYSADDR(bb->in));
				continue;
			}
			else
				break;
		}

		if (Print)
			pretty_print(stdout, bb_out);

		if (WriteFp)
		{
			rc = fwrite(bb_out, sizeof(*bb_out), 1, WriteFp);
			if (rc != 1)
				error(1, "Can't write sample!\n");
		}

		if (++bb_out >= bb_end)
			bb_out = bb_beg;
	}

	my_fep_unmap_mem(Device, va);

	return (0);
}

/*
 *	Cleanup on error or on interrupt
 */
void
error_exit(void)
{
	if (WriteFp)
	{
		fflush(WriteFp);
		fclose(WriteFp);
		WriteFp = NULL;
	}
}

void
BB_CDECL
goodbye(int signal)
{
	error_exit();
	exit(0);
}

int
BB_CDECL
main(int argc, char *argv[])
{
	int		c;
	int		rc;
	int		chanval;

	strcpy(Device, "/dev/dm/ram0");

	Argv0 = argv[0];

	error_init(Argv0, error_exit);

	while ((c=getopt(argc, argv, "b:c:C:d:e:fo:H:r:R:w:q")) != EOF) {
		switch(c) {
		case 'b':
			sprintf(Device,"/dev/dm/ram%d",atoi(optarg));
			break;
		case 'c':
			++Chanopt;
			chanval = atoi(optarg);
			if (chanval >= 0 && chanval < 32)
				Channel |= (1 << chanval);
			else
				error(1, "Channel %d out of range 0-31\n",
					chanval);
			break;
		case 'C':
			++Chanopt;
			Channel = strtoul(optarg, NULL, 0);
			break;
		case 'd':
			strcpy(Device, optarg);
			break;
		case 'e':
			EvMask = strtoul(optarg, NULL, 16);
			break;
		case 'f':
			Follow = 1;
			break;

		case 'H':
			Timestamphz = strtoul(optarg, NULL, 16);
			break;

		case 'o':
			Offset = atoi(optarg);
			break;
		case 'r':
			ReadFilename = optarg;
			break;
		case 'R':
			ReadFilename = optarg;
			Noheader = 1;
			break;
		case 'w':
			WriteFilename = optarg;
			break;
		case 'q':
			Print = 0;
			break;
		default:
			usage();
		}
	}

	argv += optind;
	argc -= optind;

	if (argc > 0) {
		usage();
	}

	signal(SIGINT, goodbye);
	signal(SIGTERM, goodbye);

	if (WriteFilename)
	{
		WriteFp = fopen(WriteFilename, "wb");
		if (!WriteFp)
			syserror(1, "Can't open '%s'\n", WriteFilename);
	}

	if (ReadFilename)
		rc = do_fromfile();
	else
		rc = do_fromdevice();

	exit(rc);
}
