Skip to content

Commit

Permalink
add initial version of hypervisor tooling
Browse files Browse the repository at this point in the history
Adds tooling to generated the hypervisor configuration (lua scripts)
using a yaml configuration file and a yaml model of this configuration
described in yaml and python. The reason for having the model "twice", is that
the yaml definition of the model describes how the configuration is parsed into
a python object model. The python model is required to do some postprocessing
of the configuration, before jinja2 is used to generate the final configuration.

The used hypervisor version can support additional features or require a different
generated configuration. For that reason the configuration model can be extended
by a configuration specialization bundled together with the hypervisor.
  • Loading branch information
MofX committed Nov 12, 2024
1 parent 134d2aa commit 500c957
Show file tree
Hide file tree
Showing 11 changed files with 1,190 additions and 0 deletions.
Empty file.
115 changes: 115 additions & 0 deletions ebcl/tools/hypervisor/config_gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import argparse
import logging
import os
from pathlib import Path

import jinja2
import yaml

from ebcl.common import init_logging, log_exception
from ebcl.common.files import resolve_file

from .schema_loader import BaseModel, FileReadProtocol, Schema, merge_dict


class BaseResolver:
"""
Resolve bases defined in the yaml config.
"""

def load(self, config_file: str, conf_dir: str) -> dict:
"""
Load config_file and all of its bases.
"""
config = {
"base": [config_file]
}
while config["base"]:
base_name = config["base"].pop(0)
base_path = resolve_file(
file=base_name, relative_base_dir=conf_dir)
old = config
config = self._load_file(base_path)
merge_dict(config, old)
return config

def _load_file(self, filename: str) -> dict:
with open(filename, "r", encoding="utf-8") as f:
return yaml.load(f, yaml.Loader)


class HvFileGenerator:
"""
Hypervisor configuration file generator
"""
config: BaseModel
schema: Schema
output_path: Path

def __init__(self, file: str, output_path: str, specialization: str | None = None) -> None:
""" Parse the yaml config file.
Args:
config_file (Path): Path to the yaml config file.
"""
self.output_path = Path(output_path)

""" Load yaml configuration. """
config_file = Path(file)

config = BaseResolver().load(config_file.name, str(config_file.parent))
del config["base"]

self.schema = Schema(specialization and Path(specialization) or None)
self.config = self.schema.parse_config(config)

def _render_template(self, outpath: Path, template: FileReadProtocol) -> None:
"""Render a template to target"""
template_obj = jinja2.Template(template.read_text("utf-8"), trim_blocks=True)

with outpath.open("w", encoding="utf-8") as f:
f.write(template_obj.render(config=self.config))

def create_files(self) -> None:
"""
Create three Files of HV
init.ned, io.cfg and modules.list
"""
self.output_path.mkdir(exist_ok=True)

for template in self.schema.templates:
base, ext = os.path.splitext(template.name)
if ext == ".j2":
logging.info("Rendering %s", base)
outpath = self.output_path / base
self._render_template(outpath, template)
else:
logging.info("Creating %s", template)
outpath = self.output_path / template.name
outpath.write_text(template.read_text("utf-8"))


@log_exception(call_exit=True)
def main() -> None:
""" Main entrypoint of EBcL hypervisor generator. """
init_logging()

logging.info('\n===================\n'
'EBcL Hypervisor Config File Generator\n'
'===================\n')

parser = argparse.ArgumentParser(
description='Create the config files for the hypervisor')
parser.add_argument('-s', '--specialization', type=str,
help='Path to hypervisor specialization directory')
parser.add_argument('config_file', type=str,
help='Path to the YAML configuration file')
parser.add_argument('output', type=str,
help='Path to the output directory')
args = parser.parse_args()

generator = HvFileGenerator(args.config_file, args.output, args.specialization)
generator.create_files()


if __name__ == "__main__":
main()
32 changes: 32 additions & 0 deletions ebcl/tools/hypervisor/data/io.cfg.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
local Res = Io.Res
local Hw = Io.Hw

local hw = Io.system_bus();

Io.Dt.add_children(hw, function()
{% for vbus in config.vbus %}
{% for dev in vbus.devices %}
{{dev.name}} = Hw.Device(function()
{% if dev.compatible %}
compatible = {"{{dev.compatible}}"}
{% endif %}
{%for mmio in dev.mmios %}
Resource.reg{{loop.index0}} = Res.mmio({{"0x%x"|format(mmio.address)}}, {{"0x%x"|format(mmio.address + mmio.size - 1)}}{% if mmio.cached %}, Io.Resource.F_cached_mem | Io.Resource.F_prefetchable{% endif %})
{% endfor %}
{%for irq in dev.irqs %}
Resource.irq{{loop.index0}} = Res.irq({{irq.irq + irq.offset}}, Io.Resource.Irq_type_{{irq.is_edge and "raising_edge" or "level_high"}})
{% endfor %}
end)
{% endfor %}
{% endfor %}
end)

Io.add_vbusses({
{% for vbus in config.vbus %}
{{vbus.name}} = Io.Vi.System_bus(function()
{% for dev in vbus.devices %}
{{dev.name}} = wrap(hw.{{dev.name}})
{% endfor %}
end),
{% endfor %}
})
45 changes: 45 additions & 0 deletions ebcl/tools/hypervisor/data/model.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://linux.elektrobit.com.com/model.schema.json",
"title": "Hypervisor Configuration Model",
"description": "Hypervisor configuration model",
"type": "object",
"additionalProperties": false,
"default": {},
"properties": {
"version": {
"type": "integer"
},
"classes": {
"type": "object",
"default": {},
"additionalProperties": {
"type": "object",
"additionalProperties": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"default": {"type": ["string", "number", "boolean", "array"]},
"aggregate": {
"enum": ["None", "list"],
"default": "None"
},
"optional": {"type": "boolean", "default": false},
"enum_values": {"type": "array", "items": {"type": "string"}}
}
}
}
},
"root": {
"type": "string",
"optional": false
},
"templates": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
26 changes: 26 additions & 0 deletions ebcl/tools/hypervisor/data/modules.list.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
default-kernel fiasco -serial_esc

entry hv-image
roottask moe rom/init.ned

module l4re
module ned
module cons
module io
{% if config.vms %}
module uvmm
{% endif %}
{% if config.vio_block %}
module virtio-block
{% endif %}
{% if config.vnets %}
module l4vio_net_p2p
{% endif %}

module init.ned
module io.cfg
module system.lua

{% for module in config.modules %}
module {{module}}
{% endfor %}
134 changes: 134 additions & 0 deletions ebcl/tools/hypervisor/data/schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# yaml-language-server: $schema=model.schema.json

version: 1

classes:
HVConfig:
vbus:
type: VBus
aggregate: list
default: []
cons:
type: Cons
optional: true
shms:
type: SHM
aggregate: list
default: []
vms:
type: VM
aggregate: list
default: []

VM:
name:
type: string
kernel:
type: string
ram:
type: integer
cpus:
type: integer
cmdline:
type: string
default: ""
initrd:
type: string
optional: true
dtb:
type: string
vbus:
type: string
optional: true
vmnets:
type: string
aggregate: list
default: []
shms:
type: string
aggregate: list
default: []
virtio_block:
type: VirtioBlockNode
optional: true
vnets:
type: string
aggregate: list
default: []


VirtioBlockNode:
servers:
type: string
aggregate: list
default: []

clients:
type: string
aggregate: list
default: []

SHM:
name:
type: string
size:
type: integer
address:
type: integer
optional: true

Cons:
default_vm:
type: string
optional: true

VBus:
name:
type: string
devices:
type: Device
aggregate: list

Device:
name:
type: string
compatible:
type: string
optional: true
mmios:
type: MMIO
aggregate: list
optional: true
irqs:
type: IRQ
aggregate: list
optional: true
MMIO:
address:
type: integer
size:
type: integer
cached:
type: boolean
default: false
IRQ:
irq:
type: integer
type:
type: enum
enum_values:
- SGI
- PPI
- SPI
default: SPI
trigger:
type: enum
enum_values:
- level_high
- rising_edge

root: HVConfig

templates:
- io.cfg.j2
- system.lua
Loading

0 comments on commit 500c957

Please sign in to comment.