#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"
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;
if (start == 0)
continue;
if ((start <= addr) && (end >= addr)) {
return (0);
}
if ((start >= addr) && (start <= addr + size)) {
return (0);
}
}
return (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;
if (map->PhysicalStart < 0x100000)
continue;
if ((map->PhysicalStart <= addr) &&
(map->PhysicalStart +
(map->NumberOfPages << EFI_PAGE_SHIFT) >= addr + size)) {
return (addr);
}
if ((map->PhysicalStart > addr) &&
(map->NumberOfPages >= EFI_SIZE_TO_PAGES(size))) {
return (map->PhysicalStart);
}
}
return (0);
}
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;
off = memmap_find(map, count, dsize, off, size);
if (off != 0)
off = addr_verify(module, off, size);
if (off != 0)
break;
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);
if (off == 0 || off >= UINT32_MAX)
off = addr_verify(module, laddr, size);
return (off);
}
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);
if (type == LOAD_ELF)
return (0);
if (type == LOAD_MEM)
size = *(size_t *)data;
else {
stat(data, &st);
size = st.st_size;
}
if (size == 0)
return (addr);
pages = EFI_SIZE_TO_PAGES(size);
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);
}
}
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;
head = &relocator->rel_chunk_head;
chunk = STAILQ_LAST(head, chunk, chunk_next);
mbi = (multiboot2_info_header_t *)(uintptr_t)chunk->chunk_paddr;
chunk = NULL;
while (!done) {
if (chunk != NULL)
chunk = STAILQ_NEXT(chunk, chunk_next);
done = true;
STAILQ_FOREACH_FROM(chunk, head, chunk_next) {
if (chunk->chunk_paddr != chunk->chunk_vaddr) {
done = false;
break;
}
}
if (done)
break;
STAILQ_FOREACH(c, head, chunk_next) {
if (c->chunk_vaddr == c->chunk_paddr)
continue;
if (c->chunk_vaddr == chunk->chunk_vaddr &&
c->chunk_size == chunk->chunk_size)
continue;
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 (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);
}