From 7a1766d3b6b1b9fcab6ff3664b498d7f49094b80 Mon Sep 17 00:00:00 2001 From: Daniel Leung Date: Fri, 26 Feb 2021 15:59:29 -0800 Subject: [PATCH] boards: x86: add qemu_x86_virt to test running in virtual space This adds a new qemu_x86_virt board where code and data are mapped in virtual address space and is actually executing within virtual address space. Signed-off-by: Daniel Leung --- boards/x86/qemu_x86/CMakeLists.txt | 9 + boards/x86/qemu_x86/board.cmake | 4 + boards/x86/qemu_x86/qemu_x86_virt.dts | 7 + boards/x86/qemu_x86/qemu_x86_virt.yaml | 13 ++ boards/x86/qemu_x86/qemu_x86_virt_defconfig | 25 +++ .../qemu_x86/scripts/update_elf_load_addr.py | 185 ++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 boards/x86/qemu_x86/qemu_x86_virt.dts create mode 100644 boards/x86/qemu_x86/qemu_x86_virt.yaml create mode 100644 boards/x86/qemu_x86/qemu_x86_virt_defconfig create mode 100644 boards/x86/qemu_x86/scripts/update_elf_load_addr.py diff --git a/boards/x86/qemu_x86/CMakeLists.txt b/boards/x86/qemu_x86/CMakeLists.txt index 1131a5c7ce9..2c24f0fcd62 100644 --- a/boards/x86/qemu_x86/CMakeLists.txt +++ b/boards/x86/qemu_x86/CMakeLists.txt @@ -8,3 +8,12 @@ set_property(GLOBAL APPEND PROPERTY extra_post_build_commands WORKING_DIRECTORY ${PROJECT_BINARY_DIR} ) endif() + +if("${BOARD}" STREQUAL "qemu_x86_virt") + set_property(GLOBAL APPEND PROPERTY extra_post_build_commands + COMMAND ${PYTHON_EXECUTABLE} ${BOARD_DIR}/scripts/update_elf_load_addr.py + --kernel ${PROJECT_BINARY_DIR}/${CONFIG_KERNEL_BIN_NAME}.elf + --output ${PROJECT_BINARY_DIR}/${BOARD}.elf + $<$:--verbose> + ) +endif() diff --git a/boards/x86/qemu_x86/board.cmake b/boards/x86/qemu_x86/board.cmake index ca0bf054fc4..3484072c13e 100644 --- a/boards/x86/qemu_x86/board.cmake +++ b/boards/x86/qemu_x86/board.cmake @@ -65,6 +65,10 @@ if(NOT CONFIG_ACPI) list(APPEND QEMU_FLAGS_${ARCH} -no-acpi) endif() +if("${BOARD}" STREQUAL "qemu_x86_virt") + set(QEMU_KERNEL_OPTION "-kernel;${ZEPHYR_BINARY_DIR}/${BOARD}.elf") +endif() + # TODO: Support debug # board_set_debugger_ifnset(qemu) # debugserver: QEMU_EXTRA_FLAGS += -s -S diff --git a/boards/x86/qemu_x86/qemu_x86_virt.dts b/boards/x86/qemu_x86/qemu_x86_virt.dts new file mode 100644 index 00000000000..109f898b20d --- /dev/null +++ b/boards/x86/qemu_x86/qemu_x86_virt.dts @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2021 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "qemu_x86.dts" diff --git a/boards/x86/qemu_x86/qemu_x86_virt.yaml b/boards/x86/qemu_x86/qemu_x86_virt.yaml new file mode 100644 index 00000000000..637ad73f19c --- /dev/null +++ b/boards/x86/qemu_x86/qemu_x86_virt.yaml @@ -0,0 +1,13 @@ +identifier: qemu_x86_virt +name: QEMU Emulation for X86 (Run in Virtual Address Space) +type: qemu +arch: x86 +simulation: qemu +toolchain: + - zephyr + - xtools +testing: + default: true + only_tags: + - kernel + - userspace diff --git a/boards/x86/qemu_x86/qemu_x86_virt_defconfig b/boards/x86/qemu_x86/qemu_x86_virt_defconfig new file mode 100644 index 00000000000..c5ff257b96a --- /dev/null +++ b/boards/x86/qemu_x86/qemu_x86_virt_defconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SOC_IA32=y +CONFIG_BOARD_QEMU_X86=y +CONFIG_HPET_TIMER=y +CONFIG_PIC_DISABLE=y +CONFIG_LOAPIC=y +CONFIG_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_NS16550=y +CONFIG_UART_CONSOLE=y +CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=25000000 +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_X86_MMU=y +CONFIG_DEBUG_INFO=y +CONFIG_SCHED_SCALABLE=y +CONFIG_WAITQ_SCALABLE=y +CONFIG_X86_VERY_EARLY_CONSOLE=y +CONFIG_QEMU_ICOUNT_SHIFT=5 + +CONFIG_SRAM_OFFSET=0x100000 +CONFIG_KERNEL_VM_SIZE=0x400000 +CONFIG_KERNEL_VM_BASE=0x40000000 +CONFIG_KERNEL_VM_OFFSET=0x100000 +CONFIG_KERNEL_LINK_IN_VIRT=y diff --git a/boards/x86/qemu_x86/scripts/update_elf_load_addr.py b/boards/x86/qemu_x86/scripts/update_elf_load_addr.py new file mode 100644 index 00000000000..ace735b8aab --- /dev/null +++ b/boards/x86/qemu_x86/scripts/update_elf_load_addr.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +"""Update ELF Load Address + +This updates the entry and physical load addresses in ELF file +so that QEMU is able to load and run the Zephyr kernel even +though the kernel is linked in virtual address space. + +When the Zephyr kernel is linked in virtual address space, +the ELF file only contains virtual addresses which may result +in QEMU loading code and data into non-existent physical memory +if both physical and virtual address space do not start at +the same address. This script modifies the physical addresses +of the load segments so QEMU will place code and data in +physical memory. This also updates the entry address to physical +address so QEMU can jump to it to start the Zephyr kernel. +""" + +import argparse +import os +import sys +import struct + +from ctypes import create_string_buffer +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import SymbolTableSection + + +# ELF Header, load entry offset +ELF_HDR_ENTRY_OFFSET = 0x18 + + +def log(text): + """Output log text if --verbose is used""" + if args.verbose: + sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") + + +def error(text): + """Output error message""" + sys.exit(os.path.basename(sys.argv[0]) + ": " + text) + + +def extract_all_symbols_from_elf(obj): + """Get all symbols from the ELF file""" + all_syms = {} + for section in obj.iter_sections(): + if isinstance(section, SymbolTableSection): + for sym in section.iter_symbols(): + all_syms[sym.name] = sym.entry.st_value + + if len(all_syms) == 0: + raise LookupError("Could not find symbol table") + + return all_syms + + +def parse_args(): + """Parse command line arguments""" + global args + + parser = argparse.ArgumentParser() + + parser.add_argument("-k", "--kernel", required=True, + help="Zephyr ELF binary") + parser.add_argument("-o", "--output", required=True, + help="Output path") + parser.add_argument("-v", "--verbose", action="store_true", + help="Print extra debugging information") + args = parser.parse_args() + + +def main(): + """Main function""" + global args + parse_args() + + if not os.path.exists(args.kernel): + error("{0} does not exist.".format(args.kernel)) + + elf_fd = open(args.kernel, "rb") + + # Create a modifiable byte stream + raw_elf = elf_fd.read() + output = create_string_buffer(raw_elf) + + elf = ELFFile(elf_fd) + + if not elf.has_dwarf_info(): + error("ELF file has no DWARF information") + + if elf.num_segments() == 0: + error("ELF file has no program header table") + + syms = extract_all_symbols_from_elf(elf) + + vm_base = syms["CONFIG_KERNEL_VM_BASE"] + vm_size = syms["CONFIG_KERNEL_VM_SIZE"] + + sram_base = syms["CONFIG_SRAM_BASE_ADDRESS"] + sram_size = syms["CONFIG_SRAM_SIZE"] * 1024 + + vm_offset = syms["CONFIG_KERNEL_VM_OFFSET"] + sram_offset = syms.get("CONFIG_SRAM_OFFSET", 0) + + # + # Calculate virtual-to-physical address translation + # + virt_to_phys_offset = (sram_base + sram_offset) - (vm_base + vm_offset) + + log("Virtual address space: 0x%x - 0x%x size 0x%x (offset 0x%x)" % + (vm_base, vm_base + vm_size, vm_size, vm_offset)) + log("Physical address space: 0x%x - 0x%x size 0x%x (offset 0x%x)" % + (sram_base, sram_base + sram_size, sram_size, sram_offset)) + + # + # Update the entry address in header + # + if elf.elfclass == 32: + load_entry_type = "I" + else: + load_entry_type = "Q" + + entry_virt = struct.unpack_from(load_entry_type, output, ELF_HDR_ENTRY_OFFSET)[0] + entry_phys = entry_virt + virt_to_phys_offset + struct.pack_into(load_entry_type, output, ELF_HDR_ENTRY_OFFSET, entry_phys) + + log("Entry Address: 0x%x -> 0x%x" % (entry_virt, entry_phys)) + + # + # Update load address in program header segments + # + + # Program header segment offset from beginning of file + ph_off = elf.header['e_phoff'] + + # ph_seg_type: segment type and other fields before virtual address + # ph_seg_addr: virtual and phyiscal addresses + # ph_seg_whole: whole segment + if elf.elfclass == 32: + ph_seg_type = "II" + ph_seg_addr = "II" + ph_seg_whole = "IIIIIIII" + else: + ph_seg_type = "IIQ" + ph_seg_addr = "QQ" + ph_seg_whole = "IIQQQQQQ" + + # Go through all segments + for ph_idx in range(elf.num_segments()): + seg_off = ph_off + struct.calcsize(ph_seg_whole) * ph_idx + + seg_type, _ = struct.unpack_from(ph_seg_type, output, seg_off) + + # Only process LOAD segments + if seg_type != 0x01: + continue + + # Add offset to get to the addresses + addr_off = seg_off + struct.calcsize(ph_seg_type) + + # Grab virtual and physical addresses + seg_vaddr, seg_paddr = struct.unpack_from(ph_seg_addr, output, addr_off) + + # Apply virt-to-phys offset so it will load into + # physical address + seg_paddr_new = seg_vaddr + virt_to_phys_offset + + log("Segment %d: physical address 0x%x -> 0x%x" % (ph_idx, seg_paddr, seg_paddr_new)) + + # Put the addresses back + struct.pack_into(ph_seg_addr, output, addr_off, seg_vaddr, seg_paddr_new) + + out_fd = open(args.output, "wb") + out_fd.write(output) + out_fd.close() + + elf_fd.close() + +if __name__ == "__main__": + main()