| /* ELF Boot loader |
| * As we have seek, this implementation can be straightforward. |
| * 2003-07 by SONE Takeshi |
| */ |
| |
| #include "config.h" |
| #include "kernel/kernel.h" |
| #include "libc/diskio.h" |
| #include "arch/common/elf_boot.h" |
| #include "libopenbios/elf_load.h" |
| #include "libopenbios/sys_info.h" |
| #include "libopenbios/ipchecksum.h" |
| #include "libopenbios/bindings.h" |
| #include "libopenbios/initprogram.h" |
| #include "libopenbios/ofmem.h" |
| #define printf printk |
| #define debug printk |
| |
| #define DEBUG 0 |
| |
| #define MAX_HEADERS 0x20 |
| #define BS 0x100 /* smallest step used when looking for the ELF header */ |
| |
| #ifdef CONFIG_PPC |
| extern void flush_icache_range( char *start, char *stop ); |
| #endif |
| |
| /* FreeBSD and possibly others mask the high 8 bits */ |
| #define addr_fixup(addr) ((addr) & 0x00ffffff) |
| |
| static char *image_name, *image_version; |
| static int fd; |
| |
| /* Note: avoid name collision with platforms which have their own version of calloc() */ |
| static void *ob_calloc(size_t nmemb, size_t size) |
| { |
| size_t alloc_size = nmemb * size; |
| void *mem; |
| |
| if (alloc_size < nmemb || alloc_size < size) { |
| printf("calloc overflow: %u, %u\n", nmemb, size); |
| return NULL; |
| } |
| |
| mem = malloc(alloc_size); |
| memset(mem, 0, alloc_size); |
| |
| return mem; |
| } |
| |
| static int check_mem_ranges(struct sys_info *info, |
| Elf_phdr *phdr, int phnum) |
| { |
| int i, j; |
| unsigned long start, end; |
| unsigned long prog_start, prog_end; |
| struct memrange *mem; |
| |
| prog_start = virt_to_phys(&_start); |
| prog_end = virt_to_phys(&_end); |
| |
| for (i = 0; i < phnum; i++) { |
| if (phdr[i].p_type != PT_LOAD) |
| continue; |
| start = addr_fixup(phdr[i].p_paddr); |
| end = start + phdr[i].p_memsz; |
| if (start < prog_start && end > prog_start) |
| goto conflict; |
| if (start < prog_end && end > prog_end) |
| goto conflict; |
| mem=info->memrange; |
| for (j = 0; j < info->n_memranges; j++) { |
| if (mem[j].base <= start && mem[j].base + mem[j].size >= end) |
| break; |
| } |
| if (j >= info->n_memranges) |
| goto badseg; |
| } |
| return 1; |
| |
| conflict: |
| printf("%s occupies [%#lx-%#lx]\n", program_name, prog_start, prog_end); |
| |
| badseg: |
| printf("Segment %d [%#lx-%#lx] doesn't fit into memory\n", i, start, end-1); |
| return 0; |
| } |
| |
| static unsigned long process_image_notes(Elf_phdr *phdr, int phnum, |
| unsigned short *sum_ptr, |
| unsigned int offset) |
| { |
| int i; |
| char *buf = NULL; |
| int retval = 0; |
| unsigned long addr, end; |
| Elf_Nhdr *nhdr; |
| const char *name; |
| void *desc; |
| |
| for (i = 0; i < phnum; i++) { |
| if (phdr[i].p_type != PT_NOTE) |
| continue; |
| buf = malloc(phdr[i].p_filesz); |
| seek_io(fd, offset + phdr[i].p_offset); |
| if ((size_t)read_io(fd, buf, phdr[i].p_filesz) != phdr[i].p_filesz) { |
| printf("Can't read note segment\n"); |
| goto out; |
| } |
| addr = (unsigned long) buf; |
| end = addr + phdr[i].p_filesz; |
| while (addr < end) { |
| nhdr = (Elf_Nhdr *) addr; |
| addr += sizeof(Elf_Nhdr); |
| name = (const char *) addr; |
| addr += (nhdr->n_namesz+3) & ~3; |
| desc = (void *) addr; |
| addr += (nhdr->n_descsz+3) & ~3; |
| |
| if (nhdr->n_namesz==sizeof(ELF_NOTE_BOOT) |
| && memcmp(name, ELF_NOTE_BOOT, sizeof(ELF_NOTE_BOOT))==0) { |
| if (nhdr->n_type == EIN_PROGRAM_NAME) { |
| image_name = ob_calloc(1, nhdr->n_descsz + 1); |
| memcpy(image_name, desc, nhdr->n_descsz); |
| } |
| if (nhdr->n_type == EIN_PROGRAM_VERSION) { |
| image_version = ob_calloc(1, nhdr->n_descsz + 1); |
| memcpy(image_version, desc, nhdr->n_descsz); |
| } |
| if (nhdr->n_type == EIN_PROGRAM_CHECKSUM) { |
| *sum_ptr = *(unsigned short *) desc; |
| debug("Image checksum: %#04x\n", *sum_ptr); |
| /* Where in the file */ |
| retval = phdr[i].p_offset |
| + (unsigned long) desc - (unsigned long) buf; |
| } |
| } |
| } |
| } |
| out: |
| close_io(fd); |
| if (buf) |
| free(buf); |
| return retval; |
| } |
| |
| static int load_segments(Elf_phdr *phdr, int phnum, |
| unsigned long checksum_offset, |
| unsigned int offset, unsigned long *bytes) |
| { |
| //unsigned int start_time, time; |
| int i; |
| |
| *bytes = 0; |
| // start_time = currticks(); |
| for (i = 0; i < phnum; i++) { |
| if (phdr[i].p_type != PT_LOAD) |
| continue; |
| debug("segment %d addr:" FMT_elf " file:" FMT_elf " mem:" FMT_elf " ", |
| i, addr_fixup(phdr[i].p_paddr), phdr[i].p_filesz, phdr[i].p_memsz); |
| seek_io(fd, offset + phdr[i].p_offset); |
| debug("loading... "); |
| if ((size_t)read_io(fd, phys_to_virt(addr_fixup(phdr[i].p_paddr)), phdr[i].p_filesz) |
| != phdr[i].p_filesz) { |
| printf("Can't read program segment %d\n", i); |
| return 0; |
| } |
| bytes += phdr[i].p_filesz; |
| debug("clearing... "); |
| memset(phys_to_virt(addr_fixup(phdr[i].p_paddr) + phdr[i].p_filesz), 0, |
| phdr[i].p_memsz - phdr[i].p_filesz); |
| if (phdr[i].p_offset <= checksum_offset |
| && phdr[i].p_offset + phdr[i].p_filesz >= checksum_offset+2) { |
| debug("clearing checksum... "); |
| memset(phys_to_virt(addr_fixup(phdr[i].p_paddr) + checksum_offset |
| - phdr[i].p_offset), 0, 2); |
| } |
| debug("ok\n"); |
| |
| } |
| // time = currticks() - start_time; |
| //debug("Loaded %lu bytes in %ums (%luKB/s)\n", bytes, time, |
| // time? bytes/time : 0); |
| debug("Loaded %lu bytes \n", *bytes); |
| |
| return 1; |
| } |
| |
| static int verify_image(Elf_ehdr *ehdr, Elf_phdr *phdr, int phnum, |
| unsigned short image_sum) |
| { |
| unsigned short sum, part_sum; |
| unsigned long offset; |
| int i; |
| |
| sum = 0; |
| offset = 0; |
| |
| part_sum = ipchksum(ehdr, sizeof *ehdr); |
| sum = add_ipchksums(offset, sum, part_sum); |
| offset += sizeof *ehdr; |
| |
| part_sum = ipchksum(phdr, phnum * sizeof(*phdr)); |
| sum = add_ipchksums(offset, sum, part_sum); |
| offset += phnum * sizeof(*phdr); |
| |
| for (i = 0; i < phnum; i++) { |
| if (phdr[i].p_type != PT_LOAD) |
| continue; |
| part_sum = ipchksum(phys_to_virt(addr_fixup(phdr[i].p_paddr)), phdr[i].p_memsz); |
| sum = add_ipchksums(offset, sum, part_sum); |
| offset += phdr[i].p_memsz; |
| } |
| |
| if (sum != image_sum) { |
| printf("Verify FAILED (image:%#04x vs computed:%#04x)\n", |
| image_sum, sum); |
| return 0; |
| } |
| return 1; |
| } |
| |
| static inline unsigned padded(unsigned s) |
| { |
| return (s + 3) & ~3; |
| } |
| |
| static Elf_Bhdr *add_boot_note(Elf_Bhdr *bhdr, const char *name, |
| unsigned type, const char *desc, unsigned descsz) |
| { |
| Elf_Nhdr nhdr; |
| unsigned ent_size, new_size, pad; |
| char *addr; |
| |
| if (!bhdr) |
| return NULL; |
| |
| nhdr.n_namesz = name? strlen(name)+1 : 0; |
| nhdr.n_descsz = descsz; |
| nhdr.n_type = type; |
| ent_size = sizeof(nhdr) + padded(nhdr.n_namesz) + padded(nhdr.n_descsz); |
| if (bhdr->b_size + ent_size > 0xffff) { |
| printf("Boot notes too big\n"); |
| free(bhdr); |
| return NULL; |
| } |
| if (bhdr->b_size + ent_size > bhdr->b_checksum) { |
| do { |
| new_size = bhdr->b_checksum * 2; |
| } while (new_size < bhdr->b_size + ent_size); |
| if (new_size > 0xffff) |
| new_size = 0xffff; |
| debug("expanding boot note size to %u\n", new_size); |
| #ifdef HAVE_REALLOC |
| bhdr = realloc(bhdr, new_size); |
| bhdr->b_checksum = new_size; |
| #else |
| printf("Boot notes too big\n"); |
| free(bhdr); |
| return NULL; |
| #endif |
| } |
| |
| addr = (char *) bhdr; |
| addr += bhdr->b_size; |
| memcpy(addr, &nhdr, sizeof(nhdr)); |
| addr += sizeof(nhdr); |
| |
| if (name && nhdr.n_namesz) { |
| memcpy(addr, name, nhdr.n_namesz); |
| addr += nhdr.n_namesz; |
| pad = padded(nhdr.n_namesz) - nhdr.n_namesz; |
| memset(addr, 0, pad); |
| addr += pad; |
| } |
| |
| memcpy(addr, desc, nhdr.n_descsz); |
| addr += nhdr.n_descsz; |
| pad = padded(nhdr.n_descsz) - nhdr.n_descsz; |
| memset(addr, 0, pad); |
| |
| bhdr->b_size += ent_size; |
| bhdr->b_records++; |
| return bhdr; |
| } |
| |
| static inline Elf_Bhdr *add_note_string(Elf_Bhdr *bhdr, const char *name, |
| unsigned type, const char *desc) |
| { |
| return add_boot_note(bhdr, name, type, desc, strlen(desc) + 1); |
| } |
| |
| static Elf_Bhdr *build_boot_notes(struct sys_info *info, const char *cmdline) |
| { |
| Elf_Bhdr *bhdr; |
| |
| bhdr = malloc(256); |
| bhdr->b_signature = ELF_BHDR_MAGIC; |
| bhdr->b_size = sizeof *bhdr; |
| bhdr->b_checksum = 256; /* XXX cache the current buffer size here */ |
| bhdr->b_records = 0; |
| |
| if (info->firmware) |
| bhdr = add_note_string(bhdr, NULL, EBN_FIRMWARE_TYPE, info->firmware); |
| bhdr = add_note_string(bhdr, NULL, EBN_BOOTLOADER_NAME, program_name); |
| bhdr = add_note_string(bhdr, NULL, EBN_BOOTLOADER_VERSION, program_version); |
| if (cmdline) |
| bhdr = add_note_string(bhdr, NULL, EBN_COMMAND_LINE, cmdline); |
| if (!bhdr) |
| return bhdr; |
| bhdr->b_checksum = 0; |
| bhdr->b_checksum = ipchksum(bhdr, bhdr->b_size); |
| return bhdr; |
| } |
| |
| int |
| is_elf(Elf_ehdr *ehdr) |
| { |
| return (ehdr->e_ident[EI_MAG0] == ELFMAG0 |
| && ehdr->e_ident[EI_MAG1] == ELFMAG1 |
| && ehdr->e_ident[EI_MAG2] == ELFMAG2 |
| && ehdr->e_ident[EI_MAG3] == ELFMAG3 |
| && ehdr->e_ident[EI_CLASS] == ARCH_ELF_CLASS |
| && ehdr->e_ident[EI_DATA] == ARCH_ELF_DATA |
| && ehdr->e_ident[EI_VERSION] == EV_CURRENT |
| && ehdr->e_type == ET_EXEC |
| && ARCH_ELF_MACHINE_OK(ehdr->e_machine) |
| && ehdr->e_version == EV_CURRENT |
| && ehdr->e_phentsize == sizeof(Elf_phdr)); |
| } |
| |
| int |
| find_elf(Elf_ehdr *ehdr) |
| { |
| int offset; |
| |
| for (offset = 0; offset < MAX_HEADERS * BS; offset += BS) { |
| if ((size_t)read_io(fd, ehdr, sizeof ehdr) != sizeof ehdr) { |
| debug("Can't read ELF header\n"); |
| return 0; |
| } |
| |
| if (is_elf(ehdr)) { |
| debug("Found ELF header at offset %d\n", offset); |
| return offset; |
| } |
| |
| seek_io(fd, offset); |
| } |
| |
| debug("Not a bootable ELF image\n"); |
| return 0; |
| } |
| |
| Elf_phdr * |
| elf_readhdrs(int offset, Elf_ehdr *ehdr) |
| { |
| unsigned long phdr_size; |
| Elf_phdr *phdr; |
| |
| phdr_size = ehdr->e_phnum * sizeof(Elf_phdr); |
| phdr = malloc(phdr_size); |
| seek_io(fd, offset + ehdr->e_phoff); |
| if ((size_t)read_io(fd, phdr, phdr_size) != phdr_size) { |
| printf("Can't read program header\n"); |
| return NULL; |
| } |
| |
| return phdr; |
| } |
| |
| int |
| elf_load(struct sys_info *info, ihandle_t dev, const char *cmdline, void **boot_notes) |
| { |
| Elf_ehdr ehdr; |
| Elf_phdr *phdr = NULL; |
| unsigned long checksum_offset, file_size; |
| unsigned short checksum = 0; |
| int retval = -1; |
| unsigned int offset; |
| |
| image_name = image_version = NULL; |
| |
| /* Mark the saved-program-state as invalid */ |
| feval("0 state-valid !"); |
| |
| fd = open_ih(dev); |
| if (fd == -1) { |
| goto out; |
| } |
| |
| offset = find_elf(&ehdr); |
| if (!offset) { |
| retval = LOADER_NOT_SUPPORT; |
| goto out; |
| } |
| |
| #if DEBUG |
| printk("ELF header:\n"); |
| printk(" ehdr.e_type = %d\n", (int)ehdr.e_type); |
| printk(" ehdr.e_machine = %d\n", (int)ehdr.e_machine); |
| printk(" ehdr.e_version = %d\n", (int)ehdr.e_version); |
| printk(" ehdr.e_entry = 0x%08x\n", (int)ehdr.e_entry); |
| printk(" ehdr.e_phoff = 0x%08x\n", (int)ehdr.e_phoff); |
| printk(" ehdr.e_shoff = 0x%08x\n", (int)ehdr.e_shoff); |
| printk(" ehdr.e_flags = %d\n", (int)ehdr.e_flags); |
| printk(" ehdr.e_ehsize = 0x%08x\n", (int)ehdr.e_ehsize); |
| printk(" ehdr.e_phentsize = 0x%08x\n", (int)ehdr.e_phentsize); |
| printk(" ehdr.e_phnum = %d\n", (int)ehdr.e_phnum); |
| #endif |
| |
| if (ehdr.e_phnum > MAX_HEADERS) { |
| printk ("elfload: too many program headers (MAX_HEADERS)\n"); |
| retval = 0; |
| goto out; |
| } |
| |
| phdr = elf_readhdrs(offset, &ehdr); |
| if (!phdr) |
| goto out; |
| |
| if (!check_mem_ranges(info, phdr, ehdr.e_phnum)) |
| goto out; |
| |
| checksum_offset = process_image_notes(phdr, ehdr.e_phnum, &checksum, offset); |
| |
| printf("Loading %s", image_name ? image_name : "image"); |
| if (image_version) |
| printf(" version %s", image_version); |
| printf("...\n"); |
| |
| if (!load_segments(phdr, ehdr.e_phnum, checksum_offset, offset, &file_size)) |
| goto out; |
| |
| if (checksum_offset) { |
| if (!verify_image(&ehdr, phdr, ehdr.e_phnum, checksum)) |
| goto out; |
| } |
| |
| /* If we are attempting an ELF boot image, we pass a non-NULL pointer |
| into boot_notes and mark the image as elf-boot rather than standard |
| ELF */ |
| if (boot_notes) { |
| *boot_notes = (void *)virt_to_phys(build_boot_notes(info, cmdline)); |
| feval("elf-boot load-state >ls.file-type !"); |
| PUSH((ucell)*boot_notes); |
| feval("elf-boot load-state >ls.param !"); |
| } else { |
| feval("elf load-state >ls.file-type !"); |
| } |
| |
| //debug("current time: %lu\n", currticks()); |
| |
| debug("entry point is " FMT_elf "\n", addr_fixup(ehdr.e_entry)); |
| |
| // Initialise saved-program-state |
| PUSH(file_size); |
| feval("load-state >ls.file-size !"); |
| feval("elf load-state >ls.file-type !"); |
| |
| out: |
| close_io(fd); |
| if (phdr) |
| free(phdr); |
| if (image_name) |
| free(image_name); |
| if (image_version) |
| free(image_version); |
| return retval; |
| } |
| |
| void |
| elf_init_program(void) |
| { |
| char *base; |
| int i; |
| Elf_ehdr *ehdr; |
| Elf_phdr *phdr; |
| size_t size, total_size = 0; |
| char *addr; |
| uintptr_t tmp; |
| |
| /* TODO: manage ELF notes section */ |
| feval("load-base"); |
| base = (char*)cell2pointer(POP()); |
| |
| ehdr = (Elf_ehdr *)base; |
| |
| if (!is_elf(ehdr)) { |
| debug("Not a valid ELF memory image\n"); |
| return; |
| } |
| |
| phdr = (Elf_phdr *)(base + ehdr->e_phoff); |
| |
| for (i = 0; i < ehdr->e_phnum; i++) { |
| |
| #if DEBUG |
| debug("filesz: %08lX memsz: %08lX p_offset: %08lX " |
| "p_vaddr %08lX\n", |
| (unsigned long)phdr[i].p_filesz, (unsigned long)phdr[i].p_memsz, |
| (unsigned long)phdr[i].p_offset, (unsigned long)phdr[i].p_vaddr ); |
| #endif |
| |
| size = MIN(phdr[i].p_filesz, phdr[i].p_memsz); |
| if (!size) |
| continue; |
| #if !defined(CONFIG_SPARC32) && !defined(CONFIG_X86) |
| if( ofmem_claim( phdr[i].p_vaddr, phdr[i].p_memsz, 0 ) == -1 ) { |
| printk("Ignoring failed claim for va %lx memsz %lx!\n", |
| (unsigned long)phdr[i].p_vaddr, |
| (unsigned long)phdr[i].p_memsz); |
| } |
| #endif |
| /* Workaround for archs where sizeof(int) != pointer size */ |
| tmp = phdr[i].p_vaddr; |
| addr = (char *)tmp; |
| |
| memcpy(addr, base + phdr[i].p_offset, size); |
| |
| total_size += size; |
| |
| #ifdef CONFIG_PPC |
| flush_icache_range( addr, addr + size ); |
| #endif |
| } |
| |
| // Initialise load-state |
| PUSH(ehdr->e_entry); |
| feval("load-state >ls.entry !"); |
| |
| arch_init_program(); |
| |
| feval("-1 state-valid !"); |
| } |