hw/riscv/virt.c | 59 ++++++++++++++++++++++++++++++++++++++++- include/hw/riscv/virt.h | 2 ++ 2 files changed, 60 insertions(+), 1 deletion(-)
The 'virt' RISC-V machine hardcodes its DRAM base to 0x80000000.
Real-world RISC-V SoCs put DRAM elsewhere (SpacemiT K1 at 0x40000000,
SpacemiT K3 at 0x100000000, etc.), which forces ports that target
those boards to build separate kernels for QEMU testing.
Add a 'dram-base' string property that lets users move VIRT_DRAM up
without rebuilding QEMU. Values must be 2 MiB-aligned and at or
above the default 0x80000000 to avoid colliding with statically-
placed MMIO regions (PCIE_MMIO ends at 0x80000000). Lower bases
would also require moving PCIE_MMIO/IMSIC/etc. and are rejected with
an explicit error pointing at the cause.
Example:
qemu-system-riscv64 -machine virt,dram-base=0x100000000 -m 6G ...
runs OpenSBI at firmware base 0x100000000, matching K3 memory layout.
Signed-off-by: Shawn Rutledge <s@ecloud.org>
---
hw/riscv/virt.c | 59 ++++++++++++++++++++++++++++++++++++++++-
include/hw/riscv/virt.h | 2 ++
2 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 51bac47a91..6ccc98842f 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -20,6 +20,7 @@
#include "qemu/osdep.h"
#include "qemu/units.h"
+#include "qemu/cutils.h"
#include "qemu/error-report.h"
#include "qemu/guest-random.h"
#include "qapi/error.h"
@@ -1307,7 +1308,13 @@ static void virt_machine_init(MachineState *machine)
int i, base_hartid, hart_count;
int socket_count = riscv_socket_count(machine);
- s->memmap = virt_memmap;
+ if (s->dram_base) {
+ s->memmap_storage = g_memdup2(virt_memmap, sizeof(virt_memmap));
+ s->memmap_storage[VIRT_DRAM].base = s->dram_base;
+ s->memmap = s->memmap_storage;
+ } else {
+ s->memmap = virt_memmap;
+ }
/* Check socket count limit */
if (VIRT_SOCKETS_MAX < socket_count) {
@@ -1545,6 +1552,7 @@ static void virt_machine_instance_finalize(Object *obj)
}
g_free(s->oem_id);
g_free(s->oem_table_id);
+ g_free(s->memmap_storage);
}
static void virt_machine_instance_init(Object *obj)
@@ -1616,6 +1624,46 @@ static void virt_set_aia(Object *obj, const char *val, Error **errp)
}
}
+static char *virt_get_dram_base(Object *obj, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+ uint64_t val = s->dram_base ? s->dram_base : virt_memmap[VIRT_DRAM].base;
+
+ return g_strdup_printf("0x%" PRIx64, val);
+}
+
+static void virt_set_dram_base(Object *obj, const char *val, Error **errp)
+{
+ RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+ const char *endptr;
+ uint64_t base;
+
+ if (qemu_strtou64(val, &endptr, 0, &base) < 0 || *endptr != '\0') {
+ error_setg(errp, "Invalid dram-base value '%s'", val);
+ return;
+ }
+ /*
+ * DRAM must clear all statically-placed MMIO regions in virt_memmap[]
+ * (PCIE_MMIO ends at 0x80000000) and be 2 MiB-aligned so a standard
+ * OpenSBI + kernel layout (firmware at base, kernel at base+0x200000)
+ * fits. Values below the default base would collide with on-board
+ * MMIO and are rejected.
+ */
+ if (base < virt_memmap[VIRT_DRAM].base) {
+ error_setg(errp,
+ "dram-base 0x%" PRIx64 " is below default 0x%" PRIx64
+ "; would collide with static MMIO regions",
+ base, (uint64_t)virt_memmap[VIRT_DRAM].base);
+ return;
+ }
+ if (base & (2 * MiB - 1)) {
+ error_setg(errp, "dram-base 0x%" PRIx64 " must be 2 MiB-aligned",
+ base);
+ return;
+ }
+ s->dram_base = base;
+}
+
static bool virt_get_aclint(Object *obj, Error **errp)
{
RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
@@ -1744,6 +1792,15 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
#endif
+ object_class_property_add_str(oc, "dram-base", virt_get_dram_base,
+ virt_set_dram_base);
+ object_class_property_set_description(oc, "dram-base",
+ "Base physical address of DRAM "
+ "(default 0x80000000). Must be "
+ "2 MiB-aligned and at or above "
+ "the default to avoid colliding "
+ "with statically-placed MMIO.");
+
object_class_property_add_bool(oc, "aclint", virt_get_aclint,
virt_set_aclint);
object_class_property_set_description(oc, "aclint",
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 36a2def410..be8a4ac049 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -61,6 +61,8 @@ struct RISCVVirtState {
char *oem_table_id;
OnOffAuto acpi;
const MemMapEntry *memmap;
+ MemMapEntry *memmap_storage; /* g_malloc'd copy when dram-base override is set */
+ uint64_t dram_base; /* 0 = use compiled-in default (0x80000000) */
struct GPEXHost *gpex_host;
OnOffAuto iommu_sys;
uint16_t pci_iommu_bdf;
--
2.55.0
Ping.
Do you have a different solution to emulate memory layouts of actual boards?
For me this is necessary for the Plan 9 porting work: a Banana Pi F3 has a different base address than one of the K3 boards, and both different than the QEMU default.
> On Jul 30, 2026, at 12:42, Shawn Rutledge <lists@ecloud.org> wrote:
>
> The 'virt' RISC-V machine hardcodes its DRAM base to 0x80000000.
> Real-world RISC-V SoCs put DRAM elsewhere (SpacemiT K1 at 0x40000000,
> SpacemiT K3 at 0x100000000, etc.), which forces ports that target
> those boards to build separate kernels for QEMU testing.
>
> Add a 'dram-base' string property that lets users move VIRT_DRAM up
> without rebuilding QEMU. Values must be 2 MiB-aligned and at or
> above the default 0x80000000 to avoid colliding with statically-
> placed MMIO regions (PCIE_MMIO ends at 0x80000000). Lower bases
> would also require moving PCIE_MMIO/IMSIC/etc. and are rejected with
> an explicit error pointing at the cause.
>
> Example:
> qemu-system-riscv64 -machine virt,dram-base=0x100000000 -m 6G ...
> runs OpenSBI at firmware base 0x100000000, matching K3 memory layout.
>
> Signed-off-by: Shawn Rutledge <s@ecloud.org>
> ---
> hw/riscv/virt.c | 59 ++++++++++++++++++++++++++++++++++++++++-
> include/hw/riscv/virt.h | 2 ++
> 2 files changed, 60 insertions(+), 1 deletion(-)
>
> diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
> index 51bac47a91..6ccc98842f 100644
> --- a/hw/riscv/virt.c
> +++ b/hw/riscv/virt.c
> @@ -20,6 +20,7 @@
>
> #include "qemu/osdep.h"
> #include "qemu/units.h"
> +#include "qemu/cutils.h"
> #include "qemu/error-report.h"
> #include "qemu/guest-random.h"
> #include "qapi/error.h"
> @@ -1307,7 +1308,13 @@ static void virt_machine_init(MachineState *machine)
> int i, base_hartid, hart_count;
> int socket_count = riscv_socket_count(machine);
>
> - s->memmap = virt_memmap;
> + if (s->dram_base) {
> + s->memmap_storage = g_memdup2(virt_memmap, sizeof(virt_memmap));
> + s->memmap_storage[VIRT_DRAM].base = s->dram_base;
> + s->memmap = s->memmap_storage;
> + } else {
> + s->memmap = virt_memmap;
> + }
>
> /* Check socket count limit */
> if (VIRT_SOCKETS_MAX < socket_count) {
> @@ -1545,6 +1552,7 @@ static void virt_machine_instance_finalize(Object *obj)
> }
> g_free(s->oem_id);
> g_free(s->oem_table_id);
> + g_free(s->memmap_storage);
> }
>
> static void virt_machine_instance_init(Object *obj)
> @@ -1616,6 +1624,46 @@ static void virt_set_aia(Object *obj, const char *val, Error **errp)
> }
> }
>
> +static char *virt_get_dram_base(Object *obj, Error **errp)
> +{
> + RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> + uint64_t val = s->dram_base ? s->dram_base : virt_memmap[VIRT_DRAM].base;
> +
> + return g_strdup_printf("0x%" PRIx64, val);
> +}
> +
> +static void virt_set_dram_base(Object *obj, const char *val, Error **errp)
> +{
> + RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> + const char *endptr;
> + uint64_t base;
> +
> + if (qemu_strtou64(val, &endptr, 0, &base) < 0 || *endptr != '\0') {
> + error_setg(errp, "Invalid dram-base value '%s'", val);
> + return;
> + }
> + /*
> + * DRAM must clear all statically-placed MMIO regions in virt_memmap[]
> + * (PCIE_MMIO ends at 0x80000000) and be 2 MiB-aligned so a standard
> + * OpenSBI + kernel layout (firmware at base, kernel at base+0x200000)
> + * fits. Values below the default base would collide with on-board
> + * MMIO and are rejected.
> + */
> + if (base < virt_memmap[VIRT_DRAM].base) {
> + error_setg(errp,
> + "dram-base 0x%" PRIx64 " is below default 0x%" PRIx64
> + "; would collide with static MMIO regions",
> + base, (uint64_t)virt_memmap[VIRT_DRAM].base);
> + return;
> + }
> + if (base & (2 * MiB - 1)) {
> + error_setg(errp, "dram-base 0x%" PRIx64 " must be 2 MiB-aligned",
> + base);
> + return;
> + }
> + s->dram_base = base;
> +}
> +
> static bool virt_get_aclint(Object *obj, Error **errp)
> {
> RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> @@ -1744,6 +1792,15 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
> machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
> #endif
>
> + object_class_property_add_str(oc, "dram-base", virt_get_dram_base,
> + virt_set_dram_base);
> + object_class_property_set_description(oc, "dram-base",
> + "Base physical address of DRAM "
> + "(default 0x80000000). Must be "
> + "2 MiB-aligned and at or above "
> + "the default to avoid colliding "
> + "with statically-placed MMIO.");
> +
> object_class_property_add_bool(oc, "aclint", virt_get_aclint,
> virt_set_aclint);
> object_class_property_set_description(oc, "aclint",
> diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
> index 36a2def410..be8a4ac049 100644
> --- a/include/hw/riscv/virt.h
> +++ b/include/hw/riscv/virt.h
> @@ -61,6 +61,8 @@ struct RISCVVirtState {
> char *oem_table_id;
> OnOffAuto acpi;
> const MemMapEntry *memmap;
> + MemMapEntry *memmap_storage; /* g_malloc'd copy when dram-base override is set */
> + uint64_t dram_base; /* 0 = use compiled-in default (0x80000000) */
> struct GPEXHost *gpex_host;
> OnOffAuto iommu_sys;
> uint16_t pci_iommu_bdf;
> --
> 2.55.0
>
On Fri, 2026-08-07 at 14:26 +0300, lists wrote:
> Ping.
>
> Do you have a different solution to emulate memory layouts of actual
> boards?
The ideal answer is to model the board
>
> For me this is necessary for the Plan 9 porting work: a Banana Pi F3
> has a different base address than one of the K3 boards, and both
> different than the QEMU default.
The Banana Pi F3 and the K3 will also have different hardware blocks at
different addresses. The starting address of memory isn't the only or
even the main difference.
I don't see how just changing the memory address fixes an issue for
you.
>
> > On Jul 30, 2026, at 12:42, Shawn Rutledge <lists@ecloud.org> wrote:
> >
> > The 'virt' RISC-V machine hardcodes its DRAM base to 0x80000000.
> > Real-world RISC-V SoCs put DRAM elsewhere (SpacemiT K1 at
> > 0x40000000,
> > SpacemiT K3 at 0x100000000, etc.), which forces ports that target
> > those boards to build separate kernels for QEMU testing.
RISC-V SoCs can locate their DRAM at any address, that is true. But I
don't see how just changing the virt machine address allows you to
suddenly test a K3 kernel on the virt board. The rest of the hardware
is still different
> >
> > Add a 'dram-base' string property that lets users move VIRT_DRAM up
> > without rebuilding QEMU. Values must be 2 MiB-aligned and at or
> > above the default 0x80000000 to avoid colliding with statically-
> > placed MMIO regions (PCIE_MMIO ends at 0x80000000). Lower bases
> > would also require moving PCIE_MMIO/IMSIC/etc. and are rejected
> > with
> > an explicit error pointing at the cause.
Am I miscounting a 0? 0x4000_0000 is less then 0x8000_0000 so this
doesn't help for half of your examples.
Alistair
> >
> > Example:
> > qemu-system-riscv64 -machine virt,dram-base=0x100000000 -m 6G ...
> > runs OpenSBI at firmware base 0x100000000, matching K3 memory
> > layout.
> >
> > Signed-off-by: Shawn Rutledge <s@ecloud.org>
> > ---
> > hw/riscv/virt.c | 59
> > ++++++++++++++++++++++++++++++++++++++++-
> > include/hw/riscv/virt.h | 2 ++
> > 2 files changed, 60 insertions(+), 1 deletion(-)
> >
> > diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
> > index 51bac47a91..6ccc98842f 100644
> > --- a/hw/riscv/virt.c
> > +++ b/hw/riscv/virt.c
> > @@ -20,6 +20,7 @@
> >
> > #include "qemu/osdep.h"
> > #include "qemu/units.h"
> > +#include "qemu/cutils.h"
> > #include "qemu/error-report.h"
> > #include "qemu/guest-random.h"
> > #include "qapi/error.h"
> > @@ -1307,7 +1308,13 @@ static void virt_machine_init(MachineState
> > *machine)
> > int i, base_hartid, hart_count;
> > int socket_count = riscv_socket_count(machine);
> >
> > - s->memmap = virt_memmap;
> > + if (s->dram_base) {
> > + s->memmap_storage = g_memdup2(virt_memmap,
> > sizeof(virt_memmap));
> > + s->memmap_storage[VIRT_DRAM].base = s->dram_base;
> > + s->memmap = s->memmap_storage;
> > + } else {
> > + s->memmap = virt_memmap;
> > + }
> >
> > /* Check socket count limit */
> > if (VIRT_SOCKETS_MAX < socket_count) {
> > @@ -1545,6 +1552,7 @@ static void
> > virt_machine_instance_finalize(Object *obj)
> > }
> > g_free(s->oem_id);
> > g_free(s->oem_table_id);
> > + g_free(s->memmap_storage);
> > }
> >
> > static void virt_machine_instance_init(Object *obj)
> > @@ -1616,6 +1624,46 @@ static void virt_set_aia(Object *obj, const
> > char *val, Error **errp)
> > }
> > }
> >
> > +static char *virt_get_dram_base(Object *obj, Error **errp)
> > +{
> > + RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> > + uint64_t val = s->dram_base ? s->dram_base :
> > virt_memmap[VIRT_DRAM].base;
> > +
> > + return g_strdup_printf("0x%" PRIx64, val);
> > +}
> > +
> > +static void virt_set_dram_base(Object *obj, const char *val, Error
> > **errp)
> > +{
> > + RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> > + const char *endptr;
> > + uint64_t base;
> > +
> > + if (qemu_strtou64(val, &endptr, 0, &base) < 0 || *endptr !=
> > '\0') {
> > + error_setg(errp, "Invalid dram-base value '%s'", val);
> > + return;
> > + }
> > + /*
> > + * DRAM must clear all statically-placed MMIO regions in
> > virt_memmap[]
> > + * (PCIE_MMIO ends at 0x80000000) and be 2 MiB-aligned so a
> > standard
> > + * OpenSBI + kernel layout (firmware at base, kernel at
> > base+0x200000)
> > + * fits. Values below the default base would collide with on-
> > board
> > + * MMIO and are rejected.
> > + */
> > + if (base < virt_memmap[VIRT_DRAM].base) {
> > + error_setg(errp,
> > + "dram-base 0x%" PRIx64 " is below default 0x%"
> > PRIx64
> > + "; would collide with static MMIO regions",
> > + base, (uint64_t)virt_memmap[VIRT_DRAM].base);
> > + return;
> > + }
> > + if (base & (2 * MiB - 1)) {
> > + error_setg(errp, "dram-base 0x%" PRIx64 " must be 2 MiB-
> > aligned",
> > + base);
> > + return;
> > + }
> > + s->dram_base = base;
> > +}
> > +
> > static bool virt_get_aclint(Object *obj, Error **errp)
> > {
> > RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
> > @@ -1744,6 +1792,15 @@ static void
> > virt_machine_class_init(ObjectClass *oc, const void *data)
> > machine_class_allow_dynamic_sysbus_dev(mc,
> > TYPE_TPM_TIS_SYSBUS);
> > #endif
> >
> > + object_class_property_add_str(oc, "dram-base",
> > virt_get_dram_base,
> > + virt_set_dram_base);
> > + object_class_property_set_description(oc, "dram-base",
> > + "Base physical address
> > of DRAM "
> > + "(default 0x80000000).
> > Must be "
> > + "2 MiB-aligned and at or
> > above "
> > + "the default to avoid
> > colliding "
> > + "with statically-placed
> > MMIO.");
> > +
> > object_class_property_add_bool(oc, "aclint", virt_get_aclint,
> > virt_set_aclint);
> > object_class_property_set_description(oc, "aclint",
> > diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
> > index 36a2def410..be8a4ac049 100644
> > --- a/include/hw/riscv/virt.h
> > +++ b/include/hw/riscv/virt.h
> > @@ -61,6 +61,8 @@ struct RISCVVirtState {
> > char *oem_table_id;
> > OnOffAuto acpi;
> > const MemMapEntry *memmap;
> > + MemMapEntry *memmap_storage; /* g_malloc'd copy when dram-base
> > override is set */
> > + uint64_t dram_base; /* 0 = use compiled-in default
> > (0x80000000) */
> > struct GPEXHost *gpex_host;
> > OnOffAuto iommu_sys;
> > uint16_t pci_iommu_bdf;
> > --
> > 2.55.0
> >
> On Aug 12, 2026, at 18:13, Alistair <alistair@alistair23.me> wrote: > > On Fri, 2026-08-07 at 14:26 +0300, lists wrote: >> Ping. >> >> Do you have a different solution to emulate memory layouts of actual >> boards? > > The ideal answer is to model the board OK; that’s more work, and then there is an expectation that it will be actively maintained, right? Banana Pi F3 is already out of production AFAICT; and it remains to be seen how many K3 boards will physically come into existence: there are already several designs so far, with different peripherals. Since I have theoretically bought the Deep Computing III Framework-based laptop (and it hasn’t shipped yet), I hope I will be able to use it for a good long time, but maybe I won’t need emulation very often. There is no demand for a distinct emulator for every known PC motherboard, despite often having different peripherals, but there is such a demand for embedded boards. Hmm. It seems like the idea to “model the board” every time and then maintain all those models doesn’t really scale. I would rather hope that riscv becomes more of a standard platform, rather than ending up with a zoo of less-compatible devices like the ARM ecosystem does. Not that there’s much evidence for such a movement so far, but IMO there should be. The point of the patch was to make the base address of virt adjustable, because it’s one thing that a particular kernel may be least able to deal with, if its data segment (or worse, some code) contains absolute pointers. For a while it seemed that we would need to build separate kernels for each board. But I changed the linker to emit a relocation table, and the kernel to fix up the data-segment pointers at boot, and now we have a kernel that boots on the bpi-f3, a couple of K3 boards and qemu. So changing qemu’s base address is no longer necessary to get it to boot, but just enables testing high-memory paths where some bugs were found. >> For me this is necessary for the Plan 9 porting work: a Banana Pi F3 >> has a different base address than one of the K3 boards, and both >> different than the QEMU default. > > The Banana Pi F3 and the K3 will also have different hardware blocks at > different addresses. The starting address of memory isn't the only or > even the main difference. > > I don't see how just changing the memory address fixes an issue for > you. It was just that certain bugs manifested in the case when the base address is at 0x100000000. So I suppose abandoning this is fine, if nobody else needs it. > RISC-V SoCs can locate their DRAM at any address, that is true. But I > don't see how just changing the virt machine address allows you to > suddenly test a K3 kernel on the virt board. The rest of the hardware > is still different I don’t necessarily need to emulate all the hardware, at least to begin with.
On Tue, 2026-08-18 at 10:30 +0200, Shawn Rutledge wrote: > > > On Aug 12, 2026, at 18:13, Alistair <alistair@alistair23.me> wrote: > > > > On Fri, 2026-08-07 at 14:26 +0300, lists wrote: > > > Ping. > > > > > > Do you have a different solution to emulate memory layouts of > > > actual > > > boards? > > > > The ideal answer is to model the board > > > OK; that’s more work, and then there is an expectation that it will > be actively maintained, right? Banana Pi F3 is already out of Correct > production AFAICT; and it remains to be seen how many K3 boards will > physically come into existence: there are already several designs so > far, with different peripherals. Since I have theoretically bought > the Deep Computing III Framework-based laptop (and it hasn’t shipped > yet), I hope I will be able to use it for a good long time, but maybe > I won’t need emulation very often. There seem to be a few K3 boards shipping. I have one on my desk now :) > > There is no demand for a distinct emulator for every known PC > motherboard, despite often having different peripherals, but there is > such a demand for embedded boards. Hmm. It seems like the idea to > “model the board” every time and then maintain all those models > doesn’t really scale. I would rather hope that riscv becomes more of > a standard platform, rather than ending up with a zoo of less- > compatible devices like the ARM ecosystem does. Not that there’s > much evidence for such a movement so far, but IMO there should be. There are attempts at RVI, things like the server reference platform. But that is up to them and the RISC-V ecosystem, not us in QEMU land. > > The point of the patch was to make the base address of virt > adjustable, because it’s one thing that a particular kernel may be > least able to deal with, if its data segment (or worse, some code) > contains absolute pointers. For a while it seemed that we would need Yeah, I get that. But it is extra complexity for us to maintain and doesn't really align with the goal of the virt board. > to build separate kernels for each board. But I changed the linker > to emit a relocation table, and the kernel to fix up the data-segment > pointers at boot, and now we have a kernel that boots on the bpi-f3, > a couple of K3 boards and qemu. So changing qemu’s base address is > no longer necessary to get it to boot, but just enables testing high- > memory paths where some bugs were found. Great! > > > > For me this is necessary for the Plan 9 porting work: a Banana Pi > > > F3 > > > has a different base address than one of the K3 boards, and both > > > different than the QEMU default. > > > > The Banana Pi F3 and the K3 will also have different hardware > > blocks at > > different addresses. The starting address of memory isn't the only > > or > > even the main difference. > > > > I don't see how just changing the memory address fixes an issue for > > you. > > > It was just that certain bugs manifested in the case when the base > address is at 0x100000000. > > So I suppose abandoning this is fine, if nobody else needs it. It doesn't seem like something that anyone else needs Thanks for the patch though! Alistair > > > RISC-V SoCs can locate their DRAM at any address, that is true. But > > I > > don't see how just changing the virt machine address allows you to > > suddenly test a K3 kernel on the virt board. The rest of the > > hardware > > is still different > > > I don’t necessarily need to emulate all the hardware, at least to > begin with.
© 2016 - 2026 Red Hat, Inc.