#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
bool c_output = false;
int verbosity = 0;
const char whitespace[] = " \t\r\n";
static void
debug_printf(int level, const char *format, ...)
{
if (verbosity >= level) {
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
}
}
static void
check(bool constraint, const char *format, ...)
{
if (!constraint) {
va_list ap;
va_start(ap, format);
fprintf(stderr, "error: ");
verrx(1, format, ap);
va_end(ap);
}
}
__attribute__((noreturn))
static void
fail(const char *format, ...)
{
va_list ap;
va_start(ap, format);
check(false, format, ap);
va_end(ap);
exit(1);
}
static void
usage(void)
{
fprintf(stderr, "usage: %s [-cv] input.lod output.bin\n",
getprogname());
exit(1);
}
static int
dsp_to_cpu_address(char section, int dsp_addr)
{
switch (section) {
case 'P':
check(0xd000 <= dsp_addr && dsp_addr <= 0xefff,
"P section dsp_addr 0x%04x not in [0xd000,0xefff]",
dsp_addr);
return dsp_addr - 0xc000;
case 'X':
if (0xb000 <= dsp_addr && dsp_addr <= 0xbfff) {
return dsp_addr - 0x4000;
} else if (0xc000 <= dsp_addr && dsp_addr <= 0xcfff) {
return dsp_addr - 0xc000;
} else {
fail("X section dsp_addr 0x%04x not in either "
"[0xb000,0xbfff] or [0xc000,0xcfff]", dsp_addr);
}
case 'Y':
check(0xb000 <= dsp_addr && dsp_addr <= 0xefff,
"Y section dsp_addr 0x%04x not in [0xb000,0xefff)",
dsp_addr);
return dsp_addr - 0x8000;
default:
fail("unknown section type '%c'", section);
}
}
static void
parse_data_line(const char *line, char *section, int *dsp_data_offset,
int* section_data_count) {
char *line_copy = strdup(line);
char *field = strtok(line_copy, whitespace);
for (int i = 0; field != NULL; i++, field = strtok(NULL, whitespace)) {
switch (i) {
case 0:
check(strcmp(field, "_DATA") == 0,
"unexpected _DATA header: \"%s\"", field);
break;
case 1:
*section = field[0];
check(strlen(field) == 1 && strspn(field, "XYPL") == 1,
"unexpected section type: \"%s\"", field);
break;
case 2:
*dsp_data_offset = strtol(field, NULL, 16);
break;
default:
fail("too many fields in _DATA line: \"%s\", "
"expected 3", line);
}
}
*section_data_count = 0;
free(line_copy);
}
int
main(int argc, char **argv)
{
int ch;
while ((ch = getopt(argc, argv, "cv")) != -1) {
switch (ch) {
case 'c':
c_output = true;
break;
case 'v':
verbosity++;
break;
case '?':
default:
usage();
}
}
argv += optind;
argc -= optind;
if (argc != 2) {
usage();
}
check(strcmp(argv[0], argv[1]) != 0,
"input and output files must be different");
const int firmware_words = 32768;
u_int32_t *firmware = malloc(firmware_words * sizeof(u_int32_t));
check(firmware != NULL, "unable to malloc buffer for firmware: %s",
strerror(errno));
memset(firmware, 0, firmware_words * sizeof(u_int32_t));
FILE *fp = fopen(argv[0], "r");
check(fp != NULL, "unable to open input file %s: %s",
argv[0], strerror(errno));
int dsp_data_offset = 0;
int section_data_count = 0;
char section = '\0';
char line[100];
for (int lineno = 1; fgets(line, sizeof(line), fp) != NULL; lineno++) {
if (strspn(line, whitespace) == strlen(line)) {
} else if (strncmp(line, "_START", 6) == 0) {
debug_printf(1, "_START on line %d\n", lineno);
} else if (strncmp(line, "_END", 4) == 0) {
if (section != '\0') {
debug_printf(1, "%c section had %d words\n",
section, section_data_count);
}
debug_printf(1, "_END on line %d\n", lineno);
break;
} else if (strncmp(line, "_DATA", 5) == 0) {
if (section != '\0') {
debug_printf(1, "%c section had %d words\n",
section, section_data_count);
}
debug_printf(1, "_DATA on line %d\n", lineno);
parse_data_line(line, §ion, &dsp_data_offset,
§ion_data_count);
debug_printf(1, "%c data section starting at dsp "
"offset 0x%04x\n", section, dsp_data_offset);
} else {
check(section != '\0', "parse error on line %d: not in "
"a _DATA section", lineno);
char *word_str = strtok(line, whitespace);
while (word_str != NULL) {
u_int32_t word = strtol(word_str, NULL, 16);
int cpu_offset = dsp_to_cpu_address(
section != 'L' ? section : (
section_data_count % 2 ? 'Y' : 'X'),
dsp_data_offset);
if (section_data_count == 0 || section == 'L') {
debug_printf(1, " cpu offset 0x%04x "
" (KSEG1 address 0x%08x)\n",
cpu_offset,
0xbfbe0000 + cpu_offset * 4);
}
check(cpu_offset >= 0 &&
cpu_offset < firmware_words,
"cpu_offset %d is out of bounds for "
"section %c, on line %d", cpu_offset,
section, lineno);
firmware[cpu_offset] = word;
section_data_count++;
dsp_data_offset++;
word_str = strtok(NULL, whitespace);
}
}
}
fclose(fp);
if (c_output) {
printf("// Firmware in big-endian format:\n");
printf("u_int32_t firmware[%d] = {\n", firmware_words);
u_int32_t zeroes[16];
memset(zeroes, 0, sizeof(zeroes));
for (int i = 0; i < firmware_words; i += 4) {
if (i > 0) {
printf(",\n");
}
if (i < firmware_words - 16 &&
memcmp(&firmware[i], zeroes, sizeof(zeroes)) == 0) {
printf("\t/* 0x%04x */ 0, 0, 0, 0, 0, 0, 0, "
"0, 0, 0, 0, 0, 0, 0, 0, 0", i);
i += (16 - 4);
} else {
printf("\t/* 0x%04x */ 0x%08x, 0x%08x, "
"0x%08x, 0x%08x", i,
firmware[i+0], firmware[i+1],
firmware[i+2], firmware[i+3]);
}
}
printf("\n};\n");
}
for (int i = 0; i < firmware_words; i++) {
firmware[i] = htonl(firmware[i]);
}
FILE *fpout = fopen(argv[1], "w");
check(fpout != NULL, "unable to open output file %s: %s",
argv[1], strerror(errno));
check(fwrite(firmware, sizeof(u_int32_t), firmware_words, fpout) ==
firmware_words, "failed to write entire output to file %s: %s",
argv[1], strerror(errno));
fclose(fpout);
return 0;
}