/* $XFree86: xc/programs/Xserver/hw/xfree86/loader/coffloader.c,v 1.21tsi Exp $ */

/*
 *
 * Copyright 1995,96 by Metro Link, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Metro Link, Inc. not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Metro Link, Inc. makes no
 * representations about the suitability of this software for any purpose.
 *  It is provided "as is" without express or implied warranty.
 *
 * METRO LINK, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL METRO LINK, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#ifdef __QNX__
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#endif
#include <sys/stat.h>

#ifdef DBMALLOC
#include <debug/malloc.h>
#define Xalloc(size) malloc(size)
#define Xcalloc(size) calloc(1,(size))
#define Xfree(size) free(size)
#endif

#include <X11/Xos.h>
#include "os.h"
#include "coff.h"

#include "sym.h"
#include "loader.h"
#include "coffloader.h"

#include "compiler.h"

#ifndef LOADERDEBUG
#define LOADERDEBUG 0
#endif

#if LOADERDEBUG
#define COFFDEBUG ErrorF
#endif

/*
 * This structure contains all of the information about a module
 * that has been loaded.
 */

typedef struct {
    int handle;
    long module;		/* Id of the module used to find inter module calls */
    int fd;
    loader_funcs *funcs;
    FILHDR *header;		/* file header */
    AOUTHDR *optheader;		/* optional file header */
    unsigned short numsh;
    SCNHDR *sections;		/* Start address of the section table */
    int secsize;		/* size of the section table */
    unsigned char **saddr;	/* Start addresss of the sections table */
    unsigned char **reladdr;	/* Start addresss of the relocation table */
    unsigned char *strtab;	/* Start address of the string table */
    int strsize;		/* size of the string table */
    unsigned char *text;	/* Start address of the .text section */
    int txtndx;			/* index of the .text section */
    long txtaddr;		/* offset of the .text section */
    int txtsize;		/* size of the .text section */
    int txtrelsize;		/* size of the .rel.text section */
    unsigned char *data;	/* Start address of the .data section */
    int datndx;			/* index of the .data section */
    long dataddr;		/* offset of the .data section */
    int datsize;		/* size of the .data section */
    int datrelsize;		/* size of the .rel.data section */
    unsigned char *bss;		/* Start address of the .bss section */
    int bssndx;			/* index of the .bss section */
    long bssaddr;		/* offset of the .bss section */
    int bsssize;		/* size of the .bss section */
    SYMENT *symtab;		/* Start address of the .symtab section */
    int symndx;			/* index of the .symtab section */
    int symsize;		/* size of the .symtab section */
    unsigned char *common;	/* Start address of the .common section */
    int comsize;		/* size of the .common section */
    long toc;			/* Offset of the TOC csect */
    unsigned char *tocaddr;	/* Address of the TOC csect */
} COFFModuleRec, *COFFModulePtr;

/*
 * If any relocation is unable to be satisfied, then put it on a list
 * to try later after more modules have been loaded.
 */
typedef struct _coff_reloc {
    COFFModulePtr file;
    RELOC *rel;
    int secndx;
    struct _coff_reloc *next;
} COFFRelocRec;

/*
 * Symbols with a section number of 0 (N_UNDEF) but a value of non-zero
 * need to have space allocated for them.
 *
 * Gather all of these symbols together, and allocate one chunk when we
 * are done.
 */

typedef struct _coff_COMMON {
    SYMENT *sym;
    int index;
    struct _coff_COMMON *next;
} COFFCommonRec;

static COFFCommonPtr listCOMMON = NULL;

/* Prototypes for static functions */
static int COFFhashCleanOut(void *, itemPtr);
static char *COFFGetSymbolName(COFFModulePtr, int);
static COFFCommonPtr COFFAddCOMMON(SYMENT *, int);
static LOOKUP *COFFCreateCOMMON(COFFModulePtr);
static COFFRelocPtr COFFDelayRelocation(COFFModulePtr, int, RELOC *);
static SYMENT *COFFGetSymbol(COFFModulePtr, int);
#if defined(i386) || defined(__powerpc__)
static unsigned char *COFFGetSymbolValue(COFFModulePtr, int);
#endif
static COFFRelocPtr COFF_RelocateEntry(COFFModulePtr, int, RELOC *);
static LOOKUP *COFF_GetSymbols(COFFModulePtr);
static void COFFCollectSections(COFFModulePtr);
static COFFRelocPtr COFFCollectRelocations(COFFModulePtr);

/*
 * Utility Functions
 */

static int
COFFhashCleanOut(void *voidptr, itemPtr item)
{
    COFFModulePtr module = (COFFModulePtr) voidptr;

    return (module->handle == item->handle);
}

/*
 * Manage listResolv
 */
static COFFRelocPtr
COFFDelayRelocation(COFFModulePtr cofffile, int secndx, RELOC *rel)
{
    COFFRelocPtr reloc;

    if ((reloc = xf86loadermalloc(sizeof(COFFRelocRec))) == NULL) {
	ErrorF("COFFDelayRelocation() Unable to allocate memory!!!!\n");
	return 0;
    }

    reloc->file = cofffile;
    reloc->secndx = secndx;
    reloc->rel = rel;
    reloc->next = 0;

    return reloc;
}

/*
 * Manage listCOMMON
 */

static COFFCommonPtr
COFFAddCOMMON(SYMENT *sym, int index)
{
    COFFCommonPtr common;

    if ((common = xf86loadermalloc(sizeof(COFFCommonRec))) == NULL) {
	ErrorF("COFFAddCOMMON() Unable to allocate memory!!!!\n");
	return 0;
    }
    common->sym = sym;
    common->index = index;
    common->next = 0;

    return common;
}

static LOOKUP *
COFFCreateCOMMON(COFFModulePtr cofffile)
{
    int numsyms = 0, size = 0, l = 0;
    int offset = 0;
    LOOKUP *lookup;
    COFFCommonPtr common;

    if (listCOMMON == NULL)
	return NULL;

    common = listCOMMON;
    for (common = listCOMMON; common; common = common->next) {
	/* Ensure long word alignment */
	if (common->sym->n_value != 2 && common->sym->n_value != 1)	/* But not for short and char ;-)(mr) */
	    if (common->sym->n_value % 4 != 0)
		common->sym->n_value += 4 - (common->sym->n_value % 4);

	/* accumulate the sizes */
	size += common->sym->n_value;
	numsyms++;
    }

#ifdef COFFDEBUG
    COFFDEBUG("COFFCreateCOMMON() %d entries (%d bytes) of COMMON data\n",
	      numsyms, size);
#endif

    if ((lookup = xf86loadermalloc((numsyms + 1) * sizeof(LOOKUP))) == NULL) {
	ErrorF("COFFCreateCOMMON() Unable to allocate memory!!!!\n");
	return NULL;
    }

    cofffile->comsize = size;
    if ((cofffile->common = xf86loadercalloc(1, size)) == NULL) {
	ErrorF("COFFCreateCOMMON() Unable to allocate memory!!!!\n");
	return NULL;
    }

    /* Traverse the common list and create a lookup table with all the
     * common symbols.  Destroy the common list in the process.
     * See also ResolveSymbols.
     */
    while (listCOMMON) {
	common = listCOMMON;
	lookup[l].symName = COFFGetSymbolName(cofffile, common->index);
	lookup[l].offset = (funcptr) (cofffile->common + offset);
#ifdef COFFDEBUG
	COFFDEBUG("Adding %p %s\n", (void *)lookup[l].offset,
		  lookup[l].symName);
#endif
	listCOMMON = common->next;
	offset += common->sym->n_value;
	xf86loaderfree(common);
	l++;
    }
    /* listCOMMON == NULL */

    lookup[l].symName = NULL;	/* Terminate the list */
    return lookup;
}

/*
 * Symbol Table
 */

/*
 * Get symbol name
 */
static char *
COFFGetSymbolName(COFFModulePtr cofffile, int index)
{
    char *name;
    SYMENT *sym;

    sym = (SYMENT *) (((unsigned char *)cofffile->symtab) + (index * SYMESZ));

#ifdef COFFDEBUG
    COFFDEBUG("COFFGetSymbolName(%p,%x) %lx", (void *)cofffile, index,
	      sym->n_zeroes);
#endif

    name = xf86loadermalloc(sym->n_zeroes ? SYMNMLEN + 1
			    : strlen((const char *)&cofffile->
				     strtab[(int)sym->n_offset - 4]) + 1);
    if (!name)
	FatalError("COFFGetSymbolName: Out of memory\n");

    if (sym->n_zeroes) {
	strncpy(name, sym->n_name, SYMNMLEN);
	name[SYMNMLEN] = '\000';
    } else {
	strcpy(name, (const char *)&cofffile->strtab[(int)sym->n_offset - 4]);
    }
#ifdef COFFDEBUG
    COFFDEBUG(" %s\n", name);
#endif
    return name;
}

static SYMENT *
COFFGetSymbol(COFFModulePtr file, int index)
{
    return (SYMENT *) (((unsigned char *)file->symtab) + (index * SYMESZ));
}

#if defined(i386) || defined(__powerpc__)
static unsigned char *
COFFGetSymbolValue(COFFModulePtr cofffile, int index)
{
    unsigned char *symval = 0;	/* value of the indicated symbol */
    itemPtr symbol;		/* name/value of symbol */
    char *symname;

    symname = COFFGetSymbolName(cofffile, index);

#ifdef COFFDEBUG
    COFFDEBUG("COFFGetSymbolValue() for %s=", symname);
#endif

    symbol = LoaderHashFind(symname);

    if (symbol)
	symval = (unsigned char *)symbol->address;

#ifdef COFFDEBUG
    COFFDEBUG("%p\n", symval);
#endif

    xf86loaderfree(symname);
    return symval;
}
#endif

#if defined(__powerpc__)
/*
 * This function returns the address of the glink routine for a symbol. This
 * address is used in cases where the function being called is not in the
 * same module as the calling function.
 */
static unsigned char *
COFFGetSymbolGlinkValue(COFFModulePtr cofffile, int index)
{
    unsigned char *symval = 0;	/* value of the indicated symbol */
    itemPtr symbol;		/* name/value of symbol */
    char *name;

    name = COFFGetSymbolName(cofffile, index);

#ifdef COFFDEBUG
    COFFDEBUG("COFFGetSymbolGlinkValue() for %s=", name);
#endif

    symbol = LoaderHashFind(name + 1);	/* Eat the '.' so we get the
					 * Function descriptor instead */

/* Here we are building up a glink function that will change the TOC
 * pointer before calling a function that resides in a different module.
 * The following code is being used to implement this.

	1 00000000 3d80xxxx	lis   r12,hi16(funcdesc)
	2 00000004 618cxxxx	ori   r12,r12,lo16(funcdesc)
	3 00000008 90410014	st    r2,20(r1)	# save old TOC pointer
	4 0000000c 804c0000 	l     r2,0(r12)	# Get address of functions
	5 00000010 7c4903a6	mtctr  r2	# load destination address
	6 00000014 804c0004	l     r2,4(r12)	# get TOC of function
	7 00000018 4e800420	bctr		# branch to it

 */
    if (symbol) {
	symval = (unsigned char *)&symbol->code.glink;
#ifdef COFFDEBUG
	COFFDEBUG("%x\n", symval);
	COFFDEBUG("glink_%s=%x\n", name, symval);
#endif
	symbol->code.glink[0] = 0x3d80;	/* lis r12 */
	symbol->code.glink[1] =
		((unsigned long)symbol->address & 0xffff0000) >> 16;
	symbol->code.glink[2] = 0x618c;	/* ori r12 */
	symbol->code.glink[3] = ((unsigned long)symbol->address & 0x0000ffff);
	symbol->code.glink[4] = 0x9041;	/* st r2,20(r1) */
	symbol->code.glink[5] = 0x0014;
	symbol->code.glink[6] = 0x804c;	/* l r2,0(r12) */
	symbol->code.glink[7] = 0x0000;
	symbol->code.glink[8] = 0x7c49;	/* mtctr r2 */
	symbol->code.glink[9] = 0x03a6;
	symbol->code.glink[10] = 0x804c;	/* l r2,4(r12) */
	symbol->code.glink[11] = 0x0004;
	symbol->code.glink[12] = 0x4e80;	/* bctr */
	symbol->code.glink[13] = 0x0420;
	ppc_flush_icache(&symbol->code.glink[0]);
	ppc_flush_icache(&symbol->code.glink[12]);
    }

    xf86loaderfree(name);
    return symval;
}
#endif /* __powerpc__ */

/*
 * Fix all of the relocation for the given section.
 */
static COFFRelocPtr
COFF_RelocateEntry(COFFModulePtr cofffile, int secndx, RELOC *rel)
{
    SYMENT *symbol;		/* value of the indicated symbol */
    unsigned long *dest32;	/* address of the place being modified */

#if defined(__powerpc__)
    unsigned short *dest16;	/* address of the place being modified */
    itemPtr symitem;		/* symbol structure from has table */
    char *name;
#endif
    unsigned char *symval;	/* value of the indicated symbol */

/*
 * Note: Section numbers are 1 biased, while the cofffile->saddr[] array
 * of pointer is 0 biased, so alway have to account for the difference.
 */

/* 
 * Reminder: secndx is the section to which the relocation is applied.
 *           symbol->n_scnum is the section in which the symbol value resides.
 */

#ifdef COFFDEBUG
    COFFDEBUG("%lx %ld %o ", (unsigned long)rel->r_vaddr,
	      rel->r_symndx, rel->r_type);
#if defined(__powerpc__)
    COFFDEBUG("[%x %x %x] ",
	      RELOC_RSIGN(*rel), RELOC_RFIXUP(*rel), RELOC_RLEN(*rel));
#endif
#endif
    symbol = COFFGetSymbol(cofffile, rel->r_symndx);
#ifdef COFFDEBUG
    COFFDEBUG("%d %lx %d-%d\n", symbol->n_sclass, symbol->n_value,
	      symbol->n_scnum, secndx);
#endif

/*
 * Check to see if the relocation offset is part of the .text segment.
 * If not, we must change the offset to be relative to the .data section
 * which is NOT contiguous.
 */
    switch (secndx + 1) {	/* change the bias */
    case N_TEXT:
	if ((long)rel->r_vaddr < cofffile->txtaddr ||
	    (long)rel->r_vaddr >
	    (long)(cofffile->txtaddr + cofffile->txtsize)) {
	    FatalError("Relocation against N_TEXT not in .text section\n");
	}
	dest32 = (unsigned long *)((long)(cofffile->saddr[secndx]) +
				   ((unsigned char *)rel->r_vaddr -
				    cofffile->txtaddr));
	break;
    case N_DATA:
	if ((long)rel->r_vaddr < cofffile->dataddr ||
	    (long)rel->r_vaddr >
	    (long)(cofffile->dataddr + cofffile->datsize)) {
	    FatalError("Relocation against N_DATA not in .data section\n");
	}
	dest32 = (unsigned long *)((long)(cofffile->saddr[secndx]) +
				   ((unsigned char *)rel->r_vaddr -
				    cofffile->dataddr));
	break;
    case N_BSS:
	if ((long)rel->r_vaddr < cofffile->bssaddr ||
	    (long)rel->r_vaddr >
	    (long)(cofffile->bssaddr + cofffile->bsssize)) {
	    FatalError("Relocation against N_TEXT not in .bss section\n");
	}
	dest32 = (unsigned long *)((long)(cofffile->saddr[secndx]) +
				   ((unsigned char *)rel->r_vaddr -
				    cofffile->bssaddr));
	break;
    default:
	FatalError("Relocation against unknown section %d\n", secndx);
    }

    if (symbol->n_sclass == 0) {
	symval = (unsigned char *)(symbol->n_value + (*dest32) -
				   symbol->n_type);
#ifdef COFFDEBUG
	COFFDEBUG("symbol->n_sclass==0\n");
	COFFDEBUG("dest32=%p\t", (void *)dest32);
	COFFDEBUG("symval=%p\t", symval);
	COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
	*dest32 = (unsigned long)symval;
	return 0;
    }

    switch (rel->r_type) {
#if defined(i386)
    case R_DIR32:
	symval = COFFGetSymbolValue(cofffile, rel->r_symndx);
	if (symval) {
#ifdef COFFDEBUG
	    char *namestr;

	    COFFDEBUG("R_DIR32 %s\n",
		      namestr = COFFGetSymbolName(cofffile, rel->r_symndx));
	    xf86loaderfree(namestr);
	    COFFDEBUG("txtsize=%x\t", cofffile->txtsize);
	    COFFDEBUG("dest32=%p\t", (void *)dest32);
	    COFFDEBUG("symval=%p\t", symval);
	    COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
	    *dest32 = (unsigned long)(symval + (*dest32) - symbol->n_value);
	} else {
	    switch (symbol->n_scnum) {
	    case N_UNDEF:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_UNDEF\n");
#endif
		return COFFDelayRelocation(cofffile, secndx, rel);
	    case N_ABS:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_ABS\n");
#endif
		return 0;
	    case N_DEBUG:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_DEBUG\n");
#endif
		return 0;
	    case N_COMMENT:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_COMMENT\n");
#endif
		return 0;
	    case N_TEXT:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_TEXT\n");
		COFFDEBUG("dest32=%p\t", (void *)dest32);
		COFFDEBUG("symval=%p\t", symval);
		COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  (unsigned long)(cofffile->
							  saddr[N_TEXT - 1]));
		break;
	    case N_DATA:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_DATA\n");
		COFFDEBUG("txtsize=%x\t", cofffile->txtsize);
		COFFDEBUG("dest32=%p\t", (void *)dest32);
		COFFDEBUG("symval=%p\t", symval);
		COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  ((unsigned long)(cofffile->
							   saddr[N_DATA -
								 1])) -
					  cofffile->dataddr);
		break;
	    case N_BSS:
#ifdef COFFDEBUG
		COFFDEBUG("R_DIR32 N_BSS\n");
		COFFDEBUG("dest32=%p\t", (void *)dest32);
		COFFDEBUG("symval=%p\t", symval);
		COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  (unsigned long)(cofffile->
							  saddr[N_BSS - 1]) -
					  (cofffile->bssaddr));
		break;
	    default:
		ErrorF("R_DIR32 with unexpected section %d\n",
		       symbol->n_scnum);
	    }

	}
#ifdef COFFDEBUG
	COFFDEBUG("*dest32=%8.8lx\n", *dest32);
#endif
	break;
    case R_PCRLONG:
	if (symbol->n_scnum == N_TEXT)
	    break;

	symval = COFFGetSymbolValue(cofffile, rel->r_symndx);
#ifdef COFFDEBUG
	COFFDEBUG("R_PCRLONG ");
	COFFDEBUG("dest32=%p\t", (void *)dest32);
	COFFDEBUG("symval=%p\t", symval);
	COFFDEBUG("*dest32=%8.8lx\t", *dest32);
#endif
	if (symval == 0) {
#ifdef COFFDEBUG
	    char *name;

	    COFFDEBUG("***Unable to resolve symbol %s\n",
		      name = COFFGetSymbolName(cofffile, rel->r_symndx));
	    xf86loaderfree(name);
#endif
	    return COFFDelayRelocation(cofffile, secndx, rel);
	}
	*dest32 = (unsigned long)(symval - ((long)dest32 + sizeof(long)));

#ifdef COFFDEBUG
	COFFDEBUG("*dest32=%8.8lx\n", *dest32);
#endif
	break;
    case R_ABS:
	/*
	 * Nothing to really do here.
	 * Usually, a dummy relocation for .file
	 */
	break;
#endif /* i386 */
#if defined(__powerpc__)
    case R_POS:
	/*
	 * Positive Relocation
	 */
	if (RELOC_RLEN(*rel) != 0x1f)
	    FatalError("R_POS with size != 32 bits");
	symval = COFFGetSymbolValue(cofffile, rel->r_symndx);
	if (symval) {
#ifdef COFFDEBUG
	    COFFDEBUG("R_POS ");
	    COFFDEBUG("dest32=%x\t", dest32);
	    COFFDEBUG("symval=%x\t", symval);
	    COFFDEBUG("*dest32=%8.8x\t", *dest32);
#endif
	    *dest32 = (unsigned long)(symval + (*dest32) - symbol->n_value);
	    ppc_flush_icache(dest32);
	} else {
	    switch (symbol->n_scnum) {
	    case N_UNDEF:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_UNDEF\n");
#endif
		return COFFDelayRelocation(cofffile, secndx, rel);
	    case N_ABS:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_ABS\n");
#endif
		return 0;
	    case N_DEBUG:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_DEBUG\n");
#endif
		return 0;
	    case N_COMMENT:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_COMMENT\n");
#endif
		return 0;
	    case N_TEXT:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_TEXT\n");
		COFFDEBUG("dest32=%x\t", dest32);
		COFFDEBUG("symval=%x\t", symval);
		COFFDEBUG("*dest32=%8.8x\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  ((unsigned long)(cofffile->
							   saddr[N_TEXT -
								 1])) -
					  cofffile->txtaddr);
		ppc_flush_icache(dest32);
		break;
	    case N_DATA:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_DATA\n");
		COFFDEBUG("txtsize=%x\t", cofffile->txtsize);
		COFFDEBUG("dest32=%x\t", dest32);
		COFFDEBUG("symval=%x\t", symval);
		COFFDEBUG("*dest32=%8.8x\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  ((unsigned long)(cofffile->
							   saddr[N_DATA -
								 1])) -
					  cofffile->dataddr);
		ppc_flush_icache(dest32);
		break;
	    case N_BSS:
#ifdef COFFDEBUG
		COFFDEBUG("R_POS N_BSS\n");
		COFFDEBUG("dest32=%x\t", dest32);
		COFFDEBUG("symval=%x\t", symval);
		COFFDEBUG("*dest32=%8.8x\t", *dest32);
#endif
		*dest32 = (unsigned long)((*dest32) +
					  (unsigned long)(cofffile->
							  saddr[N_BSS - 1]) -
					  (cofffile->bssaddr));
		ppc_flush_icache(dest32);
		break;
	    default:
		ErrorF("R_POS with unexpected section %d\n", symbol->n_scnum);
	    }
	}
#ifdef COFFDEBUG
	COFFDEBUG("*dest32=%8.8x\t", *dest32);
	COFFDEBUG("\n");
#endif
	break;
    case R_TOC:
	/*
	 * Relative to TOC
	 */
	{
	    dest16 = (unsigned short *)dest32;
	    if (RELOC_RLEN(*rel) != 0x0f)
		FatalError("R_TOC with size != 16 bits");
#ifdef COFFDEBUG
	    COFFDEBUG("R_TOC ");
	    COFFDEBUG("dest16=%x\t", dest16);
	    COFFDEBUG("symbol=%x\t", symbol);
	    COFFDEBUG("symbol->n_value=%x\t", symbol->n_value);
	    COFFDEBUG("cofffile->toc=%x\t", cofffile->toc);
	    COFFDEBUG("*dest16=%8.8x\t", *dest16);
#endif
	    *dest16 = (unsigned long)((symbol->n_value - cofffile->toc));
	    ppc_flush_icache(dest16);
	}
#ifdef COFFDEBUG
	COFFDEBUG("*dest16=%8.8x\t", *dest16);
	COFFDEBUG("\n");
#endif
	break;
    case R_BR:
	/*
	 * Branch relative to self, non-modifiable
	 */

	if (RELOC_RLEN(*rel) != 0x19)
	    FatalError("R_BR with size != 24 bits");
	name = COFFGetSymbolName(cofffile, rel->r_symndx);
	symitem = LoaderHashFind(name);
	if (symitem == 0) {
	    name++;
	    symitem = LoaderHashFind(name);
	}
	if (symitem && cofffile->module != symitem->module) {
#ifdef COFFDEBUG
	    COFFDEBUG("Symbol module %d != file module %d\n",
		      symitem->module, cofffile->module);
#endif
	    symval = COFFGetSymbolGlinkValue(cofffile, rel->r_symndx);
	} else
	    symval = COFFGetSymbolValue(cofffile, rel->r_symndx);
	if (symval == 0) {
#ifdef COFFDEBUG
	    char *name;

	    COFFDEBUG("***Unable to resolve symbol %s\n",
		      name = COFFGetSymbolName(cofffile, rel->r_symndx));
	    xf86loaderfree(name);
#endif
	    return COFFDelayRelocation(cofffile, secndx, rel);
	}
#ifdef COFFDEBUG
	COFFDEBUG("R_BR ");
	COFFDEBUG("dest32=%x\t", dest32);
	COFFDEBUG("symval=%x\t", symval);
	COFFDEBUG("*dest32=%8.8x\t", *dest32);
#endif
	{
	    unsigned long val;

	    val = ((unsigned long)symval - (unsigned long)dest32);
#ifdef COFFDEBUG
	    COFFDEBUG("val=%8.8x\n", val);
#endif
	    val = val >> 2;
	    if ((val & 0x3f000000) != 0x3f000000 &&
		(val & 0x3f000000) != 0x00000000) {
		FatalError("R_BR offset %x too large\n", val << 2);
		break;
	    }
	    val &= 0x00ffffff;
#ifdef COFFDEBUG
	    COFFDEBUG("val=%8.8x\n", val);
#endif
	    /*
	     * The address part contains the offset to the beginning
	     * of the .text section. Disreguard this since we have
	     * calculated the correct offset already.
	     */
	    (*dest32) = ((*dest32) & 0xfc000003) | (val << 2);
#ifdef COFFDEBUG
	    COFFDEBUG("*dest32=%8.8x\n", *dest32);
#endif
	    if (cofffile->module != symitem->module) {
		(*++dest32) = 0x80410014;	/* lwz r2,20(r1) */
	    }
	    ppc_flush_icache(--dest32);
	}

	break;
#endif /* __powerpc__ */
    default:
	ErrorF("COFF_RelocateEntry() Unsupported relocation type %o\n",
	       rel->r_type);
	break;
    }
    return 0;
}

static COFFRelocPtr
COFFCollectRelocations(COFFModulePtr cofffile)
{
    unsigned short i, j;
    RELOC *rel;
    SCNHDR *sec;
    COFFRelocPtr reloc_head = NULL;
    COFFRelocPtr tmp;

    for (i = 0; i < cofffile->numsh; i++) {
	if (cofffile->saddr[i] == NULL)
	    continue;		/* Section not loaded!! */
	sec = &(cofffile->sections[i]);
	for (j = 0; j < sec->s_nreloc; j++) {
	    rel = (RELOC *) (cofffile->reladdr[i] + (j * RELSZ));
	    tmp = COFFDelayRelocation(cofffile, i, rel);
	    tmp->next = reloc_head;
	    reloc_head = tmp;
	}
    }

    return reloc_head;
}

/*
 * COFF_GetSymbols()
 *
 * add the symbols to the symbol table maintained by the loader.
 */

static LOOKUP *
COFF_GetSymbols(COFFModulePtr cofffile)
{
    SYMENT *sym;
    AUXENT *aux = NULL;
    int i, l, numsyms;
    LOOKUP *lookup, *lookup_common, *p;
    char *symname;

/*
 * Load the symbols into memory
 */
    numsyms = cofffile->header->f_nsyms;

#ifdef COFFDEBUG
    COFFDEBUG("COFF_GetSymbols(): %d symbols\n", numsyms);
#endif

    cofffile->symsize = (numsyms * SYMESZ);
    cofffile->symtab =
	    (SYMENT *) _LoaderFileToMem(cofffile->fd,
					cofffile->header->f_symptr,
					(numsyms * SYMESZ), "symbols");

    if ((lookup = xf86loadermalloc((numsyms + 1) * sizeof(LOOKUP))) == NULL)
	return NULL;

    for (i = 0, l = 0; i < numsyms; i++) {
	sym = (SYMENT *) (((unsigned char *)cofffile->symtab) + (i * SYMESZ));
	symname = COFFGetSymbolName(cofffile, i);
	if (sym->n_numaux > 0)
	    aux = (AUXENT *) (((unsigned char *)cofffile->symtab) +
			      ((i + 1) * SYMESZ));
	else
	    aux = NULL;
#ifdef COFFDEBUG
	COFFDEBUG("\t%d %d %lx %x %d %d %s\n",
		  i, sym->n_scnum, sym->n_value, sym->n_type,
		  sym->n_sclass, sym->n_numaux, symname);
	if (aux)
	    COFFDEBUG("aux=\t%ld %lx %x %x %x %lx %x\n",
		      aux->x_scnlen, aux->x_parmhash, aux->x_snhash,
		      aux->x_smtyp, aux->x_smclas, aux->x_stab,
		      aux->x_snstab);
#endif
	i += sym->n_numaux;
	/*
	 * check for TOC csect before discarding C_HIDEXT below
	 */
	if (aux && aux->x_smclas == XMC_TC0) {
	    if (sym->n_scnum != N_DATA)
		FatalError("TOC not in N_DATA section");
	    cofffile->toc = sym->n_value;
	    cofffile->tocaddr = (cofffile->saddr[sym->n_scnum - 1] +
				 sym->n_value - (cofffile->dataddr));
#ifdef COFFDEBUG
	    COFFDEBUG("TOC=%lx\n", cofffile->toc);
	    COFFDEBUG("TOCaddr=%p\n", cofffile->tocaddr);
#endif
	    continue;
	}
	if (sym->n_sclass == C_HIDEXT) {
/*
		&& aux && !(aux->x_smclas == XMC_DS
		&& aux->x_smtyp == XTY_SD) ) ) {
*/
#ifdef COFFDEBUG
	    COFFDEBUG("Skipping C_HIDEXT class symbol %s\n", symname);
#endif
	    continue;
	}
	switch (sym->n_scnum) {
	case N_UNDEF:
	    if (sym->n_value != 0) {
		char *name;
		COFFCommonPtr tmp;

		name = COFFGetSymbolName(cofffile, i);
#ifdef COFFDEBUG
		COFFDEBUG("Adding COMMON space for %s\n", name);
#endif
		if (!LoaderHashFind(name)) {
		    tmp = COFFAddCOMMON(sym, i);
		    if (tmp) {
			tmp->next = listCOMMON;
			listCOMMON = tmp;
		    }
		}
		xf86loaderfree(name);
	    }
	    xf86loaderfree(symname);
	    break;
	case N_ABS:
	case N_DEBUG:
	case N_COMMENT:
#ifdef COFFDEBUG
	    COFFDEBUG("Freeing %s, section %d\n", symname, sym->n_scnum);
#endif
	    xf86loaderfree(symname);
	    break;
	case N_TEXT:
	    if ((sym->n_sclass == C_EXT || sym->n_sclass == C_HIDEXT)
		&& cofffile->saddr[sym->n_scnum - 1]) {
		lookup[l].symName = symname;
		lookup[l].offset = (funcptr)
			(cofffile->saddr[sym->n_scnum - 1] +
			 sym->n_value - cofffile->txtaddr);
#ifdef COFFDEBUG
		COFFDEBUG("Adding %p %s\n",
			  (void *)lookup[l].offset, lookup[l].symName);
#endif
		l++;
	    } else {
#ifdef COFFDEBUG
		COFFDEBUG("TEXT Section not loaded %d\n", sym->n_scnum - 1);
#endif
		xf86loaderfree(symname);
	    }
	    break;
	case N_DATA:
	    /*
	     * Note: COFF expects .data to be contiguous with
	     * .data, so that offsets for .data are relative to
	     * .text. We need to adjust for this, and make them
	     * relative to .data so that the relocation can be
	     * properly applied. This is needed becasue we allocate
	     * .data seperately from .text.
	     */
	    if ((sym->n_sclass == C_EXT || sym->n_sclass == C_HIDEXT)
		&& cofffile->saddr[sym->n_scnum - 1]) {
		lookup[l].symName = symname;
		lookup[l].offset = (funcptr)
			(cofffile->saddr[sym->n_scnum - 1] +
			 sym->n_value - cofffile->dataddr);
#ifdef COFFDEBUG
		COFFDEBUG("Adding %p %s\n",
			  (void *)lookup[l].offset, lookup[l].symName);
#endif
		l++;
	    } else {
#ifdef COFFDEBUG
		COFFDEBUG("DATA Section not loaded %d\n", sym->n_scnum - 1);
#endif
		xf86loaderfree(symname);
	    }
	    break;
	case N_BSS:
	    /*
	     * Note: COFF expects .bss to be contiguous with
	     * .data, so that offsets for .bss are relative to
	     * .text. We need to adjust for this, and make them
	     * relative to .bss so that the relocation can be
	     * properly applied. This is needed becasue we allocate
	     * .bss seperately from .text and .data.
	     */
	    if ((sym->n_sclass == C_EXT || sym->n_sclass == C_HIDEXT)
		&& cofffile->saddr[sym->n_scnum - 1]) {
		lookup[l].symName = symname;
		lookup[l].offset = (funcptr)
			(cofffile->saddr[sym->n_scnum - 1] +
			 sym->n_value - cofffile->bssaddr);
#ifdef COFFDEBUG
		COFFDEBUG("Adding %p %s\n",
			  (void *)lookup[l].offset, lookup[l].symName);
#endif
		l++;
	    } else {
#ifdef COFFDEBUG
		COFFDEBUG("BSS Section not loaded %d\n", sym->n_scnum - 1);
#endif
		xf86loaderfree(symname);
	    }
	    break;
	default:
	    ErrorF("Unknown Section number %d\n", sym->n_scnum);
	    xf86loaderfree(symname);
	    break;
	}
    }

    lookup[l].symName = NULL;	/* Terminate the list */

    lookup_common = COFFCreateCOMMON(cofffile);
    if (lookup_common) {
	for (i = 0, p = lookup_common; p->symName; i++, p++) ;
	memcpy(&(lookup[l]), lookup_common, i * sizeof(LOOKUP));

	xf86loaderfree(lookup_common);
	l += i;
	lookup[l].symName = NULL;
    }

/*
 * remove the COFF symbols that will show up in every module
 */
    for (i = 0, p = lookup; p->symName; i++, p++) {
	while (p->symName && (!strcmp(lookup[i].symName, ".text")
			      || !strcmp(lookup[i].symName, ".data")
			      || !strcmp(lookup[i].symName, ".bss")
	       )) {
	    memmove(&(lookup[i]), &(lookup[i + 1]),
		    (l-- - i) * sizeof(LOOKUP));
	}
    }

    return lookup;
}

#define SecOffset(index) cofffile->sections[index].s_scnptr
#define SecSize(index) cofffile->sections[index].s_size
#define SecAddr(index) cofffile->sections[index].s_paddr
#define RelOffset(index) cofffile->sections[index].s_relptr
#define RelSize(index) (cofffile->sections[index].s_nreloc*RELSZ)

/*
 * COFFCollectSections
 *
 * Do the work required to load each section into memory.
 */
static void
COFFCollectSections(COFFModulePtr cofffile)
{
    unsigned short i;

/*
 * Find and identify all of the Sections
 */

#ifdef COFFDEBUG
    COFFDEBUG("COFFCollectSections(): %d sections\n", cofffile->numsh);
#endif

    for (i = 0; i < cofffile->numsh; i++) {
#ifdef COFFDEBUG
	COFFDEBUG("%d %s\n", i, cofffile->sections[i].s_name);
#endif
	/* .text */
	if (strcmp(cofffile->sections[i].s_name, ".text") == 0) {
	    cofffile->text = _LoaderFileToMem(cofffile->fd,
					      SecOffset(i), SecSize(i),
					      ".text");
	    cofffile->saddr[i] = cofffile->text;
	    cofffile->txtndx = i;
	    cofffile->txtaddr = SecAddr(i);
	    cofffile->txtsize = SecSize(i);
	    cofffile->txtrelsize = RelSize(i);
	    cofffile->reladdr[i] = _LoaderFileToMem(cofffile->fd,
						    RelOffset(i), RelSize(i),
						    ".rel.text");
#ifdef COFFDEBUG
	    COFFDEBUG(".text starts at %p (%x bytes)\n", cofffile->text,
		      cofffile->txtsize);
#endif
	    continue;
	}
	/* .data */
	if (strcmp(cofffile->sections[i].s_name, ".data") == 0) {
	    cofffile->data = _LoaderFileToMem(cofffile->fd,
					      SecOffset(i), SecSize(i),
					      ".data");
	    cofffile->saddr[i] = cofffile->data;
	    cofffile->datndx = i;
	    cofffile->dataddr = SecAddr(i);
	    cofffile->datsize = SecSize(i);
	    cofffile->datrelsize = RelSize(i);
	    cofffile->reladdr[i] = _LoaderFileToMem(cofffile->fd,
						    RelOffset(i), RelSize(i),
						    ".rel.data");
#ifdef COFFDEBUG
	    COFFDEBUG(".data starts at %p (%x bytes)\n", cofffile->data,
		      cofffile->datsize);
#endif
	    continue;
	}
	/* .bss */
	if (strcmp(cofffile->sections[i].s_name, ".bss") == 0) {
	    if (SecSize(i))
		cofffile->bss = xf86loadercalloc(1, SecSize(i));
	    else
		cofffile->bss = NULL;
	    cofffile->saddr[i] = cofffile->bss;
	    cofffile->bssndx = i;
	    cofffile->bssaddr = SecAddr(i);
	    cofffile->bsssize = SecSize(i);
#ifdef COFFDEBUG
	    COFFDEBUG(".bss starts at %p (%x bytes)\n", cofffile->bss,
		      cofffile->bsssize);
#endif
	    continue;
	}
	/* .comment */
	if (strncmp(cofffile->sections[i].s_name,
		    ".comment", strlen(".comment")) == 0) {
	    continue;
	}
	/* .stab */
	if (strcmp(cofffile->sections[i].s_name, ".stab") == 0) {
	    continue;
	}
	/* .stabstr */
	if (strcmp(cofffile->sections[i].s_name, ".stabstr") == 0) {
	    continue;
	}
	/* .stab.* */
	if (strncmp(cofffile->sections[i].s_name,
		    ".stab.", strlen(".stab.")) == 0) {
	    continue;
	}
	ErrorF("COFF: Not loading %s\n", cofffile->sections[i].s_name);
    }
}

/*
 * Public API for the COFF implementation of the loader.
 */
void *
COFFLoadModule(loaderPtr modrec, int cofffd, LOOKUP **ppLookup, int flags)
{
    COFFModulePtr cofffile;
    FILHDR *header;
    int stroffset;		/* offset of string table */
    COFFRelocPtr coff_reloc, tail;
    void *v;

#ifdef COFFDEBUG
    COFFDEBUG("COFFLoadModule(%s,%x,%x)\n", modrec->name, modrec->handle,
	      cofffd);
#endif

    if ((cofffile = xf86loadercalloc(1, sizeof(COFFModuleRec))) == NULL) {
	ErrorF("Unable to allocate COFFModuleRec\n");
	return NULL;
    }

    cofffile->handle = modrec->handle;
    cofffile->module = modrec->module;
    cofffile->fd = cofffd;
    v = cofffile->funcs = modrec->funcs;

/*
 *  Get the COFF header
 */
    cofffile->header =
	    (FILHDR *) _LoaderFileToMem(cofffd, 0, sizeof(FILHDR), "header");
    header = (FILHDR *) cofffile->header;

    if (header->f_symptr == 0 || header->f_nsyms == 0) {
	ErrorF("No symbols found in module\n");
	_LoaderFreeFileMem(header, sizeof(FILHDR));
	xf86loaderfree(cofffile);
	return NULL;
    }
/*
 * Get the section table
 */
    cofffile->numsh = header->f_nscns;
    cofffile->secsize = (header->f_nscns * SCNHSZ);
    cofffile->sections =
	    (SCNHDR *) _LoaderFileToMem(cofffd, FILHSZ + header->f_opthdr,
					cofffile->secsize, "sections");
    cofffile->saddr =
	    xf86loadercalloc(cofffile->numsh, sizeof(unsigned char *));
    cofffile->reladdr =
	    xf86loadercalloc(cofffile->numsh, sizeof(unsigned char *));

/*
 * Load the optional header if we need it ?????
 */

/*
 * Load the rest of the desired sections
 */
    COFFCollectSections(cofffile);

/*
 * load the string table (must be done before we process symbols).
 */
    stroffset = header->f_symptr + (header->f_nsyms * SYMESZ);

    _LoaderFileRead(cofffd, stroffset, &(cofffile->strsize), sizeof(int));

    stroffset += 4;		/* Move past the size */
    cofffile->strsize -= sizeof(int);	/* size includes itself, so reduce by 4 */
    cofffile->strtab =
	    _LoaderFileToMem(cofffd, stroffset, cofffile->strsize, "strings");

/*
 * add symbols
 */
    *ppLookup = COFF_GetSymbols(cofffile);

/*
 * Do relocations
 */
    coff_reloc = COFFCollectRelocations(cofffile);
    if (coff_reloc) {
	for (tail = coff_reloc; tail->next; tail = tail->next) ;
	tail->next = _LoaderGetRelocations(v)->coff_reloc;
	_LoaderGetRelocations(v)->coff_reloc = coff_reloc;
    }

    return (void *)cofffile;
}

void
COFFResolveSymbols(void *mod)
{
    COFFRelocPtr newlist, p, tmp;

    /* Try to relocate everything.  Build a new list containing entries
     * which we failed to relocate.  Destroy the old list in the process.
     */
    newlist = 0;
    for (p = _LoaderGetRelocations(mod)->coff_reloc; p;) {
	tmp = COFF_RelocateEntry(p->file, p->secndx, p->rel);
	if (tmp) {
	    /* Failed to relocate.  Keep it in the list. */
	    tmp->next = newlist;
	    newlist = tmp;
	}
	tmp = p;
	p = p->next;
	xf86loaderfree(tmp);
    }
    _LoaderGetRelocations(mod)->coff_reloc = newlist;
}

int
COFFCheckForUnresolved(void *mod)
{
    char *name;
    COFFRelocPtr crel;
    int flag, fatalsym = 0;

    if ((crel = _LoaderGetRelocations(mod)->coff_reloc) == NULL)
	return 0;

    while (crel) {
	name = COFFGetSymbolName(crel->file, crel->rel->r_symndx);
	flag = _LoaderHandleUnresolved(name,
				       _LoaderHandleToName(crel->file->
							   handle));
	if (flag)
	    fatalsym = 1;
	xf86loaderfree(name);
	crel = crel->next;
    }
    return fatalsym;
}

void
COFFUnloadModule(void *modptr)
{
    COFFModulePtr cofffile = (COFFModulePtr) modptr;
    COFFRelocPtr relptr, reltptr, *brelptr;

/*
 * Delete any unresolved relocations
 */

    relptr = _LoaderGetRelocations(cofffile->funcs)->coff_reloc;
    brelptr = &(_LoaderGetRelocations(cofffile->funcs)->coff_reloc);

    while (relptr) {
	if (relptr->file == cofffile) {
	    *brelptr = relptr->next;	/* take it out of the list */
	    reltptr = relptr;	/* save pointer to this node */
	    relptr = relptr->next;	/* advance the pointer */
	    xf86loaderfree(reltptr);	/* free the node */
	} else {
	    brelptr = &(relptr->next);
	    relptr = relptr->next;	/* advance the pointer */
	}
    }

/*
 * Delete any symbols in the symbols table.
 */

    LoaderHashTraverse((void *)cofffile, COFFhashCleanOut);

/*
 * Free the sections that were allocated.
 */
#define CheckandFree(ptr,size)	if(ptr) _LoaderFreeFileMem((ptr),(size))

    CheckandFree(cofffile->strtab, cofffile->strsize);
    CheckandFree(cofffile->symtab, cofffile->symsize);
    CheckandFree(cofffile->text, cofffile->txtsize);
    CheckandFree(cofffile->reladdr[cofffile->txtndx], cofffile->txtrelsize);
    CheckandFree(cofffile->data, cofffile->datsize);
    CheckandFree(cofffile->reladdr[cofffile->datndx], cofffile->datrelsize);
    CheckandFree(cofffile->bss, cofffile->bsssize);
    if (cofffile->common)
	xf86loaderfree(cofffile->common);
/*
 * Free the section table, and section pointer array
 */
    _LoaderFreeFileMem(cofffile->sections, cofffile->secsize);
    xf86loaderfree(cofffile->saddr);
    xf86loaderfree(cofffile->reladdr);
    _LoaderFreeFileMem(cofffile->header, sizeof(FILHDR));
/*
 * Free the COFFModuleRec
 */
    xf86loaderfree(cofffile);

    return;
}

char *
COFFAddressToSection(void *modptr, unsigned long address)
{
    COFFModulePtr cofffile = (COFFModulePtr) modptr;
    int i;

    for (i = 1; i < cofffile->numsh; i++) {
	if (address >= (unsigned long)cofffile->saddr[i] &&
	    address <= (unsigned long)cofffile->saddr[i] + SecSize(i)) {
	    return cofffile->sections[i].s_name;
	}
    }
    return NULL;
}
