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 <daniel.leung@intel.com>
This commit is contained in:
Daniel Leung 2021-02-26 15:59:29 -08:00 committed by Anas Nashif
commit 7a1766d3b6
6 changed files with 243 additions and 0 deletions

View file

@ -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
$<$<BOOL:${CMAKE_VERBOSE_MAKEFILE}>:--verbose>
)
endif()

View file

@ -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

View file

@ -0,0 +1,7 @@
/*
* Copyright (c) 2021 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "qemu_x86.dts"

View file

@ -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

View file

@ -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

View file

@ -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()