| #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 "list.h" // struct hlist_node |
| #include "output.h" // warn_* |
| #include "paravirt.h" // qemu_cfg_write_file |
| |
| struct romfile_loader_file { |
| struct romfile_s *file; |
| void *data; |
| }; |
| struct romfile_loader_files { |
| int nfiles; |
| struct romfile_loader_file files[]; |
| }; |
| |
| // Data structures for storing "write pointer" entries for possible replay |
| struct romfile_wr_pointer_entry { |
| u64 pointer; |
| u32 offset; |
| u16 key; |
| u8 ptr_size; |
| struct hlist_node node; |
| }; |
| static struct hlist_head romfile_pointer_list; |
| |
| 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; |
| } |
| |
| // Replay "write pointer" entries back to QEMU |
| void romfile_fw_cfg_resume(void) |
| { |
| if (!CONFIG_QEMU) |
| return; |
| |
| struct romfile_wr_pointer_entry *entry; |
| hlist_for_each_entry(entry, &romfile_pointer_list, node) { |
| qemu_cfg_write_file_simple(&entry->pointer, entry->key, |
| entry->offset, entry->ptr_size); |
| } |
| } |
| |
| 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, 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(); |
| } |
| |
| static void romfile_loader_write_pointer(struct romfile_loader_entry_s *entry, |
| struct romfile_loader_files *files) |
| { |
| struct romfile_s *dest_file; |
| struct romfile_loader_file *src_file; |
| unsigned dst_offset = le32_to_cpu(entry->wr_pointer.dst_offset); |
| unsigned src_offset = le32_to_cpu(entry->wr_pointer.src_offset); |
| u64 pointer = 0; |
| |
| /* Writing back to a file that may not be loaded in RAM */ |
| dest_file = romfile_find(entry->wr_pointer.dest_file); |
| src_file = romfile_loader_find(entry->wr_pointer.src_file, files); |
| |
| if (!dest_file || !src_file || !src_file->data || |
| dst_offset + entry->wr_pointer.size < dst_offset || |
| dst_offset + entry->wr_pointer.size > dest_file->size || |
| src_offset >= src_file->file->size || |
| entry->wr_pointer.size < 1 || entry->wr_pointer.size > 8 || |
| entry->wr_pointer.size & (entry->wr_pointer.size - 1)) { |
| goto err; |
| } |
| |
| pointer = (unsigned long)src_file->data + src_offset; |
| /* Make sure the pointer fits within wr_pointer.size */ |
| if ((entry->wr_pointer.size != sizeof(u64)) && |
| ((pointer >> (entry->wr_pointer.size * 8)) > 0)) { |
| goto err; |
| } |
| pointer = cpu_to_le64(pointer); |
| |
| /* Only supported on QEMU */ |
| if (qemu_cfg_write_file(&pointer, dest_file, dst_offset, |
| entry->wr_pointer.size) != entry->wr_pointer.size) { |
| goto err; |
| } |
| |
| /* Store the info so it can replayed later if necessary */ |
| struct romfile_wr_pointer_entry *store = malloc_high(sizeof(*store)); |
| if (!store) { |
| warn_noalloc(); |
| return; |
| } |
| store->pointer = pointer; |
| store->key = qemu_get_romfile_key(dest_file); |
| store->offset = dst_offset; |
| store->ptr_size = entry->wr_pointer.size; |
| hlist_add_head(&store->node, &romfile_pointer_list); |
| |
| 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); |
| break; |
| case ROMFILE_LOADER_COMMAND_WRITE_POINTER: |
| romfile_loader_write_pointer(entry, files); |
| break; |
| default: |
| /* Skip commands that we don't recognize. */ |
| break; |
| } |
| } |
| |
| free(files); |
| free(data); |
| return 0; |
| |
| err: |
| free(data); |
| return -1; |
| } |