tests: Migrated 10.linux-boot scons-based test to testlib

This test has purposely been designed to be easily extendible for future
x86 boot tests. Right now, it only runs two basic Ubuntu boot test
setups.

Change-Id: I81385b5dfc0764af2ec02999eb26e523bd09a595
Issue-on: https://gem5.atlassian.net/browse/GEM5-109
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/26324
Reviewed-by: Jason Lowe-Power <power.jg@gmail.com>
Maintainer: Jason Lowe-Power <power.jg@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Bobby R. Bruce
2020-03-05 11:56:11 -08:00
parent 95e6767601
commit 0ea4313255
4 changed files with 616 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
# Copyright (c) 2016 Jason Lowe-Power
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# 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;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
# OWNER 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.
import argparse
import sys
import os
import m5
import m5.ticks
from m5.objects import *
sys.path.append(os.path.dirname(__file__) + '/system')
sys.path.append(os.path.dirname(__file__) + '/../../../configs/common/')
from system import *
parser = argparse.ArgumentParser(description="")
parser.add_argument('--kernel', type=str)
parser.add_argument('--disk', type=str)
parser.add_argument('--cpu-type', choices=['atomic', 'kvm', 'o3', 'simple',])
parser.add_argument('--num-cpus', type=int)
parser.add_argument('--boot-type', choices=['init', 'systemd',])
#(options, args) = parser.parse_args()
args = parser.parse_args()
# create the system we are going to simulate
system = MySystem(args.kernel, args.disk, args.cpu_type, args.num_cpus)
if args.boot_type == "init":
# Simply run "exit.sh"
system.boot_osflags += ' init=/root/exit.sh'
else:
if args.boot_type != "systemd":
m5.fatal("Bad option for boot_type. init or systemd.")
# set up the root SimObject and start the simulation
root = Root(full_system = True, system = system)
if system.getHostParallel():
# Required for running kvm on multiple host cores.
# Uses gem5's parallel event queue feature
# Note: The simulator is quite picky about this number!
root.sim_quantum = int(1e9) # 1 ms
# instantiate all of the objects we've created above
m5.instantiate()
print("Running the simulation")
exit_event = m5.simulate()
if exit_event.getCause() != "m5_exit instruction encountered":
print("Failed to exit correctly")
exit(1)
else:
print("Success!")
exit(0)

View File

@@ -0,0 +1,143 @@
# Copyright (c) 2016 Jason Lowe-Power
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# 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;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
# OWNER 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.
""" Caches with options for a simple gem5 configuration script
This file contains L1 I/D and L2 caches to be used in the simple
gem5 configuration script.
"""
import m5
from m5.objects import Cache, L2XBar, StridePrefetcher, SubSystem
from m5.params import AddrRange, AllMemory, MemorySize
from m5.util.convert import toMemorySize
# Some specific options for caches
# For all options see src/mem/cache/BaseCache.py
class PrefetchCache(Cache):
def __init__(self, options):
super(PrefetchCache, self).__init__()
class L1Cache(PrefetchCache):
"""Simple L1 Cache with default values"""
assoc = 8
tag_latency = 1
data_latency = 1
response_latency = 1
mshrs = 16
tgts_per_mshr = 20
writeback_clean = True
def __init__(self, options=None):
super(L1Cache, self).__init__(options)
pass
def connectBus(self, bus):
"""Connect this cache to a memory-side bus"""
self.mem_side = bus.slave
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU-side port
This must be defined in a subclass"""
raise NotImplementedError
class L1ICache(L1Cache):
"""Simple L1 instruction cache with default values"""
# Set the size
size = '32kB'
def __init__(self, opts=None):
super(L1ICache, self).__init__(opts)
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU icache port"""
self.cpu_side = cpu.icache_port
class L1DCache(L1Cache):
"""Simple L1 data cache with default values"""
# Set the size
size = '32kB'
def __init__(self, opts=None):
super(L1DCache, self).__init__(opts)
def connectCPU(self, cpu):
"""Connect this cache's port to a CPU dcache port"""
self.cpu_side = cpu.dcache_port
class MMUCache(Cache):
# Default parameters
size = '8kB'
assoc = 4
tag_latency = 1
data_latency = 1
response_latency = 1
mshrs = 20
tgts_per_mshr = 12
writeback_clean = True
def __init__(self):
super(MMUCache, self).__init__()
def connectCPU(self, cpu):
"""Connect the CPU itb and dtb to the cache
Note: This creates a new crossbar
"""
self.mmubus = L2XBar()
self.cpu_side = self.mmubus.master
for tlb in [cpu.itb, cpu.dtb]:
self.mmubus.slave = tlb.walker.port
def connectBus(self, bus):
"""Connect this cache to a memory-side bus"""
self.mem_side = bus.slave
class L2Cache(PrefetchCache):
"""Simple L2 Cache with default values"""
# Default parameters
size = '256kB'
assoc = 16
tag_latency = 10
data_latency = 10
response_latency = 1
mshrs = 20
tgts_per_mshr = 12
writeback_clean = True
def __init__(self, opts=None):
super(L2Cache, self).__init__(opts)
def connectCPUSideBus(self, bus):
self.cpu_side = bus.master
def connectMemSideBus(self, bus):
self.mem_side = bus.slave

View File

@@ -0,0 +1,322 @@
# Copyright (c) 2016 Jason Lowe-Power
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# 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;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
# OWNER 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.
import m5
from m5.objects import *
from m5.util import convert
from caches import *
import sys
class MySystem(LinuxX86System):
def __init__(self, kernel, disk, cpu_type, num_cpus):
super(MySystem, self).__init__()
self._host_parallel = cpu_type == "kvm"
# Set up the clock domain and the voltage domain
self.clk_domain = SrcClockDomain()
self.clk_domain.clock = '3GHz'
self.clk_domain.voltage_domain = VoltageDomain()
self.mem_ranges = [AddrRange(Addr('3GB')), # All data
AddrRange(0xC0000000, size=0x100000), # For I/0
]
# Create the main memory bus
# This connects to main memory
self.membus = SystemXBar(width = 64) # 64-byte width
self.membus.badaddr_responder = BadAddr()
self.membus.default = Self.badaddr_responder.pio
# Set up the system port for functional access from the simulator
self.system_port = self.membus.slave
self.initFS(self.membus, num_cpus)
# Replace these paths with the path to your disk images.
# The first disk is the root disk. The second could be used for swap
# or anything else.
self.setDiskImages(disk, disk)
# Change this path to point to the kernel you want to use
self.kernel = kernel
# Options specified on the kernel command line
boot_options = ['earlyprintk=ttyS0', 'console=ttyS0', 'lpj=7999923',
'root=/dev/hda1']
self.boot_osflags = ' '.join(boot_options)
# Create the CPUs for our system.
self.createCPU(cpu_type, num_cpus)
# Create the cache heirarchy for the system.
self.createCacheHierarchy()
# Set up the interrupt controllers for the system (x86 specific)
self.setupInterrupts()
self.createMemoryControllersDDR3()
if self._host_parallel:
# To get the KVM CPUs to run on different host CPUs
# Specify a different event queue for each CPU
for i,cpu in enumerate(self.cpu):
for obj in cpu.descendants():
obj.eventq_index = 0
cpu.eventq_index = i + 1
def getHostParallel(self):
return self._host_parallel
def totalInsts(self):
return sum([cpu.totalInsts() for cpu in self.cpu])
def createCPU(self, cpu_type, num_cpus):
if cpu_type == "atomic":
self.cpu = [AtomicSimpleCPU(cpu_id = i)
for i in range(num_cpus)]
self.mem_mode = 'atomic'
elif cpu_type == "kvm":
# Note KVM needs a VM and atomic_noncaching
self.cpu = [X86KvmCPU(cpu_id = i)
for i in range(num_cpus)]
self.kvm_vm = KvmVM()
self.mem_mode = 'atomic_noncaching'
elif cpu_type == "o3":
self.cpu = [DerivO3CPU(cpu_id = i)
for i in range(num_cpus)]
self.mem_mode = 'timing'
elif cpu_type == "simple":
self.cpu = [TimingSimpleCPU(cpu_id = i)
for i in range(num_cpus)]
self.mem_mode = 'timing'
else:
m5.fatal("No CPU type {}".format(cpu_type))
map(lambda c: c.createThreads(), self.cpu)
def setDiskImages(self, img_path_1, img_path_2):
disk0 = CowDisk(img_path_1)
disk2 = CowDisk(img_path_2)
self.pc.south_bridge.ide.disks = [disk0, disk2]
def createCacheHierarchy(self):
for cpu in self.cpu:
# Create a memory bus, a coherent crossbar, in this case
cpu.l2bus = L2XBar()
# Create an L1 instruction and data cache
cpu.icache = L1ICache()
cpu.dcache = L1DCache()
cpu.mmucache = MMUCache()
# Connect the instruction and data caches to the CPU
cpu.icache.connectCPU(cpu)
cpu.dcache.connectCPU(cpu)
cpu.mmucache.connectCPU(cpu)
# Hook the CPU ports up to the l2bus
cpu.icache.connectBus(cpu.l2bus)
cpu.dcache.connectBus(cpu.l2bus)
cpu.mmucache.connectBus(cpu.l2bus)
# Create an L2 cache and connect it to the l2bus
cpu.l2cache = L2Cache()
cpu.l2cache.connectCPUSideBus(cpu.l2bus)
# Connect the L2 cache to the L3 bus
cpu.l2cache.connectMemSideBus(self.membus)
def setupInterrupts(self):
for cpu in self.cpu:
# create the interrupt controller CPU and connect to the membus
cpu.createInterruptController()
# For x86 only, connect interrupts to the memory
# Note: these are directly connected to the memory bus and
# not cached
cpu.interrupts[0].pio = self.membus.master
cpu.interrupts[0].int_master = self.membus.slave
cpu.interrupts[0].int_slave = self.membus.master
def createMemoryControllersDDR3(self):
self._createMemoryControllers(1, DDR3_1600_8x8)
def _createMemoryControllers(self, num, cls):
self.mem_cntrls = [
cls(range = self.mem_ranges[0],
port = self.membus.master)
for i in range(num)
]
def initFS(self, membus, cpus):
self.pc = Pc()
# Constants similar to x86_traits.hh
IO_address_space_base = 0x8000000000000000
pci_config_address_space_base = 0xc000000000000000
interrupts_address_space_base = 0xa000000000000000
APIC_range_size = 1 << 12;
# North Bridge
self.iobus = IOXBar()
self.bridge = Bridge(delay='50ns')
self.bridge.master = self.iobus.slave
self.bridge.slave = membus.master
# Allow the bridge to pass through:
# 1) kernel configured PCI device memory map address: address range
# [0xC0000000, 0xFFFF0000). (The upper 64kB are reserved for m5ops.)
# 2) the bridge to pass through the IO APIC (two pages, already
# contained in 1),
# 3) everything in the IO address range up to the local APIC, and
# 4) then the entire PCI address space and beyond.
self.bridge.ranges = \
[
AddrRange(0xC0000000, 0xFFFF0000),
AddrRange(IO_address_space_base,
interrupts_address_space_base - 1),
AddrRange(pci_config_address_space_base,
Addr.max)
]
# Create a bridge from the IO bus to the memory bus to allow access
# to the local APIC (two pages)
self.apicbridge = Bridge(delay='50ns')
self.apicbridge.slave = self.iobus.master
self.apicbridge.master = membus.slave
self.apicbridge.ranges = [AddrRange(interrupts_address_space_base,
interrupts_address_space_base +
cpus * APIC_range_size
- 1)]
# connect the io bus
self.pc.attachIO(self.iobus)
# Add a tiny cache to the IO bus.
# This cache is required for the classic memory model for coherence
self.iocache = Cache(assoc=8,
tag_latency = 50,
data_latency = 50,
response_latency = 50,
mshrs = 20,
size = '1kB',
tgts_per_mshr = 12,
addr_ranges = self.mem_ranges)
self.iocache.cpu_side = self.iobus.master
self.iocache.mem_side = self.membus.slave
self.intrctrl = IntrControl()
###############################################
# Add in a Bios information structure.
self.smbios_table.structures = [X86SMBiosBiosInformation()]
# Set up the Intel MP table
base_entries = []
ext_entries = []
for i in range(cpus):
bp = X86IntelMPProcessor(
local_apic_id = i,
local_apic_version = 0x14,
enable = True,
bootstrap = (i ==0))
base_entries.append(bp)
io_apic = X86IntelMPIOAPIC(
id = cpus,
version = 0x11,
enable = True,
address = 0xfec00000)
self.pc.south_bridge.io_apic.apic_id = io_apic.id
base_entries.append(io_apic)
pci_bus = X86IntelMPBus(bus_id = 0, bus_type='PCI ')
base_entries.append(pci_bus)
isa_bus = X86IntelMPBus(bus_id = 1, bus_type='ISA ')
base_entries.append(isa_bus)
connect_busses = X86IntelMPBusHierarchy(bus_id=1,
subtractive_decode=True, parent_bus=0)
ext_entries.append(connect_busses)
pci_dev4_inta = X86IntelMPIOIntAssignment(
interrupt_type = 'INT',
polarity = 'ConformPolarity',
trigger = 'ConformTrigger',
source_bus_id = 0,
source_bus_irq = 0 + (4 << 2),
dest_io_apic_id = io_apic.id,
dest_io_apic_intin = 16)
base_entries.append(pci_dev4_inta)
def assignISAInt(irq, apicPin):
assign_8259_to_apic = X86IntelMPIOIntAssignment(
interrupt_type = 'ExtInt',
polarity = 'ConformPolarity',
trigger = 'ConformTrigger',
source_bus_id = 1,
source_bus_irq = irq,
dest_io_apic_id = io_apic.id,
dest_io_apic_intin = 0)
base_entries.append(assign_8259_to_apic)
assign_to_apic = X86IntelMPIOIntAssignment(
interrupt_type = 'INT',
polarity = 'ConformPolarity',
trigger = 'ConformTrigger',
source_bus_id = 1,
source_bus_irq = irq,
dest_io_apic_id = io_apic.id,
dest_io_apic_intin = apicPin)
base_entries.append(assign_to_apic)
assignISAInt(0, 2)
assignISAInt(1, 1)
for i in range(3, 15):
assignISAInt(i, i)
self.intel_mp_table.base_entries = base_entries
self.intel_mp_table.ext_entries = ext_entries
entries = \
[
# Mark the first megabyte of memory as reserved
X86E820Entry(addr = 0, size = '639kB', range_type = 1),
X86E820Entry(addr = 0x9fc00, size = '385kB', range_type = 2),
# Mark the rest of physical memory as available
X86E820Entry(addr = 0x100000,
size = '%dB' % (self.mem_ranges[0].size() - 0x100000),
range_type = 1),
]
# Reserve the last 16kB of the 32-bit address space for m5ops
entries.append(X86E820Entry(addr = 0xFFFF0000, size = '64kB',
range_type=2))
self.e820_table.entries = entries
class CowDisk(IdeDisk):
def __init__(self, filename):
super(CowDisk, self).__init__()
self.driveID = 'master'
self.image = CowDiskImage(child=RawDiskImage(read_only=True),
read_only=False)
self.image.child.image_file = filename

View File

@@ -0,0 +1,72 @@
# Copyright (c) 2020 The Regents of the University of California
# All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# 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;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
# OWNER 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.
import os
from testlib import *
if config.bin_path:
base_path = config.bin_path
else:
base_path = joinpath(absdirpath(__file__), '..', 'resources',
'ubuntu-boot')
image_url = 'http://dist.gem5.org/images/x86/ubuntu-18-04/base.img'
kernel_url = 'http://dist.gem5.org/kernels/x86/static/vmlinux-4.19.83'
image_name = 'ubuntu-18-04-base.img'
kernel_name = 'vmlinux-4.19.83' # 4.19 is LTS (Projected EOL: Dec, 2020)
image = DownloadedProgram(image_url, base_path, image_name)
kernel = DownloadedProgram(kernel_url, base_path, kernel_name)
def test_boot(cpu_type, num_cpus, boot_type):
gem5_verify_config(
name = 'test-ubuntu_boot-' + cpu_type + '_cpu-' + num_cpus + '_cpus-'
+ boot_type + '_boot',
verifiers = (),
fixtures = (image, kernel,),
config = joinpath(joinpath(absdirpath(__file__), 'run_exit.py')),
config_args = [
'--kernel', joinpath(base_path, kernel_name),
'--disk', joinpath(base_path, image_name),
'--cpu-type', cpu_type,
'--num-cpus', num_cpus,
'--boot-type', boot_type,
],
valid_isas = ('X86',),
valid_hosts = constants.supported_hosts,
length = constants.long_tag,
)
# Test every CPU type
cpu_types = ('atomic', 'simple',)
for cpu_type in cpu_types:
test_boot(cpu_type, '1', 'init')
# Test a multicore system
test_boot('atomic', '4', 'systemd')