diff --git a/ChangeLog b/ChangeLog index 7d7483012..6240a3548 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2013-06-16 Vladimir Serbinenko + + Add support for processed coreboot payload chainloading. + 2013-06-16 Vladimir Serbinenko Enable coreboot information commands even when not loaded as diff --git a/grub-core/loader/i386/coreboot/chainloader.c b/grub-core/loader/i386/coreboot/chainloader.c index 505aa0b6e..ea3048912 100644 --- a/grub-core/loader/i386/coreboot/chainloader.c +++ b/grub-core/loader/i386/coreboot/chainloader.c @@ -29,6 +29,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -55,14 +56,176 @@ grub_chain_unload (void) return GRUB_ERR_NONE; } +static grub_err_t +load_elf (grub_file_t file, const char *filename) +{ + grub_elf_t elf; + Elf32_Phdr *phdr; + grub_err_t err; + + elf = grub_elf_file (file, filename); + if (!elf) + return grub_errno; + + if (!grub_elf_is_elf32 (elf)) + return grub_error (GRUB_ERR_BAD_OS, "only ELF32 can be coreboot payload"); + + entry = elf->ehdr.ehdr32.e_entry; + + FOR_ELF32_PHDRS(elf, phdr) + { + grub_uint8_t *load_addr; + grub_relocator_chunk_t ch; + + if (phdr->p_type != PT_LOAD) + continue; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + phdr->p_paddr, phdr->p_memsz); + if (err) + { + elf->file = 0; + grub_elf_close (elf); + return err; + } + + load_addr = get_virtual_current_address (ch); + + if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1) + { + elf->file = 0; + grub_elf_close (elf); + return grub_errno; + } + + if (phdr->p_filesz) + { + grub_ssize_t read; + read = grub_file_read (elf->file, load_addr, phdr->p_filesz); + if (read != (grub_ssize_t) phdr->p_filesz) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), + filename); + elf->file = 0; + grub_elf_close (elf); + return grub_errno; + } + } + + if (phdr->p_filesz < phdr->p_memsz) + grub_memset ((load_addr + phdr->p_filesz), + 0, phdr->p_memsz - phdr->p_filesz); + } + + elf->file = 0; + grub_elf_close (elf); + return GRUB_ERR_NONE; +} + +static grub_err_t +load_segment (grub_file_t file, const char *filename, + void *load_addr, grub_uint32_t comp, + grub_size_t size) +{ + switch (comp) + { + case grub_cpu_to_be32_compile_time (CBFS_COMPRESS_NONE): + if (grub_file_read (file, load_addr, size) + != (grub_ssize_t) size) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), + filename); + return grub_errno; + } + return GRUB_ERR_NONE; + default: + return grub_error (GRUB_ERR_BAD_OS, "unsupported compression %d", + grub_be_to_cpu32 (comp)); + } +} + +static grub_err_t +load_chewed (grub_file_t file, const char *filename) +{ + grub_size_t i; + for (i = 0;; i++) + { + struct cbfs_payload_segment segment; + grub_err_t err; + + if (grub_file_seek (file, sizeof (segment) * i) == (grub_off_t) -1 + || grub_file_read (file, &segment, sizeof (segment)) + != sizeof (segment)) + { + if (!grub_errno) + return grub_error (GRUB_ERR_BAD_OS, + "payload is too short"); + return grub_errno; + } + + switch (segment.type) + { + case PAYLOAD_SEGMENT_PARAMS: + break; + + case PAYLOAD_SEGMENT_ENTRY: + entry = grub_be_to_cpu64 (segment.load_addr); + return GRUB_ERR_NONE; + + case PAYLOAD_SEGMENT_BSS: + segment.len = 0; + segment.offset = 0; + segment.len = 0; + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + { + grub_uint32_t target = grub_be_to_cpu64 (segment.load_addr); + grub_uint32_t memsize = grub_be_to_cpu32 (segment.mem_len); + grub_uint32_t filesize = grub_be_to_cpu32 (segment.len); + grub_uint8_t *load_addr; + grub_relocator_chunk_t ch; + + if (memsize < filesize) + memsize = filesize; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + target, memsize); + if (err) + return err; + + load_addr = get_virtual_current_address (ch); + + if (filesize) + { + if (grub_file_seek (file, grub_be_to_cpu32 (segment.offset)) + == (grub_off_t) -1) + return grub_errno; + + err = load_segment (file, filename, load_addr, + segment.compression, filesize); + if (err) + return err; + } + + if (filesize < memsize) + grub_memset ((load_addr + filesize), + 0, memsize - filesize); + } + } + } +} + static grub_err_t grub_cmd_chain (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_err_t err; grub_file_t file; - grub_elf_t elf; - Elf32_Phdr *phdr; + grub_uint32_t head; if (argc != 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); @@ -80,63 +243,43 @@ grub_cmd_chain (grub_command_t cmd __attribute__ ((unused)), return grub_errno; } - elf = grub_elf_file (file, argv[0]); - if (!elf) + if (grub_file_read (file, &head, sizeof (head)) != sizeof (head) + || grub_file_seek (file, 0) == (grub_off_t) -1) { - grub_relocator_unload (relocator); - relocator = 0; grub_file_close (file); + grub_relocator_unload (relocator); + relocator = 0; + if (!grub_errno) + return grub_error (GRUB_ERR_BAD_OS, + "payload is too short"); + return grub_errno; } + + switch (head) + { + case ELFMAG0 | (ELFMAG1 << 8) | (ELFMAG2 << 16) | (ELFMAG3 << 24): + err = load_elf (file, argv[0]); + break; + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + case PAYLOAD_SEGMENT_PARAMS: + case PAYLOAD_SEGMENT_BSS: + case PAYLOAD_SEGMENT_ENTRY: + err = load_chewed (file, argv[0]); + break; - if (!grub_elf_is_elf32 (elf)) + default: + err = grub_error (GRUB_ERR_BAD_OS, "unrecognised payload type"); + break; + } + grub_file_close (file); + if (err) { grub_relocator_unload (relocator); relocator = 0; - grub_elf_close (elf); + return err; } - entry = elf->ehdr.ehdr32.e_entry; - - FOR_ELF32_PHDRS(elf, phdr) - { - grub_uint8_t *load_addr; - grub_relocator_chunk_t ch; - - if (phdr->p_type != PT_LOAD) - continue; - - err = grub_relocator_alloc_chunk_addr (relocator, &ch, - phdr->p_paddr, phdr->p_memsz); - if (err) - break; - - load_addr = get_virtual_current_address (ch); - - if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1) - return grub_errno; - - if (phdr->p_filesz) - { - grub_ssize_t read; - read = grub_file_read (elf->file, load_addr, phdr->p_filesz); - if (read != (grub_ssize_t) phdr->p_filesz) - { - if (!grub_errno) - grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"), - argv[0]); - break; - } - } - - if (phdr->p_filesz < phdr->p_memsz) - grub_memset ((load_addr + phdr->p_filesz), - 0, phdr->p_memsz - phdr->p_filesz); - } - - grub_elf_close (elf); - if (grub_errno) - return grub_errno; - grub_loader_set (grub_chain_boot, grub_chain_unload, 0); return GRUB_ERR_NONE; }