diff --git a/configs/example/riscv/fs_linux.py b/configs/example/riscv/fs_linux.py index 3d400617ff..28e6714c8c 100644 --- a/configs/example/riscv/fs_linux.py +++ b/configs/example/riscv/fs_linux.py @@ -40,6 +40,7 @@ import optparse import sys +from os import path import m5 from m5.defines import buildEnv @@ -62,6 +63,43 @@ from common import ObjectList from common.Caches import * from common import Options + +def generateMemNode(state, mem_range): + node = FdtNode("memory@%x" % int(mem_range.start)) + node.append(FdtPropertyStrings("device_type", ["memory"])) + node.append(FdtPropertyWords("reg", + state.addrCells(mem_range.start) + + state.sizeCells(mem_range.size()) )) + return node + +def generateDtb(system): + """ + Autogenerate DTB. Arguments are the folder where the DTB + will be stored, and the name of the DTB file. + """ + state = FdtState(addr_cells=2, size_cells=2, cpu_cells=1) + root = FdtNode('/') + root.append(state.addrCellsProperty()) + root.append(state.sizeCellsProperty()) + root.appendCompatible(["riscv-virtio"]) + + for mem_range in system.mem_ranges: + root.append(generateMemNode(state, mem_range)) + + sections = [*system.cpu, system.platform] + + for section in sections: + for node in section.generateDeviceTree(state): + if node.get_name() == root.get_name(): + root.merge(node) + else: + root.append(node) + + fdt = Fdt() + fdt.add_rootnode(root) + fdt.writeDtsFile(path.join(m5.options.outdir, 'device.dts')) + fdt.writeDtbFile(path.join(m5.options.outdir, 'device.dtb')) + # ----------------------------- Add Options ---------------------------- # parser = optparse.OptionParser() Options.addCommonOptions(parser) @@ -92,12 +130,12 @@ mdesc = SysConfig(disks=options.disk_image, rootdev=options.root_device, system.mem_mode = mem_mode system.mem_ranges = [AddrRange(start=0x80000000, size=mdesc.mem())] -system.workload = RiscvBareMetal() +system.workload = RiscvLinux() system.iobus = IOXBar() system.membus = MemBus() -system.system_port = system.membus.slave +system.system_port = system.membus.cpu_side_ports system.intrctrl = IntrControl() @@ -147,7 +185,7 @@ system.cpu_clk_domain = SrcClockDomain(clock = options.cpu_clock, voltage_domain = system.cpu_voltage_domain) -system.workload.bootloader = options.kernel +system.workload.object_file = options.kernel # NOTE: Not yet tested if options.script is not None: @@ -161,12 +199,12 @@ system.cpu = [CPUClass(clk_domain=system.cpu_clk_domain, cpu_id=i) if options.caches or options.l2cache: # By default the IOCache runs at the system clock system.iocache = IOCache(addr_ranges = system.mem_ranges) - system.iocache.cpu_side = system.iobus.master - system.iocache.mem_side = system.membus.slave + system.iocache.cpu_side = system.iobus.mem_side_ports + system.iocache.mem_side = system.membus.cpu_side_ports elif not options.external_memory_system: system.iobridge = Bridge(delay='50ns', ranges = system.mem_ranges) - system.iobridge.slave = system.iobus.master - system.iobridge.master = system.membus.slave + system.iobridge.cpu_side_ports = system.iobus.mem_side_ports + system.iobridge.mem_side_ports = system.membus.cpu_side_ports # Sanity check if options.simpoint_profile: @@ -197,13 +235,27 @@ uncacheable_range = [ *system.platform._on_chip_ranges(), *system.platform._off_chip_ranges() ] -pma_checker = PMAChecker(uncacheable=uncacheable_range) # PMA checker can be defined at system-level (system.pma_checker) # or MMU-level (system.cpu[0].mmu.pma_checker). It will be resolved # by RiscvTLB's Parent.any proxy for cpu in system.cpu: - cpu.mmu.pma_checker = pma_checker + cpu.mmu.pma_checker = PMAChecker(uncacheable=uncacheable_range) + +# --------------------------- DTB Generation --------------------------- # + +generateDtb(system) +system.workload.dtb_filename = path.join(m5.options.outdir, 'device.dtb') +# Default DTB address if bbl is bulit with --with-dts option +system.workload.dtb_addr = 0x87e00000 + +# Linux boot command flags +kernel_cmd = [ + "console=ttyS0", + "root=/dev/vda", + "ro" +] +system.workload.command_line = " ".join(kernel_cmd) # ---------------------------- Default Setup --------------------------- # diff --git a/src/arch/riscv/RiscvFsWorkload.py b/src/arch/riscv/RiscvFsWorkload.py index 93e8fcea31..1a2d868a7a 100644 --- a/src/arch/riscv/RiscvFsWorkload.py +++ b/src/arch/riscv/RiscvFsWorkload.py @@ -30,22 +30,20 @@ from m5.params import * from m5.objects.System import System -from m5.objects.Workload import Workload +from m5.objects.Workload import Workload, KernelWorkload -class RiscvFsWorkload(Workload): - type = 'RiscvFsWorkload' - cxx_class = 'RiscvISA::FsWorkload' - cxx_header = 'arch/riscv/fs_workload.hh' - abstract = True - - bare_metal = Param.Bool(False, "Using Bare Metal Application?") - reset_vect = Param.Addr(0x0, 'Reset vector') - - -class RiscvBareMetal(RiscvFsWorkload): +class RiscvBareMetal(Workload): type = 'RiscvBareMetal' cxx_class = 'RiscvISA::BareMetal' cxx_header = 'arch/riscv/bare_metal/fs_workload.hh' bootloader = Param.String("File, that contains the bootloader code") + bare_metal = Param.Bool(True, "Using Bare Metal Application?") + reset_vect = Param.Addr(0x0, 'Reset vector') - bare_metal = True +class RiscvLinux(KernelWorkload): + type = 'RiscvLinux' + cxx_class = 'RiscvISA::FsLinux' + cxx_header = 'arch/riscv/linux/fs_workload.hh' + dtb_filename = Param.String("", + "File that contains the Device Tree Blob. Don't use DTB if empty.") + dtb_addr = Param.Addr(0x87e00000, "DTB address") diff --git a/src/arch/riscv/SConscript b/src/arch/riscv/SConscript index 3bd7436fb8..ac47df5815 100644 --- a/src/arch/riscv/SConscript +++ b/src/arch/riscv/SConscript @@ -58,6 +58,7 @@ if env['TARGET_ISA'] == 'riscv': Source('linux/se_workload.cc') Source('linux/linux.cc') + Source('linux/fs_workload.cc') Source('bare_metal/fs_workload.cc') diff --git a/src/arch/riscv/bare_metal/fs_workload.cc b/src/arch/riscv/bare_metal/fs_workload.cc index e9a00ac0d3..9607e19b69 100644 --- a/src/arch/riscv/bare_metal/fs_workload.cc +++ b/src/arch/riscv/bare_metal/fs_workload.cc @@ -32,12 +32,14 @@ #include "arch/riscv/faults.hh" #include "base/loader/object_file.hh" #include "sim/system.hh" +#include "sim/workload.hh" namespace RiscvISA { -BareMetal::BareMetal(const Params &p) : RiscvISA::FsWorkload(p), - bootloader(Loader::createObjectFile(p.bootloader)) +BareMetal::BareMetal(const Params &p) : Workload(p), + _isBareMetal(p.bare_metal), _resetVect(p.reset_vect), + bootloader(Loader::createObjectFile(p.bootloader)) { fatal_if(!bootloader, "Could not load bootloader file %s.", p.bootloader); _resetVect = bootloader->entryPoint(); @@ -52,7 +54,7 @@ BareMetal::~BareMetal() void BareMetal::initState() { - RiscvISA::FsWorkload::initState(); + Workload::initState(); for (auto *tc: system->threads) { RiscvISA::Reset().invoke(tc); diff --git a/src/arch/riscv/bare_metal/fs_workload.hh b/src/arch/riscv/bare_metal/fs_workload.hh index f4c62a156d..aa86ccaec4 100644 --- a/src/arch/riscv/bare_metal/fs_workload.hh +++ b/src/arch/riscv/bare_metal/fs_workload.hh @@ -29,20 +29,24 @@ #ifndef __ARCH_RISCV_BARE_METAL_SYSTEM_HH__ #define __ARCH_RISCV_BARE_METAL_SYSTEM_HH__ -#include "arch/riscv/fs_workload.hh" #include "params/RiscvBareMetal.hh" +#include "sim/workload.hh" namespace RiscvISA { -class BareMetal : public RiscvISA::FsWorkload +class BareMetal : public Workload { protected: + // checker for bare metal application + bool _isBareMetal; + // entry point for simulation + Addr _resetVect; Loader::ObjectFile *bootloader; Loader::SymbolTable bootloaderSymtab; public: - typedef RiscvBareMetalParams Params; + PARAMS(RiscvBareMetal); BareMetal(const Params &p); ~BareMetal(); @@ -54,11 +58,20 @@ class BareMetal : public RiscvISA::FsWorkload { return bootloaderSymtab; } + bool insertSymbol(const Loader::Symbol &symbol) override { return bootloaderSymtab.insert(symbol); } + + // return reset vector + Addr resetVect() const { return _resetVect; } + + // return bare metal checker + bool isBareMetal() const { return _isBareMetal; } + + Addr getEntry() const override { return _resetVect; } }; } // namespace RiscvISA diff --git a/src/arch/riscv/faults.cc b/src/arch/riscv/faults.cc index 5ac2a3c790..27b4893a63 100644 --- a/src/arch/riscv/faults.cc +++ b/src/arch/riscv/faults.cc @@ -31,7 +31,6 @@ #include "arch/riscv/faults.hh" -#include "arch/riscv/fs_workload.hh" #include "arch/riscv/isa.hh" #include "arch/riscv/registers.hh" #include "arch/riscv/utility.hh" @@ -40,6 +39,7 @@ #include "debug/Fault.hh" #include "sim/debug.hh" #include "sim/full_system.hh" +#include "sim/workload.hh" namespace RiscvISA { @@ -155,8 +155,8 @@ void Reset::invoke(ThreadContext *tc, const StaticInstPtr &inst) tc->setMiscReg(MISCREG_MCAUSE, 0); // Advance the PC to the implementation-defined reset vector - auto workload = dynamic_cast(tc->getSystemPtr()->workload); - PCState pc = workload->resetVect(); + auto workload = dynamic_cast(tc->getSystemPtr()->workload); + PCState pc = workload->getEntry(); tc->pcState(pc); } diff --git a/src/arch/riscv/linux/fs_workload.cc b/src/arch/riscv/linux/fs_workload.cc new file mode 100644 index 0000000000..fca839cc82 --- /dev/null +++ b/src/arch/riscv/linux/fs_workload.cc @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Huawei International + * 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. + */ + +#include "arch/riscv/linux/fs_workload.hh" + +#include "arch/riscv/faults.hh" +#include "base/loader/dtb_file.hh" +#include "base/loader/object_file.hh" +#include "base/loader/symtab.hh" +#include "sim/kernel_workload.hh" +#include "sim/system.hh" + +namespace RiscvISA +{ + +void +FsLinux::initState() +{ + KernelWorkload::initState(); + + if (params().dtb_filename != "") { + inform("Loading DTB file: %s at address %#x\n", params().dtb_filename, + params().dtb_addr); + + auto *dtb_file = new ::Loader::DtbFile(params().dtb_filename); + + if (!dtb_file->addBootCmdLine( + commandLine.c_str(), commandLine.size())) { + warn("couldn't append bootargs to DTB file: %s\n", + params().dtb_filename); + } + + dtb_file->buildImage().offset(params().dtb_addr) + .write(system->physProxy); + delete dtb_file; + + for (auto *tc: system->threads) { + tc->setIntReg(11, params().dtb_addr); + } + } else { + warn("No DTB file specified\n"); + } + + for (auto *tc: system->threads) { + RiscvISA::Reset().invoke(tc); + tc->activate(); + } +} + +} // namespace RiscvISA diff --git a/src/arch/riscv/fs_workload.hh b/src/arch/riscv/linux/fs_workload.hh similarity index 64% rename from src/arch/riscv/fs_workload.hh rename to src/arch/riscv/linux/fs_workload.hh index 0f18bb32a2..84b0e1dddc 100644 --- a/src/arch/riscv/fs_workload.hh +++ b/src/arch/riscv/linux/fs_workload.hh @@ -1,7 +1,6 @@ /* - * Copyright (c) 2002-2005 The Regents of The University of Michigan - * Copyright (c) 2007 MIPS Technologies, Inc. - * All rights reserved. + * Copyright (c) 2021 Huawei International + * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -27,38 +26,24 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __ARCH_RISCV_FS_WORKLOAD_HH__ -#define __ARCH_RISCV_FS_WORKLOAD_HH__ +#ifndef __ARCH_RISCV_LINUX_SYSTEM_HH__ +#define __ARCH_RISCV_LINUX_SYSTEM_HH__ -#include "params/RiscvFsWorkload.hh" -#include "sim/sim_object.hh" -#include "sim/workload.hh" +#include "params/RiscvLinux.hh" +#include "sim/kernel_workload.hh" namespace RiscvISA { -class FsWorkload : public Workload +class FsLinux : public KernelWorkload { - protected: - // checker for bare metal application - bool _isBareMetal; - // entry point for simulation - Addr _resetVect; - public: - FsWorkload(const RiscvFsWorkloadParams &p) : Workload(p), - _isBareMetal(p.bare_metal), _resetVect(p.reset_vect) - {} + PARAMS(RiscvLinux); + FsLinux(const Params &p) : KernelWorkload(p) {} - // return reset vector - Addr resetVect() const { return _resetVect; } - - // return bare metal checker - bool isBareMetal() const { return _isBareMetal; } - - Addr getEntry() const override { return _resetVect; } + void initState() override; }; } // namespace RiscvISA -#endif // __ARCH_RISCV_FS_WORKLOAD_HH__ +#endif // __ARCH_RISCV_LINUX_FS_WORKLOAD_HH__ diff --git a/src/arch/riscv/tlb.cc b/src/arch/riscv/tlb.cc index b7b09849dc..a2df355614 100644 --- a/src/arch/riscv/tlb.cc +++ b/src/arch/riscv/tlb.cc @@ -35,7 +35,6 @@ #include #include "arch/riscv/faults.hh" -#include "arch/riscv/fs_workload.hh" #include "arch/riscv/mmu.hh" #include "arch/riscv/pagetable.hh" #include "arch/riscv/pagetable_walker.hh" diff --git a/src/dev/riscv/Clint.py b/src/dev/riscv/Clint.py index 25b595b3a6..0c01749e32 100644 --- a/src/dev/riscv/Clint.py +++ b/src/dev/riscv/Clint.py @@ -37,6 +37,7 @@ from m5.objects.Device import BasicPioDevice from m5.objects.IntPin import IntSinkPin from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * class Clint(BasicPioDevice): """ @@ -51,3 +52,21 @@ class Clint(BasicPioDevice): intrctrl = Param.IntrControl(Parent.any, "interrupt controller") int_pin = IntSinkPin('Pin to receive RTC signal') pio_size = Param.Addr(0xC000, "PIO Size") + + def generateDeviceTree(self, state): + node = self.generateBasicPioDeviceNode(state, "clint", self.pio_addr, + self.pio_size) + + cpus = self.system.unproxy(self).cpu + int_extended = list() + for cpu in cpus: + phandle = state.phandle(cpu) + int_extended.append(phandle) + int_extended.append(0x3) + int_extended.append(phandle) + int_extended.append(0x7) + + node.append(FdtPropertyWords("interrupts-extended", int_extended)) + node.appendCompatible(["riscv,clint0"]) + + yield node \ No newline at end of file diff --git a/src/dev/riscv/HiFive.py b/src/dev/riscv/HiFive.py index 17d54e39ab..722b404a4c 100755 --- a/src/dev/riscv/HiFive.py +++ b/src/dev/riscv/HiFive.py @@ -42,6 +42,7 @@ from m5.objects.Uart import Uart8250 from m5.objects.Terminal import Terminal from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * class HiFive(Platform): """HiFive Platform @@ -111,6 +112,9 @@ class HiFive(Platform): uart_int_id = Param.Int(0xa, "PLIC Uart interrupt ID") terminal = Terminal() + # Dummy param for generating devicetree + cpu_count = Param.Int(0, "dummy") + def _on_chip_devices(self): """Returns a list of on-chip peripherals """ @@ -167,3 +171,39 @@ class HiFive(Platform): """ for device in self._off_chip_devices(): device.pio = bus.mem_side_ports + + def generateDeviceTree(self, state): + cpus_node = FdtNode("cpus") + cpus_node.append(FdtPropertyWords("timebase-frequency", [10000000])) + yield cpus_node + + node = FdtNode("soc") + local_state = FdtState(addr_cells=2, size_cells=2) + node.append(local_state.addrCellsProperty()) + node.append(local_state.sizeCellsProperty()) + node.append(FdtProperty("ranges")) + node.appendCompatible(["simple-bus"]) + + for subnode in self.recurseDeviceTree(local_state): + node.append(subnode) + + yield node + + def annotateCpuDeviceNode(self, cpu, state): + cpu.append(FdtPropertyStrings('mmu-type', 'riscv,sv48')) + cpu.append(FdtPropertyStrings('status', 'okay')) + cpu.append(FdtPropertyStrings('riscv,isa', 'rv64imafdcsu')) + cpu.appendCompatible(["riscv"]) + + int_node = FdtNode("interrupt-controller") + int_state = FdtState(interrupt_cells=1) + int_node.append(int_state.interruptCellsProperty()) + int_node.append(FdtProperty("interrupt-controller")) + int_node.appendCompatible("riscv,cpu-intc") + + cpus = self.system.unproxy(self).cpu + phandle = int_state.phandle(cpus[self.cpu_count]) + self.cpu_count += 1 + int_node.append(FdtPropertyWords("phandle", [phandle])) + + cpu.append(int_node) \ No newline at end of file diff --git a/src/dev/riscv/Plic.py b/src/dev/riscv/Plic.py index 0e2f386e2e..a8c97c853b 100644 --- a/src/dev/riscv/Plic.py +++ b/src/dev/riscv/Plic.py @@ -36,6 +36,7 @@ from m5.objects.Device import BasicPioDevice from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * class Plic(BasicPioDevice): """ @@ -50,3 +51,30 @@ class Plic(BasicPioDevice): intrctrl = Param.IntrControl(Parent.any, "interrupt controller") pio_size = Param.Addr(0x4000000, "PIO Size") n_src = Param.Int("Number of interrupt sources") + + def generateDeviceTree(self, state): + node = self.generateBasicPioDeviceNode(state, "plic", self.pio_addr, + self.pio_size) + + int_state = FdtState(addr_cells=0, interrupt_cells=1) + node.append(int_state.addrCellsProperty()) + node.append(int_state.interruptCellsProperty()) + + phandle = int_state.phandle(self) + node.append(FdtPropertyWords("phandle", [phandle])) + node.append(FdtPropertyWords("riscv,ndev", [self.n_src - 1])) + + cpus = self.system.unproxy(self).cpu + int_extended = list() + for cpu in cpus: + phandle = int_state.phandle(cpu) + int_extended.append(phandle) + int_extended.append(0xb) + int_extended.append(phandle) + int_extended.append(0x9) + + node.append(FdtPropertyWords("interrupts-extended", int_extended)) + node.append(FdtProperty("interrupt-controller")) + node.appendCompatible(["riscv,plic0"]) + + yield node \ No newline at end of file diff --git a/src/dev/riscv/PlicDevice.py b/src/dev/riscv/PlicDevice.py index 51956805d3..f0b30c02e6 100644 --- a/src/dev/riscv/PlicDevice.py +++ b/src/dev/riscv/PlicDevice.py @@ -36,6 +36,7 @@ from m5.objects.Device import BasicPioDevice from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * class PlicIntDevice(BasicPioDevice): type = 'PlicIntDevice' @@ -44,3 +45,13 @@ class PlicIntDevice(BasicPioDevice): platform = Param.Platform(Parent.any, "Platform") pio_size = Param.Addr("PIO Size") interrupt_id = Param.Int("PLIC Interrupt ID") + + def generatePlicDeviceNode(self, state, name): + node = self.generateBasicPioDeviceNode(state, name, + self.pio_addr, self.pio_size) + + plic = self.platform.unproxy(self).plic + + node.append(FdtPropertyWords("interrupts", [self.interrupt_id])) + node.append(FdtPropertyWords("interrupt-parent", state.phandle(plic))) + return node diff --git a/src/dev/riscv/VirtIOMMIO.py b/src/dev/riscv/VirtIOMMIO.py index 20efc512cf..06208df82d 100644 --- a/src/dev/riscv/VirtIOMMIO.py +++ b/src/dev/riscv/VirtIOMMIO.py @@ -37,6 +37,7 @@ from m5.SimObject import SimObject from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * from m5.objects.PlicDevice import PlicIntDevice from m5.objects.VirtIO import VirtIODummyDevice @@ -45,3 +46,9 @@ class MmioVirtIO(PlicIntDevice): type = 'MmioVirtIO' cxx_header = 'dev/riscv/vio_mmio.hh' vio = Param.VirtIODeviceBase(VirtIODummyDevice(), "VirtIO device") + + def generateDeviceTree(self, state): + node = self.generatePlicDeviceNode(state, "virtio_mmio") + node.appendCompatible(["virtio,mmio"]) + + yield node \ No newline at end of file diff --git a/src/dev/serial/Uart.py b/src/dev/serial/Uart.py index bcaf0a5555..76ac066c90 100644 --- a/src/dev/serial/Uart.py +++ b/src/dev/serial/Uart.py @@ -38,6 +38,8 @@ from m5.params import * from m5.proxy import * +from m5.util.fdthelper import * +from m5.defines import buildEnv from m5.objects.Device import BasicPioDevice from m5.objects.Serial import SerialDevice @@ -61,3 +63,18 @@ class Uart8250(Uart): type = 'Uart8250' cxx_header = "dev/serial/uart8250.hh" pio_size = Param.Addr(0x8, "Size of address range") + + def generateDeviceTree(self, state): + if buildEnv['TARGET_ISA'] == "riscv": + node = self.generateBasicPioDeviceNode( + state, "uart", self.pio_addr, self.pio_size) + platform = self.platform.unproxy(self) + plic = platform.plic + node.append( + FdtPropertyWords("interrupts", [platform.uart_int_id])) + node.append( + FdtPropertyWords("clock-frequency", [0x384000])) + node.append( + FdtPropertyWords("interrupt-parent", state.phandle(plic))) + node.appendCompatible(["ns8250"]) + yield node