1 /* $NetBSD: haudfw.c,v 1.1 2026/06/11 08:16:44 rumble Exp $ */ 2 3 /* 4 * Copyright (c) 2025 Stephen M. Rumble <rumble (at) ephemeral.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * This program facilitates loading the SGI Indigo's "Hollywood Audio" Motorola 21 * 56K DSP firmware by translating a loadable text file into a binary blob. 22 * 23 * The input is a .lod text file and the output is a 128KiB binary that the 24 * sgimips/haud(4) driver can copy directly into the DSP's SRAM at byte offset 25 * zero. 26 * 27 * The DSP's memory space is provided by three 8-bit 32KiB SRAMs, which are 28 * combined to provide 32768 24-bit DSP words. On the CPU side, this memory is 29 * mapped onto 32768 contiguous 32-bit words via the HPC chip. 30 * 31 * The DSP's X, Y, and P address spaces map on to different subsets of the SRAM 32 * memory space. The .lod file specifies where words are loaded into each DSP 33 * address space. These addresses need to be translated into offsets into the 34 * raw SRAM mapped into the CPU's address space in order to load code and data 35 * in the correct locations. 36 * 37 * Mappings between the DSP addresses and CPU/SRAM addresses were determined by 38 * using a test .lod file and IRIX's /usr/etc/audio/dsploader. Dumps of the SRAM 39 * showed where the utility wrote test various patterns defined in the test 40 * file's fabricated _DATA entries. The results are as follows below and appear 41 * to indicate that the DSP is configured in mode 2 ("normal expanded mode"), 42 * with 0xe000 as the post-reset start address. The ranges are inclusive. 43 * 44 * Section DSP Word Addr CPU Word Addr 45 * P 0xd000 - 0xefff 0x1000 - 0x2fff 46 * X 0xb000 - 0xbfff 0x7000 - 0x7fff 47 * X 0xc000 - 0xcfff 0x0000 - 0x0fff 48 * Y 0xb000 - 0xefff 0x3000 - 0x6fff 49 * 50 * Note that the above CPU addresses are 32-bit word offsets from the HPC's 51 * SRAM base address. So multiply by 4 to get the CPU byte address offset. 52 */ 53 54 #include <err.h> 55 #include <errno.h> 56 #include <limits.h> 57 #include <stdarg.h> 58 #include <stdbool.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <unistd.h> 63 #include <arpa/inet.h> 64 65 // Dump a C array representing the data to load into SRAM to stdout. 66 bool c_output = false; 67 68 // Larger values > 0 increase debugging output to stderr. 69 int verbosity = 0; 70 71 const char whitespace[] = " \t\r\n"; 72 73 static void 74 debug_printf(int level, const char *format, ...) 75 { 76 if (verbosity >= level) { 77 va_list ap; 78 va_start(ap, format); 79 vfprintf(stderr, format, ap); 80 va_end(ap); 81 } 82 } 83 84 static void 85 check(bool constraint, const char *format, ...) 86 { 87 if (!constraint) { 88 va_list ap; 89 va_start(ap, format); 90 fprintf(stderr, "error: "); 91 verrx(1, format, ap); 92 va_end(ap); 93 } 94 } 95 96 __attribute__((noreturn)) 97 static void 98 fail(const char *format, ...) 99 { 100 va_list ap; 101 va_start(ap, format); 102 check(false, format, ap); 103 // Never hit. exit() squelches warning. 104 va_end(ap); 105 exit(1); 106 } 107 108 static void 109 usage(void) 110 { 111 fprintf(stderr, "usage: %s [-cv] input.lod output.bin\n", 112 getprogname()); 113 exit(1); 114 } 115 116 static int 117 dsp_to_cpu_address(char section, int dsp_addr) 118 { 119 switch (section) { 120 case 'P': 121 check(0xd000 <= dsp_addr && dsp_addr <= 0xefff, 122 "P section dsp_addr 0x%04x not in [0xd000,0xefff]", 123 dsp_addr); 124 return dsp_addr - 0xc000; 125 case 'X': 126 if (0xb000 <= dsp_addr && dsp_addr <= 0xbfff) { 127 return dsp_addr - 0x4000; 128 } else if (0xc000 <= dsp_addr && dsp_addr <= 0xcfff) { 129 return dsp_addr - 0xc000; 130 } else { 131 fail("X section dsp_addr 0x%04x not in either " 132 "[0xb000,0xbfff] or [0xc000,0xcfff]", dsp_addr); 133 } 134 case 'Y': 135 check(0xb000 <= dsp_addr && dsp_addr <= 0xefff, 136 "Y section dsp_addr 0x%04x not in [0xb000,0xefff)", 137 dsp_addr); 138 return dsp_addr - 0x8000; 139 default: 140 fail("unknown section type '%c'", section); 141 } 142 } 143 144 static void 145 parse_data_line(const char *line, char *section, int *dsp_data_offset, 146 int* section_data_count) { 147 char *line_copy = strdup(line); 148 char *field = strtok(line_copy, whitespace); 149 for (int i = 0; field != NULL; i++, field = strtok(NULL, whitespace)) { 150 switch (i) { 151 case 0: 152 check(strcmp(field, "_DATA") == 0, 153 "unexpected _DATA header: \"%s\"", field); 154 break; 155 case 1: 156 *section = field[0]; 157 check(strlen(field) == 1 && strspn(field, "XYPL") == 1, 158 "unexpected section type: \"%s\"", field); 159 break; 160 case 2: 161 *dsp_data_offset = strtol(field, NULL, 16); 162 break; 163 default: 164 fail("too many fields in _DATA line: \"%s\", " 165 "expected 3", line); 166 } 167 } 168 *section_data_count = 0; 169 free(line_copy); 170 } 171 172 int 173 main(int argc, char **argv) 174 { 175 int ch; 176 while ((ch = getopt(argc, argv, "cv")) != -1) { 177 switch (ch) { 178 case 'c': 179 c_output = true; 180 break; 181 case 'v': 182 verbosity++; 183 break; 184 case '?': 185 default: 186 usage(); 187 } 188 } 189 argv += optind; 190 argc -= optind; 191 if (argc != 2) { 192 usage(); 193 } 194 195 check(strcmp(argv[0], argv[1]) != 0, 196 "input and output files must be different"); 197 198 const int firmware_words = 32768; 199 u_int32_t *firmware = malloc(firmware_words * sizeof(u_int32_t)); 200 check(firmware != NULL, "unable to malloc buffer for firmware: %s", 201 strerror(errno)); 202 memset(firmware, 0, firmware_words * sizeof(u_int32_t)); 203 204 FILE *fp = fopen(argv[0], "r"); 205 check(fp != NULL, "unable to open input file %s: %s", 206 argv[0], strerror(errno)); 207 208 int dsp_data_offset = 0; 209 int section_data_count = 0; 210 char section = '\0'; 211 char line[100]; 212 for (int lineno = 1; fgets(line, sizeof(line), fp) != NULL; lineno++) { 213 if (strspn(line, whitespace) == strlen(line)) { 214 // skip blank lines 215 } else if (strncmp(line, "_START", 6) == 0) { 216 debug_printf(1, "_START on line %d\n", lineno); 217 } else if (strncmp(line, "_END", 4) == 0) { 218 if (section != '\0') { 219 debug_printf(1, "%c section had %d words\n", 220 section, section_data_count); 221 } 222 debug_printf(1, "_END on line %d\n", lineno); 223 break; 224 } else if (strncmp(line, "_DATA", 5) == 0) { 225 if (section != '\0') { 226 debug_printf(1, "%c section had %d words\n", 227 section, section_data_count); 228 } 229 debug_printf(1, "_DATA on line %d\n", lineno); 230 parse_data_line(line, §ion, &dsp_data_offset, 231 §ion_data_count); 232 debug_printf(1, "%c data section starting at dsp " 233 "offset 0x%04x\n", section, dsp_data_offset); 234 } else { 235 // We must be in a _DATA section 236 check(section != '\0', "parse error on line %d: not in " 237 "a _DATA section", lineno); 238 239 char *word_str = strtok(line, whitespace); 240 while (word_str != NULL) { 241 u_int32_t word = strtol(word_str, NULL, 16); 242 243 int cpu_offset = dsp_to_cpu_address( 244 section != 'L' ? section : ( 245 section_data_count % 2 ? 'Y' : 'X'), 246 dsp_data_offset); 247 if (section_data_count == 0 || section == 'L') { 248 debug_printf(1, " cpu offset 0x%04x " 249 " (KSEG1 address 0x%08x)\n", 250 cpu_offset, 251 0xbfbe0000 + cpu_offset * 4); 252 } 253 254 check(cpu_offset >= 0 && 255 cpu_offset < firmware_words, 256 "cpu_offset %d is out of bounds for " 257 "section %c, on line %d", cpu_offset, 258 section, lineno); 259 260 firmware[cpu_offset] = word; 261 262 section_data_count++; 263 dsp_data_offset++; 264 265 word_str = strtok(NULL, whitespace); 266 } 267 } 268 } 269 fclose(fp); 270 271 if (c_output) { 272 printf("// Firmware in big-endian format:\n"); 273 printf("u_int32_t firmware[%d] = {\n", firmware_words); 274 u_int32_t zeroes[16]; 275 memset(zeroes, 0, sizeof(zeroes)); 276 for (int i = 0; i < firmware_words; i += 4) { 277 if (i > 0) { 278 printf(",\n"); 279 } 280 if (i < firmware_words - 16 && 281 memcmp(&firmware[i], zeroes, sizeof(zeroes)) == 0) { 282 // Make empty ranges more compact. 283 printf("\t/* 0x%04x */ 0, 0, 0, 0, 0, 0, 0, " 284 "0, 0, 0, 0, 0, 0, 0, 0, 0", i); 285 i += (16 - 4); 286 } else { 287 printf("\t/* 0x%04x */ 0x%08x, 0x%08x, " 288 "0x%08x, 0x%08x", i, 289 firmware[i+0], firmware[i+1], 290 firmware[i+2], firmware[i+3]); 291 } 292 } 293 printf("\n};\n"); 294 } 295 296 // Output needs to be big-endian, so swap if we're not on a BE machine. 297 for (int i = 0; i < firmware_words; i++) { 298 firmware[i] = htonl(firmware[i]); 299 } 300 301 FILE *fpout = fopen(argv[1], "w"); 302 check(fpout != NULL, "unable to open output file %s: %s", 303 argv[1], strerror(errno)); 304 check(fwrite(firmware, sizeof(u_int32_t), firmware_words, fpout) == 305 firmware_words, "failed to write entire output to file %s: %s", 306 argv[1], strerror(errno)); 307 fclose(fpout); 308 309 return 0; 310 } 311