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