stdlib: Create 'KernelDiskWorkload' abstract class

The purpose of this class is to:

* Create a standard way to set a typical kernel/disk image workload.
* Cleans up the manner in which readfile values/contents are set.
* Allows a user to specify their own kernel arguments, but still sets
sensible defaults.

As of this commit, this interface has been added to the X86Board and the
RISCVBoard.

Change-Id: I34f4c2b829f1ae5c1cae12039436cbb345a89d09
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/51949
Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu>
Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Bobby R. Bruce
2021-10-22 12:38:10 -07:00
parent 03f93d40b2
commit 1b0b59f49f
11 changed files with 267 additions and 173 deletions

View File

@@ -81,8 +81,10 @@ board = RiscvBoard(
board.connect_things()
# Set the Full System workload.
board.set_workload(disk_image=Resource("riscv-disk-img"),
bootloader=Resource("riscv-bootloader-vmlinux-5.10"))
board.set_kernel_disk_workload(
kernel=Resource("riscv-bootloader-vmlinux-5.10"),
disk_image=Resource("riscv-disk-img"),
)
root = Root(full_system=True, system=board)

View File

@@ -107,8 +107,8 @@ board = X86Board(
board.connect_things()
# Here we set the Full System workload.
# The `set_workload` function for the X86Board takes a kernel, a disk image,
# and, optionally, a command to run.
# The `set_kernel_disk_workload` function for the X86Board takes a kernel, a
# disk image, and, optionally, a command to run.
# This is the command to run after the system has booted. The first `m5 exit`
# will stop the simulation so we can switch the CPU cores from KVM to timing
@@ -121,7 +121,7 @@ command = "m5 exit;" \
+ "sleep 1;" \
+ "m5 exit;"
board.set_workload(
board.set_kernel_disk_workload(
# The x86 linux kernel will be automatically downloaded to the
# `tests/gem5/resources` directory if not already present.
kernel=Resource(
@@ -158,9 +158,10 @@ board.set_workload(
),
override=True,
),
command=command,
readfile_contents=command,
)
root = Root(full_system=True, system=board)
root.sim_quantum = int(1e9) # sim_quantum must be st if KVM cores are used.

View File

@@ -40,6 +40,8 @@ PySource('gem5.components.boards', 'gem5/components/boards/riscv_board.py')
PySource('gem5.components.boards', 'gem5/components/boards/simple_board.py')
PySource('gem5.components.boards', 'gem5/components/boards/test_board.py')
PySource('gem5.components.boards', 'gem5/components/boards/x86_board.py')
PySource('gem5.components.boards',
"gem5/components/boards/kernel_disk_workload.py")
PySource('gem5.components.cachehierarchies',
'gem5/components/cachehierarchies/__init__.py')
PySource('gem5.components.cachehierarchies',

View File

@@ -0,0 +1,178 @@
# Copyright (c) 2021 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.
from abc import abstractmethod
from ...resources.resource import AbstractResource
from typing import List, Optional
import os
import m5
class KernelDiskWorkload:
"""
The purpose of this abstract class is to enable a full-system boot
consisting of of a kernel which will then load a disk image.
For this to function correctly, the KernelDiskWorkload class should be
added as a superclass to a board and the abstract methods implemented.
E.g.:
```
class X86Board(AbstractBoard, KernelDiskWorkload):
...
@overrides(KernelDiskWorkload)
def get_default_kernel_args(self) -> List[str]:
return [
"earlyprintk=ttyS0",
"console=ttyS0",
"lpj=7999923",
"root={root_value}",
]
...
```
Notes
-----
* This assumes only one disk is set.
* This assumes the Linux kernel is used.
"""
@abstractmethod
def get_default_kernel_args(self) -> List[str]:
"""
Returns a default list of arguments for the workload kernel. We assume
the following strings may be used as placeholders, to be replaced when
`set_kernel_disk_workload` is executed:
* `{root_value}` : set to `get_default_kernel_root_val()`.
:returns: A default list of arguments for the workload kernel.
"""
raise NotImplementedError
@abstractmethod
def get_disk_device(self) -> str:
"""
Get the disk device, e.g., "/dev/sda", where the disk image is placed.
:returns: The disk device.
"""
raise NotImplementedError
@abstractmethod
def _add_disk_to_board(self, disk_image: AbstractResource) -> None:
"""
Sets the configuration needed to add the disk image to the board.
**Note:** This will be executed at the end of the
`set_kernel_disk_workload` function.
:param disk_image: The disk image to add to the system.
"""
raise NotImplementedError
def get_disk_root_partition(
cls, disk_image: AbstractResource
) -> Optional[str]:
"""
Obtains the root partition of a disk image by inspecting the resource's
metadata.
:returns: The disk image's root partition.
"""
try:
return disk_image.get_metadata()["additional_metadata"][
"root_partition"
]
except KeyError:
return None
def get_default_kernel_root_val(self, disk_image: AbstractResource) -> str:
"""
Get the default kernel root value to be passed to the kernel. This is
determined by the value implemented in the `get_disk_device()`
function, and the disk image partition, obtained from
`get_disk_root_partition()`
:param disk_image: The disk image to be added to the system.
:returns: The default value for the 'root' argument to be passed to the
kernel.
"""
return self.get_disk_device() + (
self.get_disk_root_partition(disk_image) or ""
)
def set_kernel_disk_workload(
self,
kernel: AbstractResource,
disk_image: AbstractResource,
readfile: Optional[str] = None,
readfile_contents: Optional[str] = None,
kernel_args: Optional[List[str]] = None,
) -> None:
"""
This function allows the setting of a full-system run with a Kernel
and a disk image.
:param kernel: The kernel to boot.
:param disk_image: The disk image to mount.
:param readfile: An optional parameter stating the file to be read by
by `m5 readfile`.
:param readfile_contents: An optional parameter stating the contents of
the readfile file. If set with `readfile`, the contents of `readfile`
will be overwritten with `readfile_contents`, otherwise a new file will
be created with the value of `readfile_contents`.
:param kernel_args: An optional parameter for setting arguments to be
passed to the kernel. By default set to `get_default_kernel_args()`.
"""
# Set the kernel to use.
self.workload.object_file = kernel.get_local_path()
# Set the arguments to be passed to the kernel.
self.workload.command_line = (
" ".join(kernel_args or self.get_default_kernel_args())
).format(
root_value=self.get_default_kernel_root_val(disk_image=disk_image)
)
# Set the readfile.
if readfile:
self.readfile = readfile
elif readfile_contents:
self.readfile = os.path.join(m5.options.outdir, "readfile")
# Add the contents to the readfile, if specified.
if readfile_contents:
file = open(self.readfile, "w+")
file.write(readfile_contents)
file.close()
self._add_disk_to_board(disk_image=disk_image)

View File

@@ -25,10 +25,12 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
from typing import Optional, List
from typing import List
from ...utils.override import overrides
from .abstract_board import AbstractBoard
from .kernel_disk_workload import KernelDiskWorkload
from ..processors.abstract_processor import AbstractProcessor
from ..memory.abstract_memory_system import AbstractMemorySystem
from ..cachehierarchies.abstract_cache_hierarchy import AbstractCacheHierarchy
@@ -65,7 +67,7 @@ from m5.util.fdthelper import (
)
class RiscvBoard(AbstractBoard):
class RiscvBoard(AbstractBoard, KernelDiskWorkload):
"""
A board capable of full system simulation for RISC-V
@@ -204,73 +206,6 @@ class RiscvBoard(AbstractBoard):
# Incorporate the memory into the motherboard.
self.get_memory().incorporate_memory(self)
def set_workload(
self, bootloader: AbstractResource, disk_image: AbstractResource,
command: Optional[str] = None
) -> None:
"""Setup the full system files
See http://resources.gem5.org/resources/riscv-fs for the currently
tested kernels and OSes.
The command is an optional string to execute once the OS is fully
booted, assuming the disk image is setup to run `m5 readfile` after
booting.
After the workload is set up, this functino will generate the device
tree file and output it to the output directory.
**Limitations**
* Only supports a Linux kernel
* Disk must be configured correctly to use the command option
* This board doesn't support the command option
:param bootloader: The resource encapsulating the compiled bootloader
with the kernel as a payload
:param disk_image: The resource encapsulating the disk image containing
the OS data. The first partition should be the root partition.
:param command: The command(s) to run with bash once the OS is booted
"""
self.workload.object_file = bootloader.get_local_path()
image = CowDiskImage(
child=RawDiskImage(read_only=True), read_only=False
)
image.child.image_file = disk_image.get_local_path()
self.disk.vio.image = image
# Determine where the root exists in the disk image. This is done by
# inspecting the resource metadata.
root_val = "/dev/vda"
try:
partition_val = disk_image.get_metadata()["additional_metadata"]\
["root_partition"]
except KeyError:
partition_val = None
if partition_val is not None:
root_val += partition_val
self.workload.command_line = f"console=ttyS0 root={root_val} ro"
# Note: This must be called after set_workload because it looks for an
# attribute named "disk" and connects
self._setup_io_devices()
self._setup_pma()
# Default DTB address if bbl is built with --with-dts option
self.workload.dtb_addr = 0x87E00000
# We need to wait to generate the device tree until after the disk is
# set up. Now that the disk and workload are set, we can generate the
# device tree file.
self.generate_device_tree(m5.options.outdir)
self.workload.dtb_filename = os.path.join(
m5.options.outdir, "device.dtb"
)
def generate_device_tree(self, outdir: str) -> None:
"""Creates the dtb and dts files.
@@ -419,3 +354,33 @@ class RiscvBoard(AbstractBoard):
fdt.add_rootnode(root)
fdt.writeDtsFile(os.path.join(outdir, "device.dts"))
fdt.writeDtbFile(os.path.join(outdir, "device.dtb"))
@overrides(KernelDiskWorkload)
def get_disk_device(self):
return "/dev/vda"
@overrides(KernelDiskWorkload)
def _add_disk_to_board(self, disk_image: AbstractResource):
image = CowDiskImage(
child=RawDiskImage(read_only=True), read_only=False
)
image.child.image_file = disk_image.get_local_path()
self.disk.vio.image = image
# Note: The below is a bit of a hack. We need to wait to generate the
# device tree until after the disk is set up. Now that the disk and
# workload are set, we can generate the device tree file.
self._setup_io_devices()
self._setup_pma()
# Default DTB address if bbl is built with --with-dts option
self.workload.dtb_addr = 0x87E00000
self.generate_device_tree(m5.options.outdir)
self.workload.dtb_filename = os.path.join(
m5.options.outdir, "device.dtb"
)
@overrides(KernelDiskWorkload)
def get_default_kernel_args(self) -> List[str]:
return ["console=ttyS0", "root={root_value}", "ro"]

View File

@@ -25,14 +25,13 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from .kernel_disk_workload import KernelDiskWorkload
from ...resources.resource import AbstractResource
from ...utils.override import overrides
from .abstract_board import AbstractBoard
from ...isas import ISA
import m5
from m5.objects import (
Cache,
Pc,
AddrRange,
X86FsLinux,
@@ -64,7 +63,7 @@ import os
from typing import List, Optional, Sequence
class X86Board(AbstractBoard):
class X86Board(AbstractBoard, KernelDiskWorkload):
"""
A board capable of full system simulation for X86.
@@ -263,84 +262,6 @@ class X86Board(AbstractBoard):
# Incorporate the memory into the motherboard.
self.get_memory().incorporate_memory(self)
def set_workload(
self,
kernel: AbstractResource,
disk_image: AbstractResource,
command: Optional[str] = None,
kernel_args: List[str] = [],
):
"""Setup the full system files
See <url> for the currently tested kernels and OSes.
The command is an optional string to execute once the OS is fully
booted, assuming the disk image is setup to run `m5 readfile` after
booting.
**Limitations**
* Only supports a Linux kernel
* Disk must be configured correctly to use the command option
:param kernel: The compiled kernel binary resource
:param disk_image: A disk image resource containing the OS data. The
first partition should be the root partition.
:param command: The command(s) to run with bash once the OS is booted
:param kernel_args: Additional arguments to be passed to the kernel.
`earlyprintk=ttyS0 console=ttyS0 lpj=7999923
root=/dev/hda<partition_val>` are already passed (`<partition_val>` is
automatically inferred from resource metadata). This parameter is used
to pass additional arguments.
"""
# Set the Linux kernel to use.
self.workload.object_file = kernel.get_local_path()
# Determine where the root exists in the disk image. This is done by
# inspecting the resource metadata.
root_val = "/dev/hda"
try:
partition_val = disk_image.get_metadata()["additional_metadata"]\
["root_partition"]
except KeyError:
partition_val = None
if partition_val is not None:
root_val += partition_val
# Options specified on the kernel command line.
self.workload.command_line = " ".join(
[
"earlyprintk=ttyS0",
"console=ttyS0",
"lpj=7999923",
f"root={root_val}",
] + kernel_args
)
# Create the Disk image SimObject.
ide_disk = IdeDisk()
ide_disk.driveID = "device0"
ide_disk.image = CowDiskImage(
child=RawDiskImage(read_only=True), read_only=False
)
ide_disk.image.child.image_file = disk_image.get_local_path()
# Attach the SimObject to the system.
self.pc.south_bridge.ide.disks = [ide_disk]
# Set the script to be passed to the simulated system to execute after
# boot.
if command:
file_name = os.path.join(m5.options.outdir, "run")
bench_file = open(file_name, "w+")
bench_file.write(command)
bench_file.close()
# Set to the system readfile
self.readfile = file_name
@overrides(AbstractBoard)
def has_io_bus(self) -> bool:
return True
@@ -382,3 +303,28 @@ class X86Board(AbstractBoard):
data_range, # All data
AddrRange(0xC0000000, size=0x100000), # For I/0
]
@overrides(KernelDiskWorkload)
def get_disk_device(self):
return "/dev/hda"
@overrides(KernelDiskWorkload)
def _add_disk_to_board(self, disk_image: AbstractResource):
ide_disk = IdeDisk()
ide_disk.driveID = "device0"
ide_disk.image = CowDiskImage(
child=RawDiskImage(read_only=True), read_only=False
)
ide_disk.image.child.image_file = disk_image.get_local_path()
# Attach the SimObject to the system.
self.pc.south_bridge.ide.disks = [ide_disk]
@overrides(KernelDiskWorkload)
def get_default_kernel_args(self) -> List[str]:
return [
"earlyprintk=ttyS0",
"console=ttyS0",
"lpj=7999923",
"root={root_value}",
]

View File

@@ -204,7 +204,7 @@ motherboard = X86Board(
motherboard.connect_things()
# Set the Full System workload.
motherboard.set_workload(
motherboard.set_kernel_disk_workload(
kernel=Resource(
"x86-linux-kernel-5.4.49",
override=args.override_download,
@@ -215,7 +215,7 @@ motherboard.set_workload(
override=args.override_download,
resource_directory=args.resource_directory,
),
command=dedent(
readfile_contents=dedent(
"""
m5 exit # signal end of boot
m5 exit # exit in children and parent

View File

@@ -186,10 +186,10 @@ motherboard = X86Board(
motherboard.connect_things()
additional_kernal_args = [args.kernel_args]
kernal_args = motherboard.get_default_kernel_args() + [args.kernel_args]
# Set the Full System workload.
motherboard.set_workload(
motherboard.set_kernel_disk_workload(
kernel=Resource(
"x86-linux-kernel-5.4.49",
override=args.override_download,
@@ -201,8 +201,8 @@ motherboard.set_workload(
resource_directory=args.resource_directory,
),
# The first exit signals to switch processors.
command="m5 exit\nm5 exit\n",
kernel_args=additional_kernal_args,
readfile_contents="m5 exit\nm5 exit\n",
kernel_args=kernal_args,
)

View File

@@ -234,7 +234,7 @@ command = (
+ "m5 exit \n"
)
board.set_workload(
board.set_kernel_disk_workload(
kernel=Resource(
"x86-linux-kernel-5.4.49",
resource_directory=args.resource_directory,
@@ -245,7 +245,7 @@ board.set_workload(
resource_directory=args.resource_directory,
override=args.override_download,
),
command=command,
readfile_contents=command,
)
print("Running with ISA: " + get_runtime_isa().name)

View File

@@ -153,14 +153,14 @@ board = RiscvBoard(
board.connect_things()
# Set the Full System workload.
board.set_workload(
disk_image=Resource(
"riscv-disk-img",
board.set_kernel_disk_workload(
kernel=Resource(
"riscv-bootloader-vmlinux-5.10",
override=args.override_download,
resource_directory=args.resource_directory,
),
bootloader=Resource(
"riscv-bootloader-vmlinux-5.10",
disk_image=Resource(
"riscv-disk-img",
override=args.override_download,
resource_directory=args.resource_directory,
),

View File

@@ -194,12 +194,12 @@ motherboard = X86Board(
motherboard.connect_things()
additional_kernal_args = []
kernal_args = motherboard.get_default_kernel_args()
if args.boot_type == "init":
additional_kernal_args.append("init=/root/exit.sh")
kernal_args.append("init=/root/exit.sh")
# Set the Full System workload.
motherboard.set_workload(
motherboard.set_kernel_disk_workload(
kernel=Resource(
"x86-linux-kernel-5.4.49",
override=args.override_download,
@@ -210,7 +210,7 @@ motherboard.set_workload(
override=args.override_download,
resource_directory=args.resource_directory,
),
kernel_args=additional_kernal_args,
kernel_args=kernal_args,
)