/* ".COD" file output for gplink
   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
   James Bowman, Scott Dattalo

This file is part of gputils.

gputils is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

gputils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with gputils; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "stdhdr.h"

#include "libgputils.h"
#include "gplink.h"
#include "cod.h"

static DirBlockInfo main_dir;
static int cod_lst_line_number = 0;
static int blocks = 0;

static void
init_DirBlock(DirBlockInfo *a_dir)
{
  int i;

  /* create space for the directory block: */

  gp_cod_create(&a_dir->dir, &blocks);

  a_dir->next_dir_block_info = NULL;

  /* The code blocks associated with this directory
     do not yet exist. */
  for(i=0; i<COD_CODE_IMAGE_BLOCKS; i++) {
    a_dir->cod_image_blocks[i].block = NULL;
    a_dir->cod_image_blocks[i].block_number = 0;
  }

  /* Initialize the directory block with known data. It'll be written
   * to the .cod file after everything else */
  gp_cod_strncpy(&a_dir->dir.block[COD_DIR_SOURCE], 
	         state.codfilename,
	         COD_DIR_DATE - COD_DIR_SOURCE);
  gp_cod_date(&a_dir->dir.block[COD_DIR_DATE], 
              COD_DIR_TIME - COD_DIR_DATE);
  gp_cod_time(&a_dir->dir.block[COD_DIR_TIME], 
              COD_DIR_VERSION - COD_DIR_TIME);
  gp_cod_strncpy(&a_dir->dir.block[COD_DIR_VERSION], 
	         VERSION,
	         COD_DIR_COMPILER - COD_DIR_VERSION);
  gp_cod_strncpy(&a_dir->dir.block[COD_DIR_COMPILER], 
	         "gplink",
	         COD_DIR_NOTICE - COD_DIR_COMPILER);
  gp_cod_strncpy(&a_dir->dir.block[COD_DIR_NOTICE], 
	         GPUTILS_COPYRIGHT_STRING,
	         COD_DIR_SYMTAB - COD_DIR_NOTICE);

  /* The address is always two shorts or 4 bytes long */
  gp_putl16(&a_dir->dir.block[COD_DIR_ADDRSIZE], 4);

}

/* Assign each file name unique file number.  A file may appear in the
   symbol table more than once.  */

static void
assign_file_id(void)
{
  struct symbol_table *file_table;
  gp_symbol_type *symbol;
  gp_aux_type *aux;
  struct symbol *s;
  int file_id = 0;
  int *value;
  
  /* build a case sensitive file table */
  file_table = push_symbol_table(NULL, false);
  
  symbol = state.object->symbols;
  while(symbol != NULL) {
    if (symbol->class == C_FILE) {
      aux = symbol->aux_list;
      assert(aux != NULL);
      s = get_symbol(file_table, aux->_aux_symbol._aux_file.filename);
      if (s) {
        /* fetch the file number */
        value = get_symbol_annotation(s);
      } else {
        /* the file hasn't been assigned a value */      
        value = malloc(sizeof(int));
        *value = file_id++;
        s = add_symbol(file_table, aux->_aux_symbol._aux_file.filename);
        annotate_symbol(s, value);
      }
      symbol->number = *value;
    }
    symbol = symbol->next;
  }

  /* destory the table */
  file_table = pop_symbol_table(file_table);

  return;
}

/*
 * init_cod - initialize the cod file
 */

void
cod_init(void)
{

  if (state.codfile != named) {
    strncpy(state.codfilename, state.basefilename, sizeof(state.codfilename));
    strncat(state.codfilename, ".cod", sizeof(state.codfilename));
  }

  if ((gp_num_errors) || (state.codfile == suppress)) {
    state.cod.f = NULL;
    state.cod.enabled = false;
    unlink(state.codfilename);
  } else {
    state.cod.f = fopen(state.codfilename, "wb");
    if (state.cod.f == NULL) {
      perror(state.codfilename);
      exit(1);
    }
    state.cod.enabled = true;
  }

  if(!state.cod.enabled)
    return;

  init_DirBlock(&main_dir);
  assert(main_dir.dir.block_number == 0);

  fseek(state.cod.f, COD_BLOCK_SIZE, SEEK_SET);

  assign_file_id();

}

/*
 * write_cod_block - write a cod block to the .cod file and adjust
 * the cod block ptrs in the directory block.
 */

static void
write_cod_block(DirBlockInfo *dbp, 
                int block_ptr_start, 
                int block_ptr_end, 
                Block *bptr)
{

  /* most of the cod blocks have a 'start' and 'end' pointer in the
   * directory block. These pointers are 16 bits wide. If the start
   * pointer is zero, then this is the first time a block of this
   * type has been written. In this case, the block pointer is written  
   * to both the start and end pointer locations. 
   */

  if(!gp_getl16(&dbp->dir.block[block_ptr_start]))
    gp_putl16(&dbp->dir.block[block_ptr_start], bptr->block_number);

  gp_putl16(&dbp->dir.block[block_ptr_end], bptr->block_number);

  fseek(state.cod.f, COD_BLOCK_SIZE*bptr->block_number, SEEK_SET);
  fwrite(bptr->block, 1, COD_BLOCK_SIZE, state.cod.f);

}

/*
 * write_file_block - write a code block that contains a list of the
 * source files.
 */
static void
write_file_block(void)
{
  gp_symbol_type *symbol;
  Block fb;
#define FILES_PER_BLOCK COD_BLOCK_SIZE/COD_FILE_SIZE
  int id_number = 0;
  int file_id = 0;

  gp_cod_create(&fb, &blocks);

  symbol = state.object->symbols;
  while(symbol != NULL) {
    if ((symbol->class == C_FILE) && (symbol->number == file_id)) {
      /* skip the duplicate file symbols */
      file_id++;

      /* The file id is used to define the index at which the file
       * name is written within the file code block. (The id's are
       * sequentially assigned when the files are opened.) If there
       * are too many files, then gpasm will abort. note: .cod files 
       * can handle larger file lists...
       */

      gp_cod_strncpy(&fb.block[1 + COD_FILE_SIZE * id_number],
                     symbol->aux_list->_aux_symbol._aux_file.filename,
                     COD_FILE_SIZE-1);

      id_number++;

      if(id_number >= FILES_PER_BLOCK) {
        write_cod_block(&main_dir, COD_DIR_NAMTAB, COD_DIR_NAMTAB+2, &fb);
        id_number = 0;
        gp_cod_next(&fb, &blocks);
      }
    }
    
    symbol = symbol->next;

  }

  if(id_number)
    write_cod_block(&main_dir, COD_DIR_NAMTAB, COD_DIR_NAMTAB+2, &fb);

  gp_cod_delete(&fb);

}

/* cod_lst_line - add a line of information that cross references the
 * the opcode's address, the source file, and the list file.
 */

void
cod_lst_line(int line_type)
{
  unsigned char smod_flag = 0xff;
  static int first_time = 1;
  static Block lb={NULL,0};
  
  int offset;

  if(!state.cod.enabled)
    return;

  switch(line_type) {
  case COD_FIRST_LST_LINE:
  case COD_NORMAL_LST_LINE:

    /* If we don't have a block yet then create one: */
    if(lb.block == NULL)
      gp_cod_create(&lb, &blocks);

    if(cod_lst_line_number >= COD_MAX_LINE_SYM) {
      write_cod_block(&main_dir, COD_DIR_LSTTAB, COD_DIR_LSTTAB+2, &lb);
      cod_lst_line_number = 0;
      gp_cod_next(&lb, &blocks);
    }

    offset = cod_lst_line_number++ * COD_LINE_SYM_SIZE;
    assert(state.lst.src != NULL);
    assert(state.lst.src->symbol != NULL);
    lb.block[offset + COD_LS_SFILE] = state.lst.src->symbol->number;

    if(state.cod.emitting != 0)
      smod_flag = 0x080;
    else
      smod_flag = 0x90;

    if(first_time != 0) {
      first_time = 0;
      smod_flag  = 0xff;
    }

    lb.block[offset + COD_LS_SMOD] = smod_flag;

    /* Write the source file line number corresponding to the list file line 
       number */
    gp_putl16(&lb.block[offset + COD_LS_SLINE], state.lst.src->line_number);

    /* Write the address of the opcode. */
    gp_putl16(&lb.block[offset + COD_LS_SLOC],
              state.lst.was_org << state.byte_addr);

    break;
  case COD_LAST_LST_LINE:
    if(lb.block) {
      write_cod_block(&main_dir, COD_DIR_LSTTAB, COD_DIR_LSTTAB+2, &lb);
      gp_cod_delete(&lb);
    }

    break;
  default:
    assert(0);
  }

}

/* cod_write_symbols - write the symbol table to the .cod file
 *
 * This routine will read the symbol table that gplink has created
 * and convert it into a format suitable for .cod files. So far, only
 * three variable types are supported: address, register, and constants.
 *
 */

void
cod_write_symbols(struct symbol **symbol_list, int num_symbols)
{
  /* Each symbol is written as a dynamically sized structure to the 
   * .cod file. Its format is like this:
   * position  0               length of the symbol name
   * positions 1 to len        the symbol name
   * positions len+1 & len+2   Type of symbol (16 bits)
   * positions len+3 to len+7  Value of symbol
   */
#define COD_SYM_TYPE  1   /* type info is 1 byte after the length */
#define COD_SYM_VALUE 3   /* value info is 3 bytes after the length */
#define COD_SYM_EXTRA 7   /* symbol name length + 7 is total structure size */
#define MAX_SYM_LEN   255 /* Maximum length of a symbol name */

  int i,offset,len,type;
  gp_coffsymbol_type *var;
  char * s;
  Block sb;

  if(!state.cod.enabled)
    return;

  gp_cod_create(&sb, &blocks);

  offset = 0;

  for(i=0; i<num_symbols; i++) {
    var = get_symbol_annotation(symbol_list[i]);
    s = get_symbol_name(symbol_list[i]);
    len = strlen(s);

    /* If this symbol extends past the end of the cod block
     * then write this block out */

    if((offset + len + COD_SYM_EXTRA) >= COD_BLOCK_SIZE) {
      write_cod_block(&main_dir, COD_DIR_LSYMTAB, COD_DIR_LSYMTAB+2, &sb);
      gp_cod_next(&sb, &blocks);
      offset = 0;
    }

    gp_cod_strncpy(&sb.block[offset +1], s, MAX_SYM_LEN);

    assert(var->symbol != NULL);
    assert(var->symbol->section != NULL);

    if (var->symbol->section->flags & STYP_TEXT) {
      type = COD_ST_ADDRESS;
    } else if (var->symbol->section->flags & STYP_DATA) {
      type = COD_ST_C_SHORT;
    } else if (var->symbol->section->flags & STYP_BSS) {
      type = COD_ST_C_SHORT;
    } else {
      type = COD_ST_CONSTANT;
    }

    gp_putl16(&sb.block[offset+len+COD_SYM_TYPE], type);

    /* write 32 bits, big endian */
    gp_putb32(&sb.block[offset+len+COD_SYM_VALUE], var->symbol->value);
    offset += (len+COD_SYM_EXTRA);
  }

  if(offset)
    write_cod_block(&main_dir, COD_DIR_LSYMTAB, COD_DIR_LSYMTAB+2, &sb);

  gp_cod_delete(&sb);
}

/* cod_emit_opcode - write one opcode to a cod_image_block
 */
static void
cod_emit_opcode(int address,int opcode)
{
  DirBlockInfo *dbi;
  int block_index;
  int found;
  int _64k_base;
  char * block;

  if(!state.cod.enabled)
    return;

  /* The code image blocks are handled in a different manner than the
   * other cod blocks. In theory, it's possible to emit opcodes in a
   * non-sequential manner. Furthermore, it's possible that there may
   * be gaps in the program memory. These cases are handled by an array
   * of code blocks. The lower 8 bits of the opcode's address form an
   * index into the code block, while bits 9-15 are an index into the
   * array of code blocks. The code image blocks are not written until
   * all of the opcodes have been emitted.
   */

  block_index = (address >> (COD_BLOCK_BITS-1)) & (COD_CODE_IMAGE_BLOCKS -1); 
  _64k_base = (address >> 15) & 0xffff;


  dbi = &main_dir;

  /* find the directory containing this 64k segment */
  found = 0;
  do {
    if(gp_getl16(&dbi->dir.block[COD_DIR_HIGHADDR]) == _64k_base)
      found = 1;
    else {
    
      /* If the next directory block (in the linked list of directory
         blocks) is NULL, then this is the first time to encounter this
         _64k segment. So we need to create a new segment. */
      if(dbi->next_dir_block_info == NULL) {
	dbi->next_dir_block_info  = malloc(sizeof(DirBlockInfo));
	init_DirBlock(dbi->next_dir_block_info);
	gp_putl16(&dbi->dir.block[COD_DIR_NEXTDIR], 
		  dbi->next_dir_block_info->dir.block_number);
	gp_putl16(&dbi->next_dir_block_info->dir.block[COD_DIR_HIGHADDR], 
		  _64k_base);
	found = 1;
      }

      dbi = dbi->next_dir_block_info;
    }
  }
  while(!found);

  if(dbi->cod_image_blocks[block_index].block == NULL) {
    gp_cod_create(&dbi->cod_image_blocks[block_index], &blocks);
  }

  block = dbi->cod_image_blocks[block_index].block;

  gp_putl16(&block[(address*2) & (COD_BLOCK_SIZE - 1)], opcode);

}

static void
write_cod_range_block(unsigned int address, Block *rb)
{

  DirBlockInfo *dbi = &main_dir;
  unsigned int _64k_base;

  _64k_base = (address >> 15) & 0xffff;

  do {

    if(gp_getl16(&dbi->dir.block[COD_DIR_HIGHADDR]) == _64k_base) {
      write_cod_block(dbi, COD_DIR_MEMMAP, COD_DIR_MEMMAP+2, rb);
      return;
    }
    dbi = dbi->next_dir_block_info;

  }  while(dbi);

  assert(0);

}

/* cod_write_code - write all of the assembled pic code to the .cod file
 */
static void
cod_write_code(void)
{
  MemBlock *m = state.i_memory;
  int mem_base;

  int i,offset;
  int start_address = 0, used_flag = 0;
  DirBlockInfo *dbi;
  Block rb = {NULL, 0};

  offset = 0;

  while(m) {
    mem_base = m->base << I_MEM_BITS;

    for(i=mem_base; (i-mem_base) <= MAX_I_MEM; i++) {
      if ((i_memory_get(state.i_memory, i) & MEM_USED_MASK) &&
          ((i-mem_base) < MAX_I_MEM)) {
	cod_emit_opcode(i, i_memory_get(state.i_memory, i) & 0xffff);
	if(used_flag == 0) {
          /* Save the start address in a range of opcodes */
          start_address = i;
	  used_flag = 1;
	  if(rb.block == NULL) {
	    gp_cod_create(&rb, &blocks);
	  }
	}
      } 
      else {

	/* No code at address i, but we need to check if this is the
	   first empty address after a range of address. */
	if(used_flag == 1) {

	  /* We need to update dir map indicating a range of memory that
	     is needed. This is done by writing the start and end address to
	     the directory map. */
	  gp_putl16(&rb.block[offset], 2*start_address);
	  gp_putl16(&rb.block[offset+2], 2*(i-1) + 1);

	  offset += 4;
	  if(offset>=COD_BLOCK_SIZE) {
	    /* If there are a whole bunch of non-contiguous pieces of 
	       code then we'll get here. But most pic apps will only need
	       one directory block (that will give you 64 ranges or non-
	       contiguous chunks of pic code). */
	    write_cod_range_block(start_address, &rb);
	    gp_cod_delete(&rb);
	    offset = 0;
	  }
	  used_flag = 0;
	}
      }
    }

    if(offset) {
      write_cod_range_block(start_address, &rb);
      gp_cod_delete(&rb);
      offset = 0;
      used_flag = 0;
    }

    m = m->next;
  }

  /* write the code image blocks */

  dbi = &main_dir;
  do {
    for(i=0; i<COD_CODE_IMAGE_BLOCKS; i++)
      if(dbi->cod_image_blocks[i].block) {
	write_cod_block(dbi,
                        COD_DIR_CODE+i*2,
                        COD_DIR_CODE+i*2,
                        &dbi->cod_image_blocks[i]);
	free(dbi->cod_image_blocks[i].block);
      }
    dbi = dbi->next_dir_block_info;
  } while(dbi);

}

/* cod_write_debug - write debug symbols to the .cod file
 */
static void
cod_write_debug(void)
{
  /* Each symbol is written as a dynamically sized structure to the 
   * .cod file. Its format is like this:
   * position  0               length of the symbol name
   * positions 1 to len        the symbol name
   * positions len+1 & len+2   Type of symbol (16 bits)
   * positions len+3 to len+7  Value of symbol
   */
#define COD_DEBUG_ADDR  0   /* type info is first */
#define COD_DEBUG_CMD   4   /* value info is 4 bytes after the address */
#define COD_DEBUG_MSG   6   /* message is 6 bytes after the address */
#define COD_DEBUG_EXTRA 6   /* symbol name length + 6 is total structure size */
#define MAX_STRING_LEN  255 /* Maximum length of a debug message */
  int offset;
  int len;
  gp_symbol_type *symbol;
  gp_aux_type *aux;
  Block db;
  char command;
  char *string;
  
  if(!state.cod.enabled)
    return;

  gp_cod_create(&db, &blocks);

  offset = 0;

  symbol = state.object->symbols;
  
  while (symbol) {
    if (strcasecmp(".direct", symbol->name) == 0) {
      assert(symbol->num_auxsym == 1);
      aux = symbol->aux_list;
      assert(aux != NULL);

      command = aux->_aux_symbol._aux_direct.command;
      string = aux->_aux_symbol._aux_direct.string;
      
      len = strlen(string);

      /* If this message extends past the end of the cod block
       * then write this block out */

      if((offset + len + COD_DEBUG_EXTRA) >= COD_BLOCK_SIZE) {
        write_cod_block(&main_dir, COD_DIR_MESSTAB, COD_DIR_MESSTAB+2, &db);
        gp_cod_next(&db, &blocks);
        offset = 0;
      }

      /* write 32 bits, big endian */
      gp_putb32(&db.block[offset+COD_DEBUG_ADDR], symbol->value);
      
      db.block[offset + COD_DEBUG_CMD] = command;
      gp_cod_strncpy(&db.block[offset + COD_DEBUG_MSG], string, MAX_STRING_LEN);


      offset += (len + COD_DEBUG_EXTRA);
    } 
    symbol = symbol->next;  
  }

  if(offset)
    write_cod_block(&main_dir, COD_DIR_MESSTAB, COD_DIR_MESSTAB+2, &db);

  gp_cod_delete(&db);

}

static void
write_directory(void)
{
  DirBlockInfo *dbi;
  dbi = &main_dir;

  do {
    fseek(state.cod.f,COD_BLOCK_SIZE * dbi->dir.block_number, SEEK_SET);
    fwrite(dbi->dir.block, 1, COD_BLOCK_SIZE, state.cod.f);

    dbi = dbi->next_dir_block_info;
  } while(dbi);
}

static void
cod_symbol_table(struct symbol_table *table)
{
  int i;
  struct symbol **sym, **ps, *s;

  ps = sym = malloc(table->count * sizeof(sym[0]));

  for (i = 0; i < HASH_SIZE; i++)
    for (s = table->hash_table[i]; s; s = s->next) 
      *ps++ = s;

  assert(ps == &sym[table->count]);

  qsort(sym, table->count, sizeof(sym[0]), symbol_compare);

  cod_write_symbols(sym, table->count);
}

void
cod_close_file(void)
{

  if(!state.cod.enabled)
    return;

  cod_lst_line(COD_LAST_LST_LINE);

  /* All the global symbols are written.  Need to figure out what to do about
     the local symbols. */
  cod_symbol_table(state.symbol.definition);

  write_file_block();

  cod_write_code();

  cod_write_debug();

  gp_cod_strncpy(&main_dir.dir.block[COD_DIR_PROCESSOR], 
	         gp_processor_name(state.processor, 2),
	         COD_DIR_LSYMTAB - COD_DIR_PROCESSOR);

  write_directory();
  fclose(state.cod.f);
  free(main_dir.dir.block);
}


syntax highlighted by Code2HTML, v. 0.9.1