/* output_srec.c Motorola S-record output driver for vasm
 (c) in 2015 by Joseph Zatarski
 
 jzatar2@illinois.edu
 
NOTE: this file makes assumptions that your character set is at least ASCII
compatible. That is, the characters 0-9, A-F, and S are the same for your
character set as in ASCII, and your newline character at least contains CR.
If your character set does not meet these requirements, then the S record will
probably look correct if it is displayed using your character set, but will
likely need to be converted to ASCII to use it elsewhere.
*/

#include "vasm.h"

#ifdef OUTSREC
static char *copyright="vasm motorola srecord output module 1.0 (c) 2015 Joseph Zatarski";

static uint8_t data[32];  /* acts as a buffer for data portion of a record */
static size_t data_size;  /* indicates current size of data[] */

/*
 * holds address for the current record
 * should only be changed after writing the current record by calling
 * write_data_buffer (which will also change srec_pc automatically after writing
 * a record, based on the size of the record just written)
 */
static unsigned long long srec_pc;

/*
 * holds address which is updated for each byte written, rather than updated
 * after writing each record. This is needed for addralign() for example.
 */
static unsigned long long pc;

/* holds address for the start record
 * default of 0 is OK for not having a start address */
static unsigned long long start_addr = 0;

#define S19 1
#define S28 2
#define S37 3
static int srecfmt = S37;

static char *start_sym; /* points to name of the start symbol, or NULL if not
                           using the execution address in the termination
                           record */

static char *default_start="start"; /* name of default execution address symbol
                                       for termination record */

static void write_hex_byte(FILE *f, uint8_t byte)
/* write a pair of ASCII characters to represent the byte in hex */
{
  uint8_t temp;
  
  temp = ((byte & 0xf0) >> 4) + '0';  /* adds offset for character '0' */
  if(temp > '9')  /* if the nibble isn't 0-9 */
    temp += 'A' - '9' - 1; /* then we convert to A-F */
  
  fw8(f,temp);
  
  temp = (byte & 0x0f) + '0';  /* see above */
  if(temp > '9')
    temp += 'A' - '9' - 1;
  
  fw8(f,temp);
}

static void write_data_buffer(FILE *f, uint8_t type)
/*
 * writes the data buffer to the output file in a record
 * then updates srec_pc for next record
 * accepts type for number of srecord desired. Currently ignores unsupported
 * types, although that should never happen.
 */
{
  uint8_t checksum = 0;
  int i;
  
  char line_end[] = "\n";
  
  if(data_size == 0 && type != 0) /* allow S0 record to have data size of 0 */
    return; /* nothing to write */

  /* check if address is out of range for this record type and error */
  if(type > 0 && ((srec_pc >> ((type + 1) * 8)) != 0))
    output_error(11,srec_pc);
  
  if(type <= 3)
  {
    fw8(f, 'S');
    fw8(f, type + '0');
  }
  
  else
    return; /* ignore types we don't handle, but this shouldn't ever happen */
  
  if(type == 3)
  {
    write_hex_byte(f, data_size + 5); /* count: 4 bytes for address + checksum */
    checksum += data_size + 5;
  
  }

  else if(type == 2)
  {
    write_hex_byte(f, data_size + 4); /* count: 3 bytes for address + checksum */
    checksum += data_size + 4;
  }
  
  else if(type <= 1) /* type is uint, so no need to check if <0 */
  {
    write_hex_byte(f, data_size + 3); /* count: 2 bytes for address + checksum */
    checksum += data_size + 3;
  }
  
  if(type > 2)
  {
    write_hex_byte(f, (srec_pc & 0xff000000) >> 24);
    checksum += (srec_pc & 0xff000000) >> 24;
  }
  
  if(type > 1)
  {
    write_hex_byte(f, (srec_pc & 0xff0000) >> 16);
    checksum += (srec_pc & 0xff0000) >> 16;
  }
  
  if(type > 0)
  {
    write_hex_byte(f, (srec_pc & 0xff00) >> 8);
    checksum += (srec_pc & 0xff00) >> 8;
    write_hex_byte(f, srec_pc & 0xff);
    checksum += srec_pc & 0xff;
  }
  
  else /* type must be 0 */
  {
    write_hex_byte(f, 0);
    write_hex_byte(f, 0);
  }
  
  for(i=0; i < data_size; i++)
  {
    write_hex_byte(f, data[i]);
    checksum += data[i];
  }
  
  write_hex_byte(f, checksum ^ 0xff);
  
  for(i = 0; line_end[i] != '\0'; i++)
    fw8(f, line_end[i]);
  
  srec_pc += data_size;
  data_size = 0;
}

static void write_termination_record(FILE *f)
/* writes termination record, S7/8/9 depending on whether we're in S19/S28/S37
 * mode */
{
  uint8_t checksum = 0;
  
  char line_end[] = "\n";
  
  int i;
  
  /* check if address is out of range for this record type and error */
  if(srecfmt > 0 && ((start_addr >> ((srecfmt + 1) * 8)) != 0))
    output_error(11, start_addr);
  
  fw8(f, 'S');
  fw8(f, (10 - srecfmt) + '0'); /* writes S header depending on S19/S28/S37 */
  
  if(srecfmt == S37)
  {
    write_hex_byte(f, 5); /* count: 4 bytes for address + checksum */
    checksum += 5;
  
  }

  else if(srecfmt == S28)
  {
    write_hex_byte(f, 4); /* count: 3 bytes for address + checksum */
    checksum += 4;
  }
  
  else if(srecfmt == S19)
  {
    write_hex_byte(f, 3); /* count: 2 bytes for address + checksum */
    checksum += 3;
  }
  
  if(srecfmt >= S37)
  {
    write_hex_byte(f, (start_addr & 0xff000000) >> 24);
    checksum += (start_addr & 0xff000000) >> 24;
  }
  
  if(srecfmt >= S28)
  {
    write_hex_byte(f, (start_addr & 0xff0000) >> 16);
    checksum += (start_addr & 0xff0000) >> 16;
  }
  
  write_hex_byte(f, (start_addr & 0xff00) >> 8);
  checksum += (start_addr & 0xff00) >> 8;
  write_hex_byte(f, start_addr & 0xff);
  checksum += start_addr & 0xff;
  
  write_hex_byte(f, checksum ^ 0xff);
  
  for(i = 0; line_end[i] != '\0'; i++)
    fw8(f, line_end[i]);
}

static void put_byte_in_buffer(FILE *f, uint8_t byte)
/* puts a byte in the data buffer and flushes the buffer if we fill it up */
/* also updates pc */
{
  data[data_size] = byte;
  data_size++;
  
  if(data_size >= 32)
    write_data_buffer(f, srecfmt);
    
  pc++;
}

static void addralign(FILE *f,atom *a,section *sec)
/* modified from fwpcalign() in supp.c */
{
  int align_warning = 0;
  taddr n = balign(pc,a->align);
  taddr patlen;
  uint8_t *pat;

  if (n == 0)
    return;

  if (a->type==SPACE && a->content.sb->space==0) {  /* space align atom */
    if (a->content.sb->maxalignbytes!=0 &&  n>a->content.sb->maxalignbytes)
      return;
    pat = a->content.sb->fill;
    patlen = a->content.sb->size;
  }
  else {
    pat = sec->pad;
    patlen = sec->padbytes;
  }

  /*pc += n; */ /*done in put_byte_in_buffer()*/

  while (n % patlen) {
    if (!align_warning) {
      align_warning = 1;
      /*output_error(9,sec->name,(unsigned long)n,(unsigned long)patlen,
                   ULLTADDR(pc));*/
    }
    put_byte_in_buffer(f,0);
    n--;
  }

  /* write alignment pattern */
  while (n >= patlen) {
    int i;
    for(i = 0; i < patlen; i++)
    {
      put_byte_in_buffer(f, *(pat + i));
    }
    n -= patlen;
  }

  while (n--) {
    if (!align_warning) {
      align_warning = 1;
      /*output_error(9,sec->name,(unsigned long)n,(unsigned long)patlen,
                   ULLTADDR(pc));*/
    }
    put_byte_in_buffer(f, 0);
  }
}

static void write_output(FILE *f,section *sec,symbol *sym)
{
  section *s,*s2,**seclist,**slp;
  atom *p;
  size_t nsecs;
  unsigned long long i, j;

  if (!sec)
    return;

  for (; sym; sym=sym->next) {
    if (sym->type == IMPORT)
      output_error(6,sym->name);  /* undefined symbol */
    if (start_sym != NULL && !strcmp(sym->name, start_sym)) /* look for start symbol */
    {
      start_addr = sym->pc;
      start_sym = NULL; /* we use this to indicate that we found the symbol */
    }
  }
  
  if (start_sym != NULL) /* if start_name is not NULL, then it means we set it
                             but we were unable to find it */
    output_error(6, start_sym);

  for (s=sec; s!=NULL; s=s->next)	/* iterate through sections */
  {
    for (data_size = 0; (*(s->name + data_size) != '\0') && (data_size < 32); data_size++)
    /* loop loads name of section into data and sets data_size properly */
    {
      data[data_size] = *(s->name + data_size);
    }
    write_data_buffer(f, 0); /* record type is S0 for header */
    
    pc = ULLTADDR(s->org);	/* start at the org address */
    srec_pc = pc;		/* need to update both */
    for (p=s->first; p; p=p->next)	/* iterate through atoms */
    {
      addralign(f,p,s);
      if(p->type == DATA)
        for (i = 0; i < p->content.db->size; i++)
          put_byte_in_buffer(f,(uint8_t)p->content.db->data[i]);
      else if (p->type == SPACE)
      {
        for (i = 0; i < p->content.sb->space; i++)
        {
          for (j = 0; j < p->content.sb->size; j++)
          {
            put_byte_in_buffer(f,p->content.sb->fill[j]);
          }
        }
      } 
    }
    
    write_data_buffer(f, srecfmt); /* now that we're done iterating through atoms */
    /* flush buffer before moving on to next section */
  }
  
  /* after all sections finished, write terminating record */
  write_termination_record(f);
}


static int output_args(char *p)
{
  if (!strcmp(p, "-s19"))
  {
    srecfmt = S19;
    return 1;
  }
  else if (!strcmp(p, "-s28"))
  {
    srecfmt = S28;
    return 1;
  }
  else if (!strcmp(p, "-s37"))
  {
    srecfmt = S37;
    return 1;
  }
  
  else if (!strcmp(p, "-exec"))
  {
    start_sym = default_start;
    return 1;
  }
  
  else if (!strncmp(p, "-exec=", 6))
  {
    start_sym = p + 6;
    return 1;
  }
  return 0;	
}


int init_output_srec(char **cp,void (**wo)(FILE *,section *,symbol *),int (**oa)(char *))
{
  *cp = copyright;
  *wo = write_output;
  *oa = output_args;
  return 1;
}

#else

int init_output_srec(char **cp,void (**wo)(FILE *,section *,symbol *),int (**oa)(char *))
{
  return 0;
}
#endif
