Skip to content
Home » QEMU Development

QEMU Development

QEMU development during Global Chip Shortage

Considering the current developments in the global market, especially having the issue of a Global Chip Shortage, there are some issues to be addressed when having to deploy software on an Embedded platform, but the platform itself is missing.

The issue posed is no small undertaking, especially for companies deploying software for different platforms and not having the possibility of holding in stock the multitude of SoCs available on the market. There is also the situation when a new chip is under development and, for efficiency reasons, the software for it has to be developed in parallel.

All of this considered, it has to be handy to emulate hardware on our standard x64 machines. But what do we do when there are different memory maps or even ISAs (Instruction Set Architectures) involved? For this task, QEMU’s capabilities come to the rescue.

This article documents the steps required to add a custom machine to QEMUs existing machines and make use of established constructs in QEMU, and even use of the project’s open-source nature.

A short QEMU description

QEMU is described as an open-source machine emulator and virtualizer. Meaning that it can both act as your VM of choice, or even act as a machine virtualizer, mocking memory maps of actual hardware and producing effects (visible to the software running as a guest on it) highly similar to its corresponding hardware counterpart.

It currently supports a high variety of ARM guests, offering support for around fifty different machines. There is support offered for both “A-profile” CPUs, and “M-profile” CPUs as well, although in a more numerically limited fashion, machine-wise, for the latter.

Even though there is support offered for a variety of specific machines and SoCs, there is also a virt machine being offered. This machine, called virt, is a generic ARM system emulation variant. Virt is offering a variety of supported “cpus” (e.g. cortex-a15, cortex-a53, etc.), and different peripherals. The use of this machine is recommended only when the particularities of specific hardware and its limitation are ignored in the software development process.

System Requirements

QEMU is able to run on the following host platforms:

  • Linux
  • Microsoft Windows
  • macOS
  • some other UNIX platforms

Initialization

For the running of QEMU, there is a recommendation of using a Linux-based system as a host, at least for the specific case of this article. In this case, QEMU is running on an Ubuntu version 18.04.

The QEMU version used for this example is marked with the tag v.7.0.0 on GitHub.

DISCLAIMER: this is not a fully-fledged tutorial, if you expect to have a specific machine implemented at the end of it, with exact addresses and other parameters of a specific machine, I would recommend the tutorial mentioned in the References from S. Sopha (with the mention that the FreeRTOS tutorial required to check the machine implementation is not a complete one). The current work documents another perspective on how to implement a machine in QEMU, and it aims to be more explanatory than practical starting with the “Cloning already existing source and header files” part. Before this, the tutorial can be followed step by step and fulfills practical requirements.

Cloning of the Git Repository

For cloning the most recent version of QEMU, the following command has to be run:

				
					git clone https://github.com/qemu/qemu
				
			

Pre-build preparations

There are a number of dependencies required for successfully running QEMU, but the following script should suffice at least for the version 7.0.0 of QEMU:

				
					# Set up locales
apt-get update && apt-get install -y \
    apt-utils locales sudo && \
    dpkg-reconfigure locales && \
    locale-gen en_US.UTF-8 && \
    update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

apt-get update && apt-get install -y \
    git \
    build-essential \
    zlib1g-dev \
    pkg-config \
    libglib2.0-dev \
    binutils-dev \
    libboost-all-dev \
    autoconf \
    libtool \
    libssl-dev \
    libpixman-1-dev \
    libpython-dev \
    python-pip \
    python-capstone \
    virtualenv && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

apt update -y
apt upgrade -y
apt install ninja-build
				
			

Configuring and building

For configuring and building there is a need to be sure that a build directory exists inside of the QEMU repository. The selected configuration is marked in line 4 and is for a number of 32-bit and 64-bit ARM-supported machines. This configuration includes the virt generic machine, as well.

Warning: In case of performing modifications on source or header files already included by configure, there is no need to run configure again when compiling. Only make should be run, because of the time required to compile these numerous machine header and source files from ground zero.

				
					cd qemu 
mkdir build 
cd build 
../configure --target-list=aarch64-softmmu # preparing the build for ARM machines running on 64 and 32 bits
make
				
			

Checking the machine list

To check the machine list the following commands are used:

				
					cd qemu
cd build
cd aarch64-softmmu
./qemu-system-aarch64 -M help
				
			

This command is useful to check if the compilation process was performed correctly, and is going to be useful when trying to add the custom machine to QEMU.

Running a machine

Running of a machine might require a kernel image and a device tree file (.dtb – device tree blob file). The kernel image can be a buildroot compiled Linux kernel image, or even Yocto could be used for this task. The compilation of a Linux kernel image is outside of the scope of this tutorial. The .dtb file describes the memory mappings of the representing hardware.

				
					# Follow the steps from the pervious code snippet to get to the qemu-system-aarch64 file, or use make install instead of just make
./qemu-system-aarch64 -M <machine_selected_from_list> 
                      -kernel <path_to_kernel_image_file> \
                      -dtb <path_to_dtb_file>
				
			

Adding a new machine to QEMU

Let’s name the new machine arm-example or ARM_EXAMPLE, in order to be generic enough. This naming convention is used over the course of this section.

To have the machine appear when calling ./qemu-system-aarch64 -M help, it has to be added to Kconfig, meson.build files.

After adding the mentioned parts, the source and header files are copied from a similar enough machine to be further implemented.

Modifications have to be performed to variables representing the addresses of the machine. Also, there could be a need to modify constants representing machine name, type, clocks, and interrupt routines.

Adding machine to configuration

Considering it is an ARM 64-bit machine, first, there is a need to locate hw/arm directory inside of the qemu directory. There, the meson.build and Kconfig files are modified like in the snippets below:

Kconfig:

				
					config RASPI
    bool
    select FRAMEBUFFER
    select PL011 # UART
    select SDHCI
    select USB_DWC2

# beginning of added arm-example/ARM_EXAMPLE 
config ARM_EXAMPLE
    bool
    select PL011 # UART for a latter implementation, it can even be commented
# end of added arm-example/ARM_EXAMPLE

config STM32F100_SOC
    bool
    select ARM_V7M
    select STM32F2XX_USART
    select STM32F2XX_SPI
				
			

meson.build:

				
					arm_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_peripherals.c', 'bcm2836.c', 'raspi.c'))

# beginning of added arm-example/ARM_EXAMPLE file(s)
arm_ss.add(when: 'CONFIG_ARM_EXAMPLE', if_true: files('arm-example.c'))
# end of added arm-example/ARM_EXAMPLE file(s)

arm_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
				
			

In the configs/devices/ directory, the arm-softmmu.mak file has to be modified, as following.

arm-softmmu.mak:

				
					CONFIG_TOSA=y

# beginning of added arm-example/ARM_EXAMPLE
CONFIG_ARM_EXAMPLE=y
# end of added arm-example/ARM_EXAMPLE

CONFIG_Z2=y
				
			

Cloning already existing source and header files

The main raspi.c source file and its corresponding header and dependency files are either cloned or kept in original form for this example to work.

Copy the raspi.c file, and rename it to arm-example.c.

In the arm-example.c the following structure is going to be used:

  • header files;
  • constants – representing the name, firmware address, machine name macro initialization, etc.;
  • function board_ram_size – for retrieving board specific RAM memory size available;
  • function setup_boot – verifies if firmware exists and loads the kernel;
  • function arm_example_machine_init – initialization of different devices used by this machine (e.g. SoC);
  • function arm_example_machine_class_common_init – declares the names and some variables present in the MachineClass data structure;
  • function arm_example_machine_class_init – declares general names and variables in the MachineClass data structure;
  • constant arm_example_machine_types – declares fields used for registering the machine in the QEMU’s list of machines, links the initialization functions from above with the machine type;
  • call of the function defined by DEFINE_TYPES macro – used for appending the machine to the list of machines by calling the TypeInfo constant described exactly above.

Following, the snippets for these parts of code are introduced. The order is going to be from below to above.

DEFINE_TYPES macro

				
					DEFINE_TYPES(arm_example_machine_types)
				
			

CONSTANT arm_example_machine_types

				
					static const TypeInfo arm_example_machine_types[] = {
    {
        .name           = MACHINE_TYPE_NAME("arm-example"),
        .parent         = TYPE_ARM_EXAMPLE_MACHINE,
        .class_init     = arm_example_machine_class_init,
    },
    {
        .name           = TYPE_ARM_EXAMPLE_MACHINE,
        .parent         = TYPE_MACHINE,
        .instance_size  = sizeof(ARMExampleMachineState),
        .class_size     = sizeof(ARMExampleMachineClass),
        .abstract       = true, 
    }
};
				
			

FUNCTION arm_example_machine_class_init

				
					static void arm_example_machine_class_init(ObjectClass *oc, void *data)
{
    MachineClass *mc = MACHINE_CLASS(oc);
    ARMExampleMachineClass *smc = ARM_EXAMPLE_MACHINE_CLASS(oc);

    smc->board_rev = 0x920092;
    arm_example_machine_class_common_init(mc, smc->board_rev);
};
				
			

FUNCTION arm_example_machine_class_common_init

				
					static void arm_example_machine_class_common_init (MachineClass *mc, uint32_t board_rev) {
    mc->desc = "ARM EXAMPLE MACHINE (64-bit)";
    mc->init = arm_example_machine_init;
    mc->default_ram_size = board_ram_size(board_rev);
    mc->default_ram_id = "ram";
};
				
			

FUNCTION arm_example_machine_init

				
					static void arm_example_machine_init(MachineState *machine) {
    ARMExampleMachineClass *mc = ARM_EXAMPLE_MACHINE_GET_CLASS(machine);
    ARMExampleMachineState *s = ARM_EXAMPLE_MACHINE(machine);
    uint32_t board_rev = mc->board_rev;
    uint32_t vcram_size;
    uint64_t ram_size = board_ram_size(board_rev);
    
    if (machine->ram_size != ram_size) {
            char *size_str = size_to_str(ram_size);
            error_report("Invalid RAM size, should be %s", size_str);
            g_free(size_str);
            exit(1);
        }

    memory_region_add_subregion_overlap(get_system_memory(), 0,
                                             machine->ram, 0);

    /* Setup the SoC */
    object_initialize_child(OBJECT(machine), "soc", &s->soc, "arm-example-soc");
    object_property_add_const_link(OBJECT(&s->soc), "ram", OBJECT(machine->ram));
    object_property_set_int(OBJECT(&s->soc), "board-rev", board_rev, &error_abort);


    qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);

    vcram_size = object_property_get_uint(OBJECT(&s->soc), "vcram-size",
                                          &error_abort);
    setup_boot(machine, 0,
               machine->ram_size - vcram_size);
}
				
			

FUNCTION setup_boot

				
					static void setup_boot(MachineState *machine, int processor_id, 
                        size_t ram_size) 
{
    ARMExampleMachineState *s = ARM_EXAMPLE_MACHINE(machine);

    s->bdinfo.board_id = 0; // TODO: Change this, if required
    s->bdinfo.ram_size = ram_size;

    if (machine->firmware) {    
        int r = load_image_targphys(machine->firmware, FIRMWARE_ADDR, ram_size - FIRMWARE_ADDR);
        if (r < 0) {
            error_report("Failed to load firmware from %s", machine->firmware);
            exit(1);
        }
        s->bdinfo.entry = FIRMWARE_ADDR;
        s->bdinfo.firmware_loaded = true;
    }

    arm_load_kernel(&s->soc.cpu[0].core, machine, &s->bdinfo);
}
				
			

FUNCTION board_ram_size

				
					static uint64_t board_ram_size(uint32_t board_rev)
{
    assert(FIELD_EX32(board_rev, REV_CODE, STYLE)); /* Only new style */
    return 256 * MiB << FIELD_EX32(board_rev, REV_CODE, MEMORY_SIZE);
}
				
			

Constants

				
					#define BOARDSETUP_ADDR (ARM_EXAMPLE_RAM_START_ADDR + 0x20)
#define FIRMWARE_ADDR 0x8000 // TODO: Change this if needed

#define NAME_SIZE 20

FIELD(REV_CODE, REVISION,           0, 4);
FIELD(REV_CODE, TYPE,               4, 8);
FIELD(REV_CODE, PROCESSOR,         12, 4);
FIELD(REV_CODE, MANUFACTURER,      16, 4);
FIELD(REV_CODE, MEMORY_SIZE,       20, 3);
FIELD(REV_CODE, STYLE,             23, 1);

struct ARMExampleMachineState {
    /*< private >*/
    MachineState parent_obj;
    /*< public >*/
    ARMExample_SoCState soc;
    struct arm_boot_info bdinfo;
    // MemoryRegion ram;
};
typedef struct ARMExampleMachineState ARMExampleMachineState;

struct ARMExampleMachineClass {
    /*< private >*/
    MachineClass parent_obj;
    /*< public >*/
    uint32_t board_rev;
    // MemoryRegion ram;
    
};
typedef struct ARMExampleMachineClass ARMExampleMachineClass;

#define TYPE_ARM_EXAMPLE_MACHINE       MACHINE_TYPE_NAME("arm-example-common")
DECLARE_OBJ_CHECKERS(ARMExampleMachineState, ARMExampleMachineClass,
                     ARM_EXAMPLE_MACHINE, TYPE_ARM_EXAMPLE_MACHINE)
				
			

Header files

				
					#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/arm/arm-example.h"
#include "qemu/cutils.h"
#include "qemu/units.h"
#include "hw/registerfields.h"
#include "qemu/error-report.h"
#include "hw/arm/boot.h"
#include "hw/boards.h"
#include "qemu/module.h"
#include "qom/object.h"
#include "hw/loader.h"
				
			

Other corresponding files are the following:

  • arm_example.h

  • arm_example_soc.h

  • arm_example_soc.c

  • arm_example_peripherals.h

  • arm_example_peripherals.c

MODIFY arm_example.h

There is a requirement of implementing an enum containing the number of cpus, devices, timers, and other machine-specific devices. It could be named ARMExampleConfiguration.

There is also a need for an enum containing the entire memory map of the device wished to be implemented. The recommendation is to implement the entire memory map mentioned in the reference manual of the machine or development board to be implemented. It could be named ARMExampleMemoryMap.

The last enum to be implemented can represent the IRQs. As mentioned previously, the IRQs can be found in the Reference manual of the specific machine/device. It can be named ARMExampleIRQs.

MODIFY arm_example_soc.h

This header file contains includes the header files of peripherals and control parts. The constants for the name of the machine are declared, and a struct represents SoC’s state.

The aforementioned struct can be seen in the code snippet below:

				
					struct ARMExample_SoCState {
    /*< private >*/
    DeviceState parent_obj;
    /*< public >*/

    uint32_t enabled_cpus;
    struct {
        ARMCPU core;
    } cpu[ARM_EXAMPLE_SOC_NCPUS];
    BCM2835PeripheralState peripherals; // TODO: Change this implementation later
    BCM2836ControlState control;        // TODO: Change this implementation later
};
				
			

MODIFY arm_example_soc.c

This Source file focuses on adding the SoC device to the QEMU device tree.

If follows a path similar to arm_example.c, defining types, a types structure, initialization, and implementation functions, and declaration of constants. Like in the other source file example, the walkthrough is going to be from below to above.

DEFINE_TYPES macro:

				
					DEFINE_TYPES(arm_example_types)
				
			

Constant arm_example_types:

				
					static const TypeInfo arm_example_types[] = {
     {
        .name           = TYPE_ARM_EXAMPLE_SOC,
        .parent         = TYPE_DEVICE,
        .instance_size  = sizeof(ARMExample_SoCState),
        .instance_init  = s32g_init,
        .class_size     = sizeof(ARMExample_SOCClass),
        .class_init     = s32g_class_init,
        .abstract       = false,
    }
};
				
			

Function arm_example_class_init:

				
					static void arm_example_class_init(ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);
    ARMExample_SOCClass *sc = ARM_EXAMPLE_SOC_CLASS(oc);

    // dc->user_creatable = false; // TODO: Eliminate this one, if needed
    
    sc->cpu_type = ARM_CPU_TYPE_NAME("cortex-a53");
    sc->core_count = ARM_EXAMPLE_NCPUS;
    sc->peri_base = ARM_EXAMPLE_PERIPH_GR_0_ADDR; // peripheral base address
    sc->clusterid = 0xf; // No idea from where is this one.
    dc->realize = arm_example_soc_realize;
}
				
			

Function arm_example_soc_realize:

				
					static void s32g_soc_realize(DeviceState *dev, Error **errp)
{
    ARMExample_SoCState *s = ARM_EXAMPLE_SOC(dev);

    if (!arm_example_soc_common_realize(dev, errp)) {
        return;
    }

    if (!qdev_realize(DEVICE(&s->cpu[0].core), NULL, errp)) {
        return;
    }

    /* Connect irq/fiq outputs from the interrupt controller. */
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 0,
            qdev_get_gpio_in(DEVICE(&s->cpu[0].core), ARM_CPU_IRQ));
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->peripherals), 1,
            qdev_get_gpio_in(DEVICE(&s->cpu[0].core), ARM_CPU_FIQ));
}
				
			

Function arm_example_soc_common_realize:

				
					static bool arm_example_soc_common_realize(DeviceState *dev, Error **errp)
{
    ARMExample_SoCState *s = ARM_EXAMPLE_SOC(dev);
    // ARMExample_SOCClass *sc = ARM_EXAMPLE_SOC_GET_CLASS(dev);
    Object *obj;

    obj = object_property_get_link(OBJECT(dev), "ram", &error_abort);

    /* START: UNCOMMENT WHEN PERIPHERALS IMPLEMENTED */
    object_property_add_const_link(OBJECT(&s->peripherals), "ram", obj);

    if (!sysbus_realize(SYS_BUS_DEVICE(&s->peripherals), errp)) {
        return false;
    }

    // sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->peripherals), 0,
                            // sc->peri_base, 1);
    /* END: UNCOMMENT WHEN PERIPHERALS IMPLEMENTED */                            
    return true;                      
}  
				
			

Function arm_example_init:

				
					static void arm_example_init(Object *obj)
{
    ARMExample_SoCState *s = ARM_EXAMPLE_SOC(obj);
    ARMExample_SOCClass *sc = ARM_EXAMPLE_SOC_GET_CLASS(obj);
    int n;

    for (n = 0; n < sc->core_count; n++)
    {
        object_initialize_child(obj, "cpu[*]", &s->cpu[n].core, sc->cpu_type);
    }

    if (sc->core_count > 1) {
        qdev_property_add_static(DEVICE(obj), &arm_example_soc_enabled_cores_property);
        qdev_prop_set_uint32(DEVICE(obj), "enabled-cpus", sc->core_count);
    }

    if (sc->ctrl_base) {
        object_initialize_child(obj, "control", &s->control,
                                TYPE_BCM2836_CONTROL); // TODO: Change this, if required
    }

    object_initialize_child(obj, "peripherals", &s->peripherals,
                            TYPE_BCM2835_PERIPHERALS); // TODO: Change this, if required
    object_property_add_alias(obj, "board-rev", OBJECT(&s->peripherals),
                              "board-rev");
    object_property_add_alias(obj, "vcram-size", OBJECT(&s->peripherals),
                              "vcram-size");
} 
				
			

Constants:

				
					typedef struct ARMExample_SOCClass {
    /*< private >*/
    DeviceClass parent_class;
    /*< public >*/
    const char *name;
    const char *cpu_type;
    unsigned core_count;
    hwaddr peri_base;
    hwaddr ctrl_base;
    int clusterid;
} ARMExample_SOCClass;

#define ARM_EXAMPLE_SOC_CLASS(klass) \
    OBJECT_CLASS_CHECK(ARMExample_SOCClass, (klass), TYPE_ARM_EXAMPLE_SOC)

#define ARM_EXAMPLE_SOC_GET_CLASS(obj) \
    OBJECT_GET_CLASS(ARMExample_SOCClass, (obj), TYPE_ARM_EXAMPLE_SOC)

static Property arm_example_soc_enabled_cores_property =
    DEFINE_PROP_UINT32("enabled-cpus", ARMExample_SoCState, enabled_cpus, 0);
				
			

Header files:

				
					#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/arm/arm_example_soc.h"
#include "hw/arm/arm_example.h" // Maybe this one could and should be replaced
#include "hw/sysbus.h"
#include "hw/qdev-properties.h"
				
			

MODIFY arm_example_peripherals.h

In the following file a simplified implementation of the include/hw/arm/bcm2835_peripherals.h is chosen. especially when talking about the headers included. There is the choice of either implementing own arm_example_ic.h, arm_example_systmr.h, arm_example_fb.h, and arm_example_mbox.h, or using the original headers from the original implementation mentioned at the beginning of the paragraph.

ARMExamplePeripheralState struct:

				
					 struct ARMExamplePeripheralState {
    /* < private > */
    SysBusDevice parent_obj;

    /* < public > */
    MemoryRegion peri_mr, peri_mr_alias, gpu_bus_mr, mbow_mr;
    MemoryRegion ram_alias[4];
    qemu_irq irq, fiq;

    ARMExampleICState ic;
    ARMExampleSystemTimerState systmr;
    ARMExampleMboxState mboxes;
    ARMExampleFBState fb;
};
				
			

Constants:

				
					#define TYPE_ARM_EXAMPLE_PERIPHERALS "arm-example-peripherals"
OBJECT_DECLARE_SIMPLE_TYPE(ARMExamplePeripheralState, ARM_EXAMPLE_PERIPHERALS)
				
			

Headers:

				
					#include "hw/sysbus.h"
#include "exec/memory.h"
#include "qom/object.h"
#include "hw/intc/arm_example_ic.h"
#include "hw/timer/arm_example_systmr.h"
#include "hw/misc/unimp.h"
#include "hw/display/arm_example_fb.h"
#include "hw/misc/arm_example_mbox.h"
				
			

MODIFY arm_example_peripherals.c

type_init macro:

				
					type_init(arm_example_peripherals_register_types)
				
			

Function arm_example_peripherals_register_types:

				
					static void arm_example_peripherals_register_types(void) 
{
    type_register_static(&arm_example_peripherals_type_info);
}
				
			

Constant arm_example_peripherals_type_info:

				
					static const TypeInfo arm_example_peripherals_type_info = {
    .name = TYPE_ARM_EXAMPLE_PERIPHERALS,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(ARMExamplePeripheralState),
    .instance_init = arm_example_peripherals_init,
    .class_init = arm_example_peripherals_class_init,
};
				
			

Function arm_example_peripherals_class_init:

				
					static void arm_example_peripherals_class_init (ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);

    dc->realize = arm_example_peripherals_realize;
} 
				
			

Function arm_example_peripherals_class_init:

				
					static void arm_example_peripherals_realize (DeviceState *dev, Error **errp)
{
    ARMExamplePeripheralState *s = ARM_EXAMPLE_PERIPHERALS(dev);
    Object *obj;
    MemoryRegion *ram;
    Error *err = NULL;
    uint64_t ram_size, vcram_size;
    int n;

    obj = object_property_get_link(OBJECT(dev), "ram", &error_abort);

    ram = MEMORY_REGION(obj);
    ram_size = memory_region_size(ram);

    /* Interrupt Controller */
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ic), errp)) {
        return;
    }


    /* Sys Timer */
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->systmr), errp)) {
        return;
    }

    /* Framebuffer */
    vcram_size = object_property_get_uint(OBJECT(s), "vcram-size", &err);
    if (err) {
        error_propagate(errp, err);
        return;
    }

    if (!object_property_set_uint(OBJECT(&s->fb), "vcram-base",
                                  ram_size - vcram_size, errp)) {
        return;
    }

    if (!sysbus_realize(SYS_BUS_DEVICE(&s->fb), errp)) {
        return;
    }

    memory_region_add_subregion(&s->mbox_mr, MBOX_CHAN_FB << MBOX_AS_CHAN_SHIFT,
                sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->fb), 0));
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->fb), 0,
                       qdev_get_gpio_in(DEVICE(&s->mboxes), MBOX_CHAN_FB));
} 
				
			

Function arm_example_peripherals_init:

				
					static void arm_example_peripherals_init (Object *obj)
{
    ARMExamplePeripheralState *s = ARM_EXAMPLE_PERIPHERALS(dev);

    /* Memory region for peripheral devices, which we export to our parent */
    memory_region_init(&s->peri_mr, obj,"arm-example-peripherals", 0x1000000);  
    // TODO: Change this line above, if needed
    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->peri_mr);

    /* Interrupt Controller */
    object_initialize_child(obj, "ic", &s->ic, TYPE_ARM_EXAMPLE_IC);

    /* SYS Timer */
    object_initialize_child(obj, "systimer", &s->systmr,
                            TYPE_ARM_EXAMPLE_SYSTIMER);
} 
				
			
				
					#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/arm/arm_example_peripherals.h"
#include "hw/arm/arm_example.h"
#include "sysemu/sysemu.h"
				
			

Header files:

				
					#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/arm/arm_example_peripherals.h"
#include "hw/arm/arm_example.h"
#include "sysemu/sysemu.h"
				
			

Issues encountered

If you are an experienced programmer, probably you have encountered some line endings incompatibilities from time to time. The issue happens when you want to modify code on a Windows host in Visual Studio code, and want to run the QEMU code in a Linux container. There will be some errors about ‘/r’ incompatibilities.

Testing & Results

  • Testing

The testing can be performed either using GDB or using a kernel and running QEMU normally with the specific machine enabled. What is important when using a kernel image, is to use the properly compiled one, which respects the memory map and the IRQs of the implemented devices.

  • Results

Following the steps presented previously and making use of the Related materials should be enough for an experienced programmer to be able to implement a fully functional ARM-based machine in QEMU.

Conclusion

Although not ideal, there is an alternative to real hardware, especially in these times of hard try when having to deal with supply chain issues for chips, or even when having to address the issue of not having the physical device in production.

In conclusion, the task at hand might seem intimidating: to implement the virtualization of an actual machine and emulate its real effects in a virtual environment, but this abstractization should pave the road for even more recognition of QEMU as a valuable tool.

As a final idea, if the idea of hardware emulation is popularized enough, there might be useful to create a generic tool that creates the possibility of implementing at least the common parts of ARM machines in a simpler fashion than through coding or having to deal with a code-as-documentation situation like in the case of QEMU source code. There is a possibility that even a GUI solution might accelerate the innovation in the embedded domain even further.

References

See other articles:

Yocto Devtool

A command-line utility by the name of devtool serves as the foundation of the extensible SDK. With the help of this tool, you may

Read More »
outsourcing

Advantages of Outsourcing

Nowadays, most embedded systems have functionalities implemented in software. The usage of embedded software by electronics manufacturers for expanded functionality, improved quality, and reusability

Read More »

OpenRC

OpenRC is a dependency-based init system that was designed to work with Unix-like computer operating systems. It keeps compatibility with the system-provided init system,

Read More »

Yocto Project

Yocto Project is an open source community project that helps developers to create customized systems based on Linux. It has an accessible toolset that

Read More »

Software testing

The investigation of artifacts and the behavior of the software under test is known as software testing. It also determines whether the actual results

Read More »

Leave a Reply

Your email address will not be published.