root/usr/src/boot/efi/loader/copy.c
/*
 * Copyright (c) 2013 The FreeBSD Foundation
 * All rights reserved.
 *
 * This software was developed by Benno Rice under sponsorship from
 * the FreeBSD Foundation.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>

#include <sys/param.h>
#include <sys/multiboot2.h>

#include <stand.h>
#include <bootstrap.h>

#include <efi.h>
#include <efilib.h>

#include "loader_efi.h"

/*
 * Verify the address is not in use by existing modules.
 */
static vm_offset_t
addr_verify(multiboot_tag_module_t *module, vm_offset_t addr, size_t size)
{
        vm_offset_t start, end;

        for (; module->mb_type == MULTIBOOT_TAG_TYPE_MODULE;
            module = (multiboot_tag_module_t *)
            roundup((uintptr_t)module + module->mb_size, MULTIBOOT_TAG_ALIGN)) {

                start = module->mb_mod_start;
                end = module->mb_mod_end;

                /* Does this module have address assigned? */
                if (start == 0)
                        continue;

                if ((start <= addr) && (end >= addr)) {
                        return (0);
                }
                if ((start >= addr) && (start <= addr + size)) {
                        return (0);
                }
        }
        return (addr);
}

/*
 * Find memory map entry above 1MB, able to contain size bytes from addr.
 */
static vm_offset_t
memmap_find(EFI_MEMORY_DESCRIPTOR *map, size_t count, UINTN dsize,
    vm_offset_t addr, size_t size)
{
        int i;

        for (i = 0; i < count; i++, map = NextMemoryDescriptor(map, dsize)) {

                if (map->Type != EfiConventionalMemory)
                        continue;

                /* We do not want address below 1MB. */
                if (map->PhysicalStart < 0x100000)
                        continue;

                /* Do we fit into current entry? */
                if ((map->PhysicalStart <= addr) &&
                    (map->PhysicalStart +
                    (map->NumberOfPages << EFI_PAGE_SHIFT) >= addr + size)) {
                        return (addr);
                }

                /* Do we fit into new entry? */
                if ((map->PhysicalStart > addr) &&
                    (map->NumberOfPages >= EFI_SIZE_TO_PAGES(size))) {
                        return (map->PhysicalStart);
                }
        }
        return (0);
}

/*
 * Find usable address for loading. The address for the kernel is fixed, as
 * it is determined by kernel linker map (dboot PT_LOAD address).
 * For modules, we need to consult memory map, the module address has to be
 * aligned to page boundary and we have to fit into map entry.
 */
vm_offset_t
efi_physaddr(multiboot_tag_module_t *module, vm_offset_t addr,
    EFI_MEMORY_DESCRIPTOR *map, size_t count, UINTN dsize, vm_offset_t laddr,
    size_t size)
{
        multiboot_tag_module_t *mp;
        vm_offset_t off;

        if (addr == 0)
                return (addr);

        mp = module;
        do {
                off = addr;
                /* Test proposed address */
                off = memmap_find(map, count, dsize, off, size);
                if (off != 0)
                        off = addr_verify(module, off, size);
                if (off != 0)
                        break;

                /* The module list is exhausted */
                if (mp->mb_type != MULTIBOOT_TAG_TYPE_MODULE)
                        break;

                if (mp->mb_mod_start != 0) {
                        addr = roundup2(mp->mb_mod_end + 1,
                            MULTIBOOT_MOD_ALIGN);
                }
                mp = (multiboot_tag_module_t *)
                    roundup((uintptr_t)mp + mp->mb_size, MULTIBOOT_TAG_ALIGN);
        } while (off == 0);

        /*
         * memmap_find failed to get us address, try to use load
         * address.
         */
        if (off == 0 || off >= UINT32_MAX)
                off = addr_verify(module, laddr, size);

        return (off);
}

/*
 * Allocate pages for data to be loaded. As we can not expect AllocateAddress
 * to succeed, we allocate using AllocateMaxAddress from 4GB limit.
 * 4GB limit is because reportedly some 64bit systems are reported to have
 * issues with memory above 4GB. It should be quite enough anyhow.
 * Note: AllocateMaxAddress will only make sure we are below the specified
 * address, we can not make any assumptions about actual location or
 * about the order of the allocated blocks.
 */
vm_offset_t
efi_loadaddr(uint_t type, void *data, vm_offset_t addr)
{
        EFI_PHYSICAL_ADDRESS paddr;
        struct stat st;
        size_t size;
        uint64_t pages;
        EFI_STATUS status;

        if (addr == 0)
                return (addr);  /* nothing to do */

        if (type == LOAD_ELF)
                return (0);     /* not supported */

        if (type == LOAD_MEM)
                size = *(size_t *)data;
        else {
                stat(data, &st);
                size = st.st_size;
        }

        /* AllocatePages can not allocate 0 pages. */
        if (size == 0)
                return (addr);

        pages = EFI_SIZE_TO_PAGES(size);
        /* 4GB upper limit */
        paddr = UINT32_MAX;

        status = BS->AllocatePages(AllocateMaxAddress, EfiLoaderData,
            pages, &paddr);

        if (EFI_ERROR(status)) {
                printf("failed to allocate %zu bytes for staging area: %lu\n",
                    size, DECODE_ERROR(status));
                return (0);
        }

        return (paddr);
}

void
efi_free_loadaddr(vm_offset_t addr, size_t pages)
{
        (void) BS->FreePages(addr, pages);
}

void *
efi_translate(vm_offset_t ptr)
{
        return ((void *)ptr);
}

ssize_t
efi_copyin(const void *src, vm_offset_t dest, const size_t len)
{
        if (dest + len >= dest && (uint64_t)dest + len <= UINT32_MAX) {
                bcopy(src, (void *)(uintptr_t)dest, len);
                return (len);
        } else {
                errno = EFBIG;
                return (-1);
        }
}

ssize_t
efi_copyout(const vm_offset_t src, void *dest, const size_t len)
{
        if (src + len >= src && (uint64_t)src + len <= UINT32_MAX) {
                bcopy((void *)(uintptr_t)src, dest, len);
                return (len);
        } else {
                errno = EFBIG;
                return (-1);
        }
}


ssize_t
efi_readin(const int fd, vm_offset_t dest, const size_t len)
{
        if (dest + len >= dest && (uint64_t)dest + len <= UINT32_MAX) {
                return (read(fd, (void *)dest, len));
        } else {
                errno = EFBIG;
                return (-1);
        }
}

/*
 * Relocate chunks and return pointer to MBI.
 * This function is relocated before being called and we only have
 * memmove() available, as most likely moving chunks into the final
 * destination will destroy the rest of the loader code.
 *
 * In safe area we have relocator data, multiboot_tramp, efi_copy_finish,
 * memmove and stack.
 */
multiboot2_info_header_t *
efi_copy_finish(struct relocator *relocator)
{
        multiboot2_info_header_t *mbi;
        struct chunk *chunk, *c;
        struct chunk_head *head;
        bool done = false;
        void (*move)(void *s1, const void *s2, size_t n);

        move = (void *)relocator->rel_memmove;

        /* MBI is the last chunk in the list. */
        head = &relocator->rel_chunk_head;
        chunk = STAILQ_LAST(head, chunk, chunk_next);
        mbi = (multiboot2_info_header_t *)(uintptr_t)chunk->chunk_paddr;

        /*
         * If chunk paddr == vaddr, the chunk is in place.
         * If all chunks are in place, we are done.
         */
        chunk = NULL;
        while (!done) {
                /* Advance to next item in list. */
                if (chunk != NULL)
                        chunk = STAILQ_NEXT(chunk, chunk_next);

                /*
                 * First check if we have anything to do.
                 * We set chunk to NULL every time we move the data.
                 */
                done = true;
                STAILQ_FOREACH_FROM(chunk, head, chunk_next) {
                        if (chunk->chunk_paddr != chunk->chunk_vaddr) {
                                done = false;
                                break;
                        }
                }
                if (done)
                        break;

                /*
                 * Make sure the destination is not conflicting
                 * with rest of the modules.
                 */
                STAILQ_FOREACH(c, head, chunk_next) {
                        /* Moved already? */
                        if (c->chunk_vaddr == c->chunk_paddr)
                                continue;

                        /* Is it the chunk itself? */
                        if (c->chunk_vaddr == chunk->chunk_vaddr &&
                            c->chunk_size == chunk->chunk_size)
                                continue;

                        /*
                         * Check for overlaps.
                         */
                        if ((c->chunk_vaddr >= chunk->chunk_paddr &&
                            c->chunk_vaddr <=
                            chunk->chunk_paddr + chunk->chunk_size) ||
                            (c->chunk_vaddr + c->chunk_size >=
                            chunk->chunk_paddr &&
                            c->chunk_vaddr + c->chunk_size <=
                            chunk->chunk_paddr + chunk->chunk_size)) {
                                break;
                        }
                }
                /* If there are no conflicts, move to place and restart. */
                if (c == NULL) {
                        move((void *)(uintptr_t)chunk->chunk_paddr,
                            (void *)(uintptr_t)chunk->chunk_vaddr,
                            chunk->chunk_size);
                        chunk->chunk_vaddr = chunk->chunk_paddr;
                        chunk = NULL;
                        continue;
                }
        }

        return (mbi);
}