--- /dev/null
+#include "romfile_loader.h"
+#include "byteorder.h" // leXX_to_cpu/cpu_to_leXX
+#include "util.h" // checksum
+#include "string.h" // strcmp
+#include "romfile.h" // struct romfile_s
+#include "malloc.h" // Zone*, _malloc
+#include "output.h" // warn_*
+
+struct romfile_loader_file {
+ struct romfile_s *file;
+ void *data;
+};
+struct romfile_loader_files {
+ int nfiles;
+ struct romfile_loader_file files[];
+};
+
+static struct romfile_loader_file *
+romfile_loader_find(const char *name,
+ struct romfile_loader_files *files)
+{
+ int i;
+ if (name[ROMFILE_LOADER_FILESZ - 1])
+ return NULL;
+ for (i = 0; i < files->nfiles; ++i)
+ if (!strcmp(files->files[i].file->name, name))
+ return &files->files[i];
+ return NULL;
+}
+
+static void romfile_loader_allocate(struct romfile_loader_entry_s *entry,
+ struct romfile_loader_files *files)
+{
+ struct zone_s *zone;
+ struct romfile_loader_file *file = &files->files[files->nfiles];
+ void *data;
+ int ret;
+ unsigned alloc_align = le32_to_cpu(entry->alloc_align);
+
+ if (alloc_align & (alloc_align - 1))
+ goto err;
+
+ switch (entry->alloc_zone) {
+ case ROMFILE_LOADER_ALLOC_ZONE_HIGH:
+ zone = &ZoneHigh;
+ break;
+ case ROMFILE_LOADER_ALLOC_ZONE_FSEG:
+ zone = &ZoneFSeg;
+ break;
+ default:
+ goto err;
+ }
+ if (alloc_align < MALLOC_MIN_ALIGN)
+ alloc_align = MALLOC_MIN_ALIGN;
+ if (entry->alloc_file[ROMFILE_LOADER_FILESZ - 1])
+ goto err;
+ file->file = romfile_find(entry->alloc_file);
+ if (!file->file || !file->file->size)
+ return;
+ data = _malloc(zone, MALLOC_DEFAULT_HANDLE, file->file->size, alloc_align);
+ if (!data) {
+ warn_noalloc();
+ return;
+ }
+ ret = file->file->copy(file->file, data, file->file->size);
+ if (ret != file->file->size)
+ goto file_err;
+ file->data = data;
+ files->nfiles++;
+ return;
+
+file_err:
+ free(data);
+err:
+ warn_internalerror();
+}
+
+static void romfile_loader_add_pointer(struct romfile_loader_entry_s *entry,
+ struct romfile_loader_files *files)
+{
+ struct romfile_loader_file *dest_file;
+ struct romfile_loader_file *src_file;
+ unsigned offset = le32_to_cpu(entry->pointer_offset);
+ u64 pointer = 0;
+
+ dest_file = romfile_loader_find(entry->pointer_dest_file, files);
+ src_file = romfile_loader_find(entry->pointer_src_file, files);
+
+ if (!dest_file || !src_file || !dest_file->data || !src_file->data ||
+ offset + entry->pointer_size < offset ||
+ offset + entry->pointer_size > dest_file->file->size ||
+ entry->pointer_size < 1 || entry->pointer_size > 8 ||
+ entry->pointer_size & (entry->pointer_size - 1))
+ goto err;
+
+ memcpy(&pointer, dest_file->data + offset, entry->pointer_size);
+ pointer = le64_to_cpu(pointer);
+ pointer += (unsigned long)src_file->data;
+ pointer = cpu_to_le64(pointer);
+ memcpy(dest_file->data + offset, &pointer, entry->pointer_size);
+
+ return;
+err:
+ warn_internalerror();
+}
+
+static void romfile_loader_add_checksum(struct romfile_loader_entry_s *entry,
+ struct romfile_loader_files *files)
+{
+ struct romfile_loader_file *file;
+ unsigned offset = le32_to_cpu(entry->cksum_offset);
+ unsigned start = le32_to_cpu(entry->cksum_start);
+ unsigned len = le32_to_cpu(entry->cksum_length);
+ u8 *data;
+
+ file = romfile_loader_find(entry->cksum_file, files);
+
+ if (!file || !file->data || offset >= file->file->size ||
+ start + len < start || start + len > file->file->size)
+ goto err;
+
+ data = file->data + offset;
+ *data -= checksum(file->data + start, len);
+
+ return;
+err:
+ warn_internalerror();
+}
+
+int romfile_loader_execute(const char *name)
+{
+ struct romfile_loader_entry_s *entry;
+ int size, offset = 0, nfiles;
+ struct romfile_loader_files *files;
+ void *data = romfile_loadfile(name, &size);
+ if (!data)
+ return -1;
+
+ if (size % sizeof(*entry)) {
+ warn_internalerror();
+ goto err;
+ }
+
+ /* (over)estimate the number of files to load. */
+ nfiles = size / sizeof(*entry);
+ files = malloc_tmp(sizeof(*files) + nfiles * sizeof(files->files[0]));
+ if (!files) {
+ warn_noalloc();
+ goto err;
+ }
+ files->nfiles = 0;
+
+ for (offset = 0; offset < size; offset += sizeof(*entry)) {
+ entry = data + offset;
+ switch (le32_to_cpu(entry->command)) {
+ case ROMFILE_LOADER_COMMAND_ALLOCATE:
+ romfile_loader_allocate(entry, files);
+ break;
+ case ROMFILE_LOADER_COMMAND_ADD_POINTER:
+ romfile_loader_add_pointer(entry, files);
+ break;
+ case ROMFILE_LOADER_COMMAND_ADD_CHECKSUM:
+ romfile_loader_add_checksum(entry, files);
+ default:
+ /* Skip commands that we don't recognize. */
+ break;
+ }
+ }
+
+ free(files);
+ free(data);
+ return 0;
+
+err:
+ free(data);
+ return -1;
+}
--- /dev/null
+#ifndef __ROMFILE_LOADER_H
+#define __ROMFILE_LOADER_H
+
+#include "types.h" // u8
+#include "util.h" // romfile_s
+
+#define ROMFILE_LOADER_FILESZ 56
+
+/* ROM file linker/loader interface. Linker uses little endian format */
+struct romfile_loader_entry_s {
+ u32 command;
+ union {
+ /*
+ * COMMAND_ALLOCATE - allocate a table from @alloc_file
+ * subject to @alloc_align alignment (must be power of 2)
+ * and @alloc_zone (can be HIGH or FSEG) requirements.
+ *
+ * Must appear exactly once for each file, and before
+ * this file is referenced by any other command.
+ */
+ struct {
+ char alloc_file[ROMFILE_LOADER_FILESZ];
+ u32 alloc_align;
+ u8 alloc_zone;
+ };
+
+ /*
+ * COMMAND_ADD_POINTER - patch the table (originating from
+ * @dest_file) at @pointer_offset, by adding a pointer to the table
+ * originating from @src_file. 1,2,4 or 8 byte unsigned
+ * addition is used depending on @pointer_size.
+ */
+ struct {
+ char pointer_dest_file[ROMFILE_LOADER_FILESZ];
+ char pointer_src_file[ROMFILE_LOADER_FILESZ];
+ u32 pointer_offset;
+ u8 pointer_size;
+ };
+
+ /*
+ * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by
+ * @cksum_start and @cksum_length fields,
+ * and then add the value at @cksum_offset.
+ * Checksum simply sums -X for each byte X in the range
+ * using 8-bit math.
+ */
+ struct {
+ char cksum_file[ROMFILE_LOADER_FILESZ];
+ u32 cksum_offset;
+ u32 cksum_start;
+ u32 cksum_length;
+ };
+
+ /* padding */
+ char pad[124];
+ };
+};
+
+enum {
+ ROMFILE_LOADER_COMMAND_ALLOCATE = 0x1,
+ ROMFILE_LOADER_COMMAND_ADD_POINTER = 0x2,
+ ROMFILE_LOADER_COMMAND_ADD_CHECKSUM = 0x3,
+};
+
+enum {
+ ROMFILE_LOADER_ALLOC_ZONE_HIGH = 0x1,
+ ROMFILE_LOADER_ALLOC_ZONE_FSEG = 0x2,
+};
+
+int romfile_loader_execute(const char *name);
+
+#endif