seL4-microkit/tool/sel4coreplat/loader.py

263 lines
9.4 KiB
Python

#
# Copyright 2021, Breakaway Consulting Pty. Ltd.
#
# SPDX-License-Identifier: BSD-2-Clause
#
from pathlib import Path
from struct import pack
from typing import Dict, List, Optional, Tuple, Union
from sel4coreplat.elf import ElfFile
from sel4coreplat.util import kb, mb, round_up, MemoryRegion
AARCH64_1GB_BLOCK_BITS = 30
AARCH64_2MB_BLOCK_BITS = 21
AARCH64_LVL0_BITS = 9
AARCH64_LVL1_BITS = 9
AARCH64_LVL2_BITS = 9
PAGE_TABLE_SIZE = 4096
def mask(x: int) -> int:
return ((1 << x) - 1)
def lvl0_index(addr: int) -> int:
return (((addr) >> (AARCH64_2MB_BLOCK_BITS + AARCH64_LVL2_BITS + AARCH64_LVL1_BITS)) & mask(AARCH64_LVL0_BITS))
def lvl1_index(addr: int) -> int:
return (((addr) >> (AARCH64_2MB_BLOCK_BITS + AARCH64_LVL2_BITS)) & mask(AARCH64_LVL1_BITS))
def lvl2_index(addr: int) -> int:
return (((addr) >> (AARCH64_2MB_BLOCK_BITS)) & mask(AARCH64_LVL2_BITS))
def lvl0_addr(addr: int) -> int:
bits = AARCH64_2MB_BLOCK_BITS + AARCH64_LVL2_BITS + AARCH64_LVL1_BITS
return (addr >> bits) << bits
def lvl1_addr(addr: int) -> int:
bits = AARCH64_2MB_BLOCK_BITS + AARCH64_LVL2_BITS
return (addr >> bits) << bits
def lvl2_addr(addr: int) -> int:
bits = AARCH64_2MB_BLOCK_BITS
return (addr >> bits) << bits
def _check_non_overlapping(regions: List[Tuple[int, bytes]]) -> None:
checked: List[Tuple[int, int]] = []
for base, data in regions:
end = base + len(data)
# Check that this does not overlap any checked regions
for b, e in checked:
if not (end <= b or base >= e):
raise Exception(f"Overlapping: {base:x}--{end:x} overlaps {b:x} -- {e:x}")
checked.append((base, end))
class Loader:
def __init__(self,
loader_elf_path: Path,
kernel_elf: ElfFile,
initial_task_elf: ElfFile,
reserved_region: MemoryRegion,
regions: List[Tuple[int, bytes]]
) -> None:
# Setup the pagetable data structures (directly embedded in the loader)
self._elf = ElfFile.from_path(loader_elf_path)
sz = self._elf.word_size
self._header_struct_fmt = "<IIIIIIIIII" if sz == 32 else "<QQQQqQQQQQ"
self._region_struct_fmt = "<IIII" if sz == 32 else "<QQQQ"
self._magic = 0x5e14dead if sz== 32 else 0x5e14dead14de5ead
for loader_segment in self._elf.segments:
if loader_segment.loadable:
break
else:
raise Exception("Didn't find loadable segment")
if loader_segment.virt_addr != self._elf.entry:
raise Exception("The loader entry point must be the first byte in the image")
self._image = loader_segment.data
self._regions: List[Tuple[int, Union[bytes, bytearray]]] = []
kernel_first_vaddr: Optional[int] = None
kernel_last_vaddr: Optional[int] = None
kernel_first_paddr: Optional[int] = None
kernel_p_v_offset: Optional[int] = None
for segment in kernel_elf.segments:
if segment.loadable:
# NOTE: For now we include any zeroes. We could optimize in the future
if kernel_first_vaddr is None or segment.virt_addr < kernel_first_vaddr:
kernel_first_vaddr = segment.virt_addr
if kernel_last_vaddr is None or segment.virt_addr + segment.mem_size > kernel_last_vaddr:
kernel_last_vaddr = round_up(segment.virt_addr + segment.mem_size, mb(2))
if kernel_first_paddr is None or segment.phys_addr < kernel_first_paddr:
kernel_first_paddr = segment.phys_addr
if kernel_p_v_offset is None:
kernel_p_v_offset = segment.virt_addr - segment.phys_addr
else:
if kernel_p_v_offset != segment.virt_addr - segment.phys_addr:
raise Exception("Kernel does not have constistent phys to virt offset")
self._regions.append((
segment.phys_addr,
segment.data
))
inittask_first_vaddr: Optional[int] = None
inittask_last_vaddr: Optional[int] = None
inittask_first_paddr: Optional[int] = None
inittask_p_v_offset: Optional[int] = None
assert kernel_first_paddr is not None
for segment in initial_task_elf.segments:
if segment.loadable:
if inittask_first_vaddr is None or segment.virt_addr < inittask_first_vaddr:
inittask_first_vaddr = segment.virt_addr
if inittask_last_vaddr is None or segment.virt_addr + segment.mem_size > inittask_last_vaddr:
inittask_last_vaddr = round_up(segment.virt_addr + segment.mem_size, kb(4))
if inittask_first_paddr is None or segment.phys_addr < kernel_first_paddr:
inittask_first_paddr = segment.phys_addr
if inittask_p_v_offset is None:
inittask_p_v_offset = segment.virt_addr - segment.phys_addr
else:
if inittask_p_v_offset != segment.virt_addr - segment.phys_addr:
raise Exception("Init task does not have constistent phys to virt offset")
# NOTE: For now we include any zeroes. We could optimize in the future
self._regions.append((
segment.phys_addr,
segment.data
))
# Determine the pagetable variables
assert kernel_first_vaddr is not None
assert kernel_first_paddr is not None
pagetable_vars = self._setup_pagetables(kernel_first_vaddr, kernel_first_paddr)
for var_name, var_data in pagetable_vars.items():
var_addr, var_size = self._elf.find_symbol(var_name)
offset = var_addr - loader_segment.virt_addr
assert var_size == len(var_data)
assert offset > 0
assert offset <= len(self._image)
self._image[offset:offset + var_size] = var_data
kernel_entry = kernel_elf.entry
assert inittask_first_paddr is not None
assert inittask_first_vaddr is not None
pv_offset = inittask_first_paddr - inittask_first_vaddr
ui_p_reg_start = inittask_first_paddr
assert inittask_last_vaddr is not None
assert inittask_p_v_offset is not None
ui_p_reg_end = inittask_last_vaddr - inittask_p_v_offset
assert(ui_p_reg_end > ui_p_reg_start)
v_entry = initial_task_elf.entry
extra_device_addr_p = reserved_region.base
extra_device_size = reserved_region.size
self._regions += regions
_check_non_overlapping(self._regions)
# FIXME: Should be a way to determine if seL4 needs hypervisor mode or not
flags = 0
self._header = (
self._magic,
flags,
kernel_entry,
ui_p_reg_start,
ui_p_reg_end,
pv_offset,
v_entry,
extra_device_addr_p,
extra_device_size,
len(self._regions)
)
def _setup_pagetables(self, first_vaddr: int, first_paddr: int) -> Dict[str, bytes]:
boot_lvl1_lower_addr, _ = self._elf.find_symbol("boot_lvl1_lower")
boot_lvl1_upper_addr, _ = self._elf.find_symbol("boot_lvl1_upper")
boot_lvl2_upper_addr, _ = self._elf.find_symbol("boot_lvl2_upper")
boot_lvl0_lower = bytearray(PAGE_TABLE_SIZE)
boot_lvl0_lower[:8] = pack("<Q", boot_lvl1_lower_addr | 3)
boot_lvl1_lower = bytearray(PAGE_TABLE_SIZE)
for i in range(512):
pt_entry = (
(i << AARCH64_1GB_BLOCK_BITS) |
(1 << 10) | # access flag
(0 << 2) | # strongly ordered memory
(1) # 1G block
)
boot_lvl1_lower[8*i:8*(i+1)] = pack("<Q", pt_entry)
boot_lvl0_upper = bytearray(PAGE_TABLE_SIZE)
ptentry = boot_lvl1_upper_addr | 3
idx = lvl0_index(first_vaddr)
boot_lvl0_upper[8 * idx:8 * (idx+1)] = pack("<Q", ptentry)
boot_lvl1_upper = bytearray(PAGE_TABLE_SIZE)
ptentry = boot_lvl2_upper_addr | 3
idx = lvl1_index(first_vaddr)
boot_lvl1_upper[8 * idx:8 * (idx+1)] = pack("<Q", ptentry)
boot_lvl2_upper = bytearray(PAGE_TABLE_SIZE)
for i in range(lvl2_index(first_vaddr), 512):
pt_entry = (
first_paddr |
(1 << 10) | # access flag
(3 << 8) | # make sure the shareability is the same as the kernel's
(4 << 2) | #MT_NORMAL memory
(1 << 0) # 2M block
)
first_paddr += (1 << AARCH64_2MB_BLOCK_BITS)
boot_lvl2_upper[8*i:8*(i+1)] = pack("<Q", pt_entry)
return {
"boot_lvl0_lower": boot_lvl0_lower,
"boot_lvl1_lower": boot_lvl1_lower,
"boot_lvl0_upper": boot_lvl0_upper,
"boot_lvl1_upper": boot_lvl1_upper,
"boot_lvl2_upper": boot_lvl2_upper,
}
def write_image(self, path: Path) -> None:
with path.open("wb") as f:
header_binary = pack(self._header_struct_fmt, *self._header)
offset = 0
for addr, data in self._regions:
header_binary += pack(self._region_struct_fmt, addr, len(data), offset, 1)
offset += len(data)
# Finally write everything out to a file.
f.write(self._image)
f.write(header_binary)
for _, data in self._regions:
f.write(data)