Zum Inhalt springen
Startseite » QEMU-Entwicklung

QEMU-Entwicklung

QEMU development during Global Chip Shortage

In Anbetracht der aktuellen Entwicklungen auf dem Weltmarkt, insbesondere der weltweiten Chip-Knappheit, gibt es einige Probleme, die bei der Bereitstellung von Software auf einer Embedded-Plattform zu lösen sind, aber es fehlt die Plattform selbst.

Das ist kein leichtes Unterfangen, insbesondere für Unternehmen, die Software für verschiedene Plattformen einsetzen und nicht die Möglichkeit haben, die Vielzahl der auf dem Markt erhältlichen SoCs auf Lager zu haben. Es gibt auch die Situation, dass ein neuer Chip in der Entwicklung ist und die Software dafür aus Effizienzgründen parallel entwickelt werden muss.

In Anbetracht all dessen muss es praktisch sein, Hardware auf unseren Standard-x64-Maschinen zu emulieren. Aber was tun wir, wenn unterschiedliche Speicherkarten oder sogar ISAs (Instruction Set Architectures) beteiligt sind? Bei dieser Aufgabe kommen die Fähigkeiten von QEMU zur Hilfe.

Dieser Artikel dokumentiert die Schritte, die erforderlich sind, um eine benutzerdefinierte Maschine zu QEMUs bestehenden Maschinen hinzuzufügen und die etablierten Konstrukte in QEMU zu nutzen und sogar die Open-Source-Natur des Projekts zu nutzen.

Eine kurze QEMU-Beschreibung

QEMU als Open-Source-Maschinenemulator und Virtualisierer. Das bedeutet, dass er sowohl als VM Ihrer Wahl als auch als Maschinenvirtualisierer fungieren kann, indem er Speicherabbildungen der tatsächlichen Hardware nachahmt und (für die als Gast ausgeführte Software sichtbare) Effekte erzeugt, die dem entsprechenden Hardware-Gegenstück sehr ähnlich sind.

Es unterstützt derzeit eine Vielzahl von ARM-Gästen und bietet Unterstützung für etwa fünfzig verschiedene Maschinen. Es werden sowohl „A-Profil"-CPUs als auch „M-Profil"-CPUs unterstützt, wenn auch in einer numerisch begrenzteren Form, was die Maschinen betrifft.

Obwohl Unterstützung für eine Vielzahl von spezifischen Maschinen und SoCs angeboten wird, wird auch eine virtuelle Maschine angeboten. Diese Maschine, virt genannt, ist eine allgemeine ARM-Systememulationsvariante. Virt bietet eine Vielzahl von unterstützten „cpus" (z. B. cortex-a15, cortex-a53 usw.) und verschiedene Peripheriegeräte an. Die Verwendung dieser Maschine ist nur dann zu empfehlen, wenn die Besonderheiten der spezifischen Hardware und ihre Beschränkungen bei der Softwareentwicklung ignoriert werden.

Systemanforderungen

QEMU kann auf den folgenden Host-Plattformen ausgeführt werden:

  • Linux
  • Microsoft Windows
  • macOS
  • einige andere UNIX-Plattformen

Initialisierung

Für die Ausführung von QEMU wird die Verwendung eines Linux-basierten Systems als Host empfohlen, zumindest für den speziellen Fall dieses Artikels. In diesem Fall läuft QEMU auf einer Ubuntu-Version 18.04.

Die für dieses Beispiel verwendete QEMU-Version ist auf GitHub mit dem Tag v.7.0.0 gekennzeichnet.

DISCLAIMER: Dies ist kein vollwertiges Tutorial, wenn Sie erwarten, dass Sie am Ende eine spezifische Maschine implementiert haben, mit genauen Adressen und anderen Parametern einer spezifischen Maschine, würde ich das Tutorial empfehlen, das in den Referenzen von S. Sopha erwähnt wird (mit dem Hinweis, dass das FreeRTOS-Tutorial, das benötigt wird, um die Maschinenimplementierung zu überprüfen, nicht vollständig ist). Die vorliegende Arbeit dokumentiert eine andere Sichtweise auf die Implementierung einer Maschine in QEMU und sie zielt darauf ab, mehr erklärend als praktisch zu sein, beginnend mit dem Teil „Klonen bereits existierender Quell- und Header-Dateien". Zuvor können Sie dem Tutorial Schritt für Schritt folgen und die praktischen Anforderungen erfüllen.

Klonen des Git-Repositorys

Um die aktuellste Version von QEMU zu klonen, sollten Sie den folgenden Befehl ausführen:

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

Vorbereitungen für den Aufbau

Es gibt eine Reihe von Abhängigkeiten, die für den erfolgreichen Betrieb von QEMU erforderlich sind, aber das folgende Skript sollte zumindest für die Version 7.0.0 von QEMU ausreichend sein:

				
					# 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
				

Konfigurieren und Erstellen

Für die Konfiguration und den Build muss sichergestellt werden, dass ein Build-Verzeichnis innerhalb des QEMU-Repositorys existiert. Die ausgewählte Konfiguration ist in Zeile 4 markiert und gilt für eine Reihe von 32-Bit- und 64-Bit-ARM-unterstützten Maschinen. Diese Konfiguration umfasst auch die virt generic machine.

! Warnung: Wenn Sie Änderungen an Quell- oder Headerdateien vornehmen, die bereits von configure eingebunden wurden, müssen Sie configure beim Kompilieren nicht erneut ausführen. Sie sollten nur make ausführen, da es sehr zeitaufwendig ist, diese zahlreichen Header- und Quelldateien von Grund auf zu kompilieren.

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

Prüfen der Maschinenliste

Um die Maschinenliste zu überprüfen, verwenden Sie die folgenden Befehle:

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

Dieser Befehl ist nützlich, um zu überprüfen, ob der Kompilierungsprozess korrekt durchgeführt wurde und er wird nützlich sein, wenn Sie versuchen, die benutzerdefinierte Maschine zu QEMU hinzuzufügen.

Betrieb eines Rechners

Zum Betrieb eines Rechners sind möglicherweise ein Kernel-Image und eine Gerätebaumdatei (.dtb - device tree blob file) erforderlich. Das Kernel-Image kann ein von buildroot kompiliertes Linux-Kernel-Image sein oder auch Yocto kann für diese Aufgabe verwendet werden. Die Kompilierung eines Linux-Kernel-Images liegt außerhalb des Rahmens dieses Tutorials. Die .dtb-Datei beschreibt die Speicherzuordnungen der darstellenden 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>
				

Hinzufügen eines neuen Rechners zu QEMU

Nennen wir den neuen Rechner arm-example oder ARM_EXAMPLE, um allgemein genug zu sein. Daher werden wir im weiteren Verlauf dieses Abschnitts diese Namenskonvention verwenden.

Damit der Rechner beim Aufruf von ./qemu-system-aarch64 -M help erscheint, müssen Sie ihn zu den Dateien Kconfig, meson.build hinzufügen.

Nach dem Hinzufügen der genannten Teile werden die Quell- und Headerdateien von einem Rechner kopiert, der ähnlich genug ist, um weiter implementiert zu werden.

An den Variablen, die die Adressen des Rechners darstellen, müssen Änderungen vorgenommen werden. Außerdem könnte es erforderlich sein, Konstanten zu ändern, die den Maschinennamen, den Typ, die Uhren und die Unterbrechungsroutinen darstellen.

Hinzufügen eines Rechners zur Konfiguration

Da es sich um einen ARM 64-Bit-Rechner handelt, muss zunächst das Verzeichnis hw/arm innerhalb des qemu-Verzeichnisses gefunden werden. Dort werden die Dateien meson.build und Kconfig wie in den folgenden Schnipseln geändert:

  • 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'))
				

Im Verzeichnis configs/devices/ müssen Sie die Datei arm-softmmu.mak wie folgt ändern.

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
				

Klonen bereits vorhandener Quell- und Header-Dateien

Die Hauptquelldatei raspi.c und die entsprechenden Header- und Abhängigkeitsdateien werden entweder geklont oder in ihrer ursprünglichen Form beibehalten, damit dieses Beispiel funktioniert.

Kopieren Sie die Datei raspi.c und benennen Sie sie in arm-example.c um.

In der Datei arm-example.c wird die folgende Struktur verwendet:

  • Header-Dateien;
  • Konstanten, die den Namen, die Firmware-Adresse, die Makroinitialisierung des Rechnernamens usw. darstellen;
  • Funktion board_ram_size - zum Abrufen der Größe des verfügbaren RAM-Speichers auf der Karte;
  • Funktion setup_boot - prüft, ob Firmware vorhanden ist und lädt den Kernel;
  • Funktion arm_example_machine_init - Initialisierung der verschiedenen Geräte, die von diesem Rechner verwendet werden (z. B. SoC);
  • Funktion arm_example_machine_class_common_init - deklariert die Namen und einige Variablen, die in der MachineClass Datenstruktur vorhanden sind;
  • Funktion arm_example_machine_class_init - deklariert allgemeine Namen und Variablen in der MachineClass Datenstruktur;
  • Constant arm_example_machine_types - deklariert Felder, die für die Registrierung der Maschine in der QEMU-Liste der Maschinen verwendet werden und verknüpft die Initialisierungsfunktionen von oben mit dem Maschinentyp;
  • Aufruf der durch das DEFINE_TYPES-Makro definierten Funktion - wird verwendet, um die Maschine durch Aufruf der oben beschriebenen TypeInfo-Konstante an die Liste der Maschinen anzuhängen.

Im Folgenden werden die Schnipsel für diese Teile des Codes vorgestellt. Die Reihenfolge ist von unten nach oben.

  • 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, 
    }
};
				
  • FUNKTION 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);
};
				
  • FUNKTION 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";
};
				
  • FUNKTION 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);
}
				
  • FUNKTION 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);
}
				
  • FUNKTION 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);
}
				
  • KONSTANTEN
				
					#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-DATEIEN
				
					#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"
				

Andere entsprechende Dateien sind folgende:

  • arm_example.h

  • arm_example_soc.h

  • arm_example_soc.c

  • arm_example_peripherals.h

  • arm_example_peripherals.c

  • MODIFY arm_example.h

Es ist erforderlich, eine enum zu implementieren, das die Anzahl der CPUs, Geräte, Zeitgeber und anderer maschinenspezifischer Geräte enthält. Wir können es ARMExampleConfiguration nennen.

Es besteht auch Bedarf an einer enum, die die gesamte Speicherkarte des zu implementierenden Geräts enthält. Es wird empfohlen, die gesamte Memory Map zu implementieren, die im Referenzhandbuch der zu implementierenden Maschine oder Entwicklungsplatine angegeben ist. Wir können sie ARMExampleMemoryMap nennen.

Die letzte zu implementierende enum kann die IRQs darstellen. Wie bereits erwähnt, können die IRQs im Referenzhandbuch des jeweiligen Geräts nachgelesen werden. Wir können es ARMExampleIRQs nennen.

  • MODIFY arm_example_soc.h

Diese Header-Datei enthält die Header-Dateien der Peripheriegeräte sowie der Steuerteile. Die Konstanten für den Namen der Maschine werden deklariert, und eine Struktur stellt den Zustand von SoC dar.

Sie können die oben erwähnte Struktur im nachstehenden Codeschnipsel sehen:

				
					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

Diese Quelldatei konzentriert sich auf das Hinzufügen des SoC-Geräts zum QEMU-Gerätebaum.

Wenn es einem ähnlichen Pfad wie arm_example.c folgt und Typen, eine Typenstruktur, Initialisierungs- und Implementierungsfunktionen definiert sowie die Deklaration von Konstanten. Wie in dem anderen Beispiel für die Quelldatei wird der Durchgang von unten nach oben erfolgen.

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,
    }
};
				
  • Funktion 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;
}
				
  • Funktion 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));
}
				
  • Funktion 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;                      
}  
				
  • Funktion 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");
} 
				
  • Konstanten:
				
					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-Dateien:
				
					#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 der folgenden Datei haben wir uns für eine vereinfachte Implementierung der Datei include/hw/arm/bcm2835_peripherals.h entschieden, vor allem wenn es um die enthaltenen Header geht Es besteht die Möglichkeit, entweder eigene arm_example_ic.h, arm_example_systmr.h, arm_example_fb.h und arm_example_mbox.h zu implementieren oder die ursprünglichen Header der eingangs erwähnten Originalimplementierung zu verwenden.

  • MODIFY arm_example.h

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;
};
				
  • Konstanten:
				
					#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)
				
  • Funktion 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,
};
				
  • Funktion 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;
} 
				
  • Funktion 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(&#038;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-Dateien:
				
					#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"
				

Auftretende Probleme

Wenn Sie ein erfahrener Programmierer sind, sind Sie wahrscheinlich von Zeit zu Zeit auf Inkompatibilitäten bei den Zeilenenden gestoßen. Das Problem tritt auf, wenn Sie Code auf einem Windows-Host in Visual Studio-Code ändern und den QEMU-Code in einem Linux-Container ausführen möchten. Es wird einige Fehlermeldungen über '/r'-Inkompatibilitäten geben.

Tests und Ergebnisse

  • Testen

Wir können die Tests entweder mit GDB oder mit einem Kernel durchführen und QEMU normal ausführen, wobei der spezifische Rechner aktiviert ist. Bei der Verwendung eines Kernel-Images ist es wichtig, das richtig kompilierte Image zu verwenden, das die Speicherzuordnung und die IRQs der implementierten Geräte berücksichtigt.

  • Ergebnisse

Wenn Sie die zuvor vorgestellten Schritte befolgen und die zugehörigen Materialien verwenden, sollte ein erfahrener Programmierer in der Lage sein, eine voll funktionsfähige ARM-basierte Maschine in QEMU zu implementieren.

Schlussfolgerung

Auch wenn es nicht ideal ist, gibt es eine Alternative zu echter Hardware, insbesondere in Zeiten, in denen die Lieferkette für Chips schwierig ist oder sogar das Problem auftritt, dass das physische Gerät nicht in der Produktion vorhanden ist.

Zusammenfassend lässt sich sagen, dass die Aufgabe, die vor uns liegt, einschüchternd erscheinen mag: die Virtualisierung einer tatsächlichen Maschine zu implementieren und ihre realen Auswirkungen in einer virtuellen Umgebung zu emulieren, aber diese Abstraktion sollte den Weg für eine noch größere Anerkennung von QEMU als wertvolles Werkzeug ebnen.

Wenn die Idee der Hardware-Emulation populär genug ist, könnte es nützlich sein, ein generisches Tool zu entwickeln, das die Möglichkeit bietet, zumindest die gängigen Teile von ARM-Maschinen auf einfachere Weise zu implementieren als durch Kodierung oder mit einer Code-als-Dokumentation-Situation wie im Fall des QEMU-Quellcodes umzugehen. Es besteht die Möglichkeit, dass sogar eine GUI-Lösung die Innovation im Embedded-Bereich noch weiter beschleunigen könnte.

Referenzen

Weitere Artikel anschauen:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website ist durch reCAPTCHA geschützt und es gelten die Datenschutzbestimmungen und Nutzungsbedingungen von Google