/*
 * corelistdump.c - This program traverses the linked list of registered
 *                  firmware data structures and creates a valid corefile
 *                  for use with gdb.  The use of this program is primarily
 *                  to generate a core from the corelist that we can verify 
 *                  with gdb.  This can also be used as an example of how
 *                  to traverse the corelist in general.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>

#include "feptypes.h"
#include "triglbl.h"
#include "dxbglbl.h"
#include "fepglbl.h"
#include "hou2mmap.h"

typedef struct {
	uint32 offset;		/* Offset of memory area to save */
	uint32 length;		/* Length of memory area to save */
} CHUNKENTRY;

void usage(void);
int chunkcompare(const void *, const void *);


int main(int argc, char *argv[])
{
	int verbose=0;
	FILE *outfd;
	char *vaddr;
	ulong gco;
	GLOBAL_FEP_DATA *gfd;
	COREINFO *ci;
	int devicefd, c, nodecount, i, j, gapwords;
	CHUNKENTRY *chunklist;
	long currentoffset, rc, memsize=0;
	unsigned int core_constant = CORE_CONSTANT;
	char junkbuf[4096];

	while((c=getopt(argc, argv, "v")) != EOF) {
		switch(c) {
			case 'v':
				verbose = 1;
				break;
			default:
				usage();
		}
	}

	if(argc-optind != 2) {
		usage();
	}


	if((devicefd=open(argv[optind], O_RDONLY)) < 0) {
		fprintf(stderr, "Can't open '%s': %s\n", argv[optind], strerror(errno));
		exit(1);
	}

	/*
	 * stdio fopen() used for its buffering ability
	 */
	if((outfd=fopen(argv[optind+1], "w")) == NULL) {
		fprintf(stderr, "Can't open '%s': %s\n", 
								argv[optind+1], strerror(errno));
		exit(1);
	}

	/*
	 * Determine size of RAM device file.  Okay, okay, so this is a really
	 * corny way of determing RAM size.  I thought it was the most portable
	 * way without having to first mmap() the device file in or requiring the
	 * driver to implement the lseek() hook for the RAM minor devices.
	 */
	while((rc=read(devicefd, junkbuf, 4096)) > 0) {
		memsize += rc;
	}

	if(verbose)
		printf("Board's memory size in bytes = %ld\n", memsize);

	vaddr = (uchar *) mmap((caddr_t) 0, memsize, PROT_READ, 
										MAP_SHARED, devicefd, 0x00000000);

	if(vaddr == (void *)-1) {
		fprintf(stderr, "Can't mmap '%s': %s\n", argv[optind], strerror(errno));
		exit(1);
	}

	/*
	 *	Get pointer to global common area
	 */
	gco = * (ulong *) (vaddr + GLOBAL_DATA_OFFSET);

	if(gco == 0) {
		fprintf(stderr, "Error: Invalid Global Data Offset value!\n");
		exit(1);
	}

	gfd = (GLOBAL_FEP_DATA *) (vaddr + gco);

	if(gfd->common.magic != MAGIC_FEP) {
		fprintf(stderr, "Error: Global FEP Data Magic incorrect!\n");
		exit(1);
	}

	if(verbose) {
		printf("coresize (in decimal bytes) = %d\n"
		       "coreinfo offset             = 0x%x\n", 
		       gfd->coresize, gfd->coreinfo);
	}

	ci = (COREINFO *) (vaddr + gfd->coreinfo);

	/*
	 * Let's first count the number of nodes in the list and make sure that
	 * each node is valid.  We count the nodes so that we can alloc the right
	 * size (okay, so I'm lazy and don't feel like using realloc() or guessing
	 * what the max size of the chunklist would have to be if it was a
	 * fixed-size array).
	 */

	nodecount = 0;

	if(verbose)
		printf("Corelist contents:\n");

	while(1) {
		if(ci->magic != MAGIC_COREINFO) {
			fprintf(stderr, "Corrupt Corelist!\n");
			exit(1);
		}

		if(verbose)
			printf("offset      = 0x%x\n"
			       "length      = %d\n"
			       "next_offset = 0x%x\n",
			       ci->offset, ci->length, ci->next_offset);

		nodecount++;

		if(verbose)
			printf("----------------------\n");

		if(ci->next_offset == 0)
			break;

		ci = (COREINFO *) (vaddr + ci->next_offset);
	}

	if(verbose)
		printf("\n\n");
	
	ci = (COREINFO *) (vaddr + gfd->coreinfo);
	i = 0;
	chunklist = (CHUNKENTRY *) malloc(sizeof(CHUNKENTRY) * nodecount);
	memset(chunklist, '\0', sizeof(CHUNKENTRY) * nodecount);

	while(1) {
		chunklist[i].offset = ci->offset;
		chunklist[i].length = ci->length;

		i++;
		
		if(ci->next_offset == 0)
			break;

		ci = (COREINFO *) (vaddr + ci->next_offset);
	}

	qsort(chunklist, nodecount, sizeof(CHUNKENTRY), chunkcompare);


	/*
	 * Now write out the corefile and fill the gaps with CORE_CONSTANT.
	 */

	for(i=0; i < nodecount; ++i) {
		currentoffset = ftell(outfd);
		gapwords = (chunklist[i].offset - currentoffset)/sizeof(core_constant);

		for(j=0; j < gapwords; ++j) {
			if(fwrite(&core_constant, sizeof(core_constant), 1, outfd) != 1) {
				fprintf(stderr, "write error: %s\n", strerror(errno));
				exit(1);
			}
		}

		if(fwrite((vaddr+chunklist[i].offset), 1, chunklist[i].length, outfd) 
													!= chunklist[i].length) {
			fprintf(stderr, "write error (2): %s\n", strerror(errno));
			exit(1);
		}

		if(verbose) {
			printf("Wrote address range 0x%08x - 0x%08x to core file.\n",
				(int)chunklist[i].offset, 
				(int)(chunklist[i].offset + chunklist[i].length));
		}
	}


	/*
	 * Fill the rest of the core file with the CORE_CONSTANT...
	 */
	currentoffset = ftell(outfd);
	gapwords = (memsize - currentoffset)/sizeof(core_constant);

	for(j=0; j < gapwords; ++j) {
		if(fwrite(&core_constant, sizeof(core_constant), 1, outfd) != 1) {
			fprintf(stderr, "write error: %s\n", strerror(errno));
			exit(1);
		}
	}

	if(verbose)
		printf("Done.\n");

	fclose(outfd);
	close(devicefd);

	exit(0);
}



int chunkcompare(const void *el1, const void *el2)
{
	return(((CHUNKENTRY*)el1)->offset - ((CHUNKENTRY*)el2)->offset);
}

void usage(void)
{
	fprintf(stderr, "usage: corelistdump [-v] <ram-device> <outfile>\n");
	exit(1);
}
