6502/vasm/output_srec.c

395 lines
9.6 KiB
C

/* 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