In the first article of this series, I showed you how a new machine can be added to QEMU. Now it’s time to implement the CPU emulation. This will be a longer process, as we need to specify multiple files and implement a number of functions. We will basically tell QEMU what features an AVR32 CPU has and how it behaves.

CPU definition

In this post, we will mostly work in the target/avr32 folder that you need to create now. Inside that folder, you need to place a file cpu.h with the following content:

#ifndef QEMU_AVR32A_CPU_H
#define QEMU_AVR32A_CPU_H

#include "cpu-qom.h"
#include "exec/cpu-defs.h"

#define AVR32_EXP 0x100
#define AVR32_EXP_S    AVR32_EXP | 0x30

//AVR32 CPUs have 12 general purpose registers, a PC, LR and a SP registers.
#define AVR32A_REG_PAGE_SIZE 16 // r0 - r12 + LR + SP + PC
#define AVR32A_PC_REG 15
#define AVR32A_LR_REG 14
#define AVR32A_SP_REG 13
//There are also 256 system registers.
#define AVR32A_SYS_REG 256

//The CPU need a definition
struct AVR32ACPUDef {
    const char* name;
    const char* parent_microarch; // AVR32A or AVR32B, two variants of the AVR32 instruction set.
    size_t core_type;
    size_t series_type;
    size_t clock_speed;
    bool audio; // MCUs with 'AU' suffix
    bool aes;  // MCUs with 'S' suffix
};
#define AVR32A_CPU_TYPE_SUFFIX "-" TYPE_AVR32A_CPU
#define AVR32A_CPU_TYPE_NAME(name) (name AVR32A_CPU_TYPE_SUFFIX)
#define CPU_RESOLVING_TYPE TYPE_AVR32A_CPU
// Global Interrupt Mask
#define AVR32_GM_FLAG(sr)       (sr & 0x10000) >> 16

//AVR32 instructions are 16 or 32 bits long. 32 bit instrucitons ALWAYS start with b'111'
#define AVR32_EXTENDED_INSTR_FORMAT_MASK 0b1110000000000000
#define AVR32_EXTENDED_INSTR_FORMAT_MASK_LE 0b11100000

As you can see, nearly all things in QEMU start with definitions. You can also see the most relevant facts about the AVR32 CPU that we need to consider, like the number of registers. The register #defines are only for our convenience, so we do not need to memorize if the PC register was number 15 or 14. We also should define some strings for the register names. There is also a special status register that holds information about the CPU state. For example, the first bit in this register is set to ‘1’ if an arithmetic operation results in an overflow. To be able to better address these bits, we also give them a name. If you want to know more about their purpose, look up the AVR32 Architecture Document.

Let’s add the following code:

static const char avr32_cpu_r_names[AVR32A_REG_PAGE_SIZE][8] = {
        "r0",  "r1",  "r2",  "r3",  "r4",  "r5",  "r6",  "r7",
        "r8",  "r9",  "r10",  "r11",  "r12",  "SP",  "LR",  "PC",
};

static const char avr32_cpu_sr_flag_names[32][8] = {
        "sregC", "sregZ", "sregN", "sregV", "sregQ", "sregL",
        "sreg6", "sreg7", "sreg8", "sreg9", "sreg10", "sreg11","sreg12","sreg13",
        "sregT", "sregR",
        "sregGM","sregI0M","sregI1M", "sregI2M", "sregI3M","sregEM","sregM0",
        "sregM1",
        "sregM2", "sreg25","sregD","sregDM","sregJ","sregH", "sreg30", "sregSS"
};

typedef struct CPUArchState {
    // Status Register
    uint sr;
    uint pc_w;

    uint32_t sflags[32];

    // Register File Registers
    uint32_t r[AVR32A_REG_PAGE_SIZE]; // 32 bits each

    //System registers
    uint32_t sysr[AVR32A_SYS_REG];

    //interrupt source
    int intsrc;
    int intlevel;
    uint64_t autovector;
    int isInInterrupt;

} CPUAVR32AState;

struct ArchCPU {
    /*< private >*/
    CPUState parent_obj;
    /*< public >*/

    CPUNegativeOffsetState neg;
    CPUAVR32AState env;
};

We also added the state definition for our CPU. You can see that the registers are part of the state. For later improvements, we also added some information about interrupts. We will implement interrupt handling in a later article. The ArchCPU is needed by QEMU to work with our CPU implementation. As you can see, the CPU state is a part of it.

Next, we need to define a number of functions. Some of them will be implemented in cpu.c later.

int avr32_print_insn(bfd_vma addr, disassemble_info *info);


static inline int cpu_interrupts_enabled(CPUAVR32AState* env)
{
    return AVR32_GM_FLAG(env->sr);
}

static inline int cpu_mmu_index(CPUAVR32AState *env, bool ifetch)
{
    // There is only one MMU, so that should be fine
    return 0;
}

static inline void cpu_get_tb_cpu_state(CPUAVR32AState *env, target_ulong *pc,
                                        target_ulong *cs_base, uint32_t *pflags)
{
    *pc = env->r[AVR32A_PC_REG];
    *cs_base = 0;
    *pflags = 0;
}

void avr32_tcg_init(void);
bool avr32_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
                        MMUAccessType access_type, int mmu_idx,
                        bool probe, uintptr_t retaddr);
void avr32_cpu_do_interrupt(CPUState *cpu);
void avr32_cpu_set_int(void *opaque, int irq, int level);
hwaddr avr32_cpu_get_phys_page_debug(CPUState *cs, vaddr addr);
int avr32_cpu_memory_rw_debug(CPUState *cs, vaddr addr, uint8_t *buf, int len, bool is_write);

void avr32_cpu_synchronize_from_tb(CPUState *cs, const TranslationBlock *tb);

#include "exec/cpu-all.h"

#endif /* !defined (QEMU_AVR32A_CPU_H) */

Now we can add the cup.c file. It contains the implementation of the function that we just defined.

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/qemu-print.h"
#include "exec/exec-all.h"
#include "cpu.h"
#include "disas/dis-asm.h"
#include "hw/core/tcg-cpu-ops.h"
#include "exec/address-spaces.h"
#include "exec/helper-proto.h"

static AVR32ACPU * cpu_self;
static bool first_reset = true;
static void avr32_cpu_disas_set_info(CPUState *cpu, disassemble_info *info)
{
    printf("[AVR32-DISAS] avr32_cpu_disas_set_info\n");
    //This was defined in the last article
    info->mach = bfd_arch_avr32;
}

//An init function, just like the one of the example micorcontroller
static void avr32a_cpu_init(Object* obj)
{
    //Generic CPU state from object
    CPUState *cs = CPU(obj);
    //Specific CPU from object
    AVR32ACPU *cpu = AVR32A_CPU(obj);
    //Specific state from CPU
    CPUAVR32AState *env = &cpu->env;

    cpu_set_cpustate_pointers(cpu);
    cs->env_ptr = env;
}

//Realize function, sets up the CPU
static void avr32_cpu_realizefn(DeviceState *dev, Error **errp)
{
    CPUState* cs = CPU(dev);
    cpu_self = AVR32A_CPU(cs);
    AVR32ACPUClass* acc = AVR32A_CPU_GET_CLASS(dev);
    Error *local_err = NULL;

    cpu_exec_realizefn(cs, &local_err);
    if (local_err != NULL) {
        error_propagate(errp, local_err);
        return;
    }

    qemu_init_vcpu(cs);
    cpu_reset(cs);

    acc->parent_realize(dev, errp);
}

//CPU reset function sets the CPU state to the default state that is
//defined in the instruction set manual. 
static void avr32_cpu_reset(DeviceState *dev)
{
    CPUState *cs = CPU(dev);
    AVR32ACPU *cpu = AVR32A_CPU(cs);
    AVR32ACPUClass* acc = AVR32A_CPU_GET_CLASS(dev);
    CPUAVR32AState* env = &cpu->env;

    env->isInInterrupt = 0;
    env->intlevel = 0;
    env->intsrc = -1;
    acc->parent_reset(dev);

    if(first_reset) {
        memset(env->r, 0, sizeof(env->r));
        memset(env->sflags, 1, sizeof(env->sflags));
        first_reset = false;
    }

    env->sr = 0;

    for(int i= 0; i< 32; i++){
        env->sflags[i] = 0;
    }
    env->sflags[16] = 1;
    env->sflags[21] = 1;
    env->sflags[22] = 1;


    for(int i= 0; i< AVR32A_SYS_REG; i++){
        env->sysr[i] = 0;
    }

    for(int i= 0; i< AVR32A_REG_PAGE_SIZE; i++){
        env->r[i] = 0;
    }

    printf("RESET 2\n");

    //Start address of the execution. Notice, that this is the start of the flash memory address
    //from the microcontroller implementation.
    env->r[AVR32A_PC_REG] = 0xd0000000;
    env->r[AVR32A_LR_REG] = 0;
    env->r[AVR32A_SP_REG] = 0;
}

The operations inside the reset function are different for every CPU architecture. In AVR32, the instruction set manual defines the state of the CPU registers to be zero. Except for three of the status bits. We also need to set the initial program counter here.

Let’s continue with some other needed functions:

static ObjectClass* avr32_cpu_class_by_name(const char *cpu_model){
    ObjectClass *oc;
    printf("[AVR32-TODO] avr32_cpu_class_by_name: %s\n", cpu_model);
    oc = object_class_by_name(AVR32A_CPU_TYPE_NAME("AVR32EXPC"));
    return oc;
}

static bool avr32_cpu_has_work(CPUState *cs)
{
    AVR32ACPU *cpu = AVR32A_CPU(cs);
    CPUAVR32AState *env = &cpu->env;

    return (cs->interrupt_request & CPU_INTERRUPT_HARD) &&
           cpu_interrupts_enabled(env);
}

//When using the '-d cpu' paramater, QEMU executes this function after every translation block
//to give us th CPU state at the begining of the block.
static void avr32_cpu_dump_state(CPUState *cs, FILE *f, int flags)
{
    AVR32ACPU *cpu = AVR32A_CPU(cs);
    //The CPU enviourment is used to access the content of the emulated registers.
    CPUAVR32AState *env = &cpu->env;

    qemu_fprintf(f, "PC:    " TARGET_FMT_lx "\n", env->r[AVR32A_PC_REG]);
    qemu_fprintf(f, "SP:    " TARGET_FMT_lx "\n", env->r[AVR32A_SP_REG]);
    qemu_fprintf(f, "LR:    " TARGET_FMT_lx "\n", env->r[AVR32A_LR_REG]);

    //Print general purpose registers.
    int i;
    for(i = 0;i < AVR32A_REG_PAGE_SIZE-3; ++i) {
        qemu_fprintf(f, "r%d:    " TARGET_FMT_lx "\n", i, env->r[i]);
    }

    //Print status register
    for(i= 0; i< 32; i++){
        qemu_fprintf(f, "%s:    " TARGET_FMT_lx "\n", avr32_cpu_sr_flag_names[i], env->sflags[i]);
    }
    qemu_fprintf(f, "\n");
}

static void avr32_cpu_set_pc(CPUState *cs, vaddr value)
{
    AVR32ACPU *cpu = AVR32A_CPU(cs);

    printf("[AVR32-CPU] avr32_cpu_set_pc, pc: %04lx\n", value);
    cpu->env.r[AVR32A_PC_REG] = value;
}

static bool avr32_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
{
    //TODO: Later
    return false;
}

#include "hw/core/sysemu-cpu-ops.h"

static const struct SysemuCPUOps avr32_sysemu_ops = {
        .get_phys_page_debug = avr32_cpu_get_phys_page_debug,
};

//Here we map the above function to their purpose, so that QEMU knows when to execute them.
static const struct TCGCPUOps avr32_tcg_ops = {
        .initialize = avr32_tcg_init,
        .synchronize_from_tb = avr32_cpu_synchronize_from_tb,
        .cpu_exec_interrupt = avr32_cpu_exec_interrupt,
        .tlb_fill = avr32_cpu_tlb_fill,
        .do_interrupt = avr32_cpu_do_interrupt,
};

void avr32_cpu_synchronize_from_tb(CPUState *cs, const TranslationBlock *tb){
    AVR32ACPU *cpu = AVR32A_CPU(cs);
    cpu->env.r[AVR32A_PC_REG] = tb->pc;
}

//The class init function has the same purpose than the one in the example board.
static void avr32a_cpu_class_init(ObjectClass *oc, void *data)
{
    printf("CPU-INIT!\n");
    AVR32ACPUClass *acc = AVR32A_CPU_CLASS(oc);
    CPUClass *cc = CPU_CLASS(oc);
    DeviceClass *dc = DEVICE_CLASS(oc);

    device_class_set_parent_realize(dc, avr32_cpu_realizefn, &acc->parent_realize);
    device_class_set_parent_reset(dc, avr32_cpu_reset, &acc->parent_reset);

    cc->class_by_name = avr32_cpu_class_by_name;

    cc->has_work = avr32_cpu_has_work;
    cc->dump_state = avr32_cpu_dump_state;
    cc->set_pc = avr32_cpu_set_pc;
    cc->memory_rw_debug = avr32_cpu_memory_rw_debug;
    cc->sysemu_ops = &avr32_sysemu_ops;
    cc->disas_set_info = avr32_cpu_disas_set_info;
    cc->tcg_ops = &avr32_tcg_ops;
}

static const AVR32ACPUDef avr32_cpu_defs[] = {
        {
                //This is the TYPE that was defined in avr32emp.h in the /hw folder
                //Here we tell QEMU that this CPU logic should be used with the example board/microncontroller
                .name = "AVR32EXPC",
                .parent_microarch = TYPE_AVR32A_CPU,
                .core_type = AVR32_EXP,
                .series_type = AVR32_EXP_S,
                .clock_speed = 66 * 1000 * 1000, /* 66 MHz */
                .audio = false,
                .aes = false
        }
};

static void avr32_cpu_cpudef_class_init(ObjectClass *oc, void *data)
{
    AVR32ACPUClass* acc = AVR32A_CPU_CLASS(oc);
    acc->cpu_def = data;
}

static char* avr32_cpu_type_name(const char* model_name)
{
    return g_strdup_printf(AVR32A_CPU_TYPE_NAME("%s"), model_name);
}

static void avr32_register_cpudef_type(const struct AVR32ACPUDef* def)
{
    char* cpu_model_name = avr32_cpu_type_name(def->name);
    TypeInfo ti = {
            .name = cpu_model_name,
            .parent = def->parent_microarch,
            .class_init = avr32_cpu_cpudef_class_init,
            .class_data = (void *)def,
    };

    type_register(&ti);
    g_free(cpu_model_name);
}

// We also have type definitions.
static const TypeInfo avr32_cpu_arch_types[] = {
        {
                .name = TYPE_AVR32A_CPU,
                .parent = TYPE_CPU,
                .instance_size = sizeof(AVR32ACPU),
                .instance_init = avr32a_cpu_init,
                .abstract = true,
                .class_size = sizeof(AVR32ACPUClass),
                .class_init = avr32a_cpu_class_init,
        },
        {
                .name = TYPE_AVR32B_CPU,
                .parent = TYPE_CPU,
                .instance_size = sizeof(AVR32ACPU),
                .instance_init = avr32b_cpu_init,
                .abstract = true,
                .class_size = sizeof(AVR32ACPUClass),
                .class_init = avr32b_cpu_class_init,
        }
};

static void avr32_cpu_register_types(void)
{
    int i;

    type_register_static_array(avr32_cpu_arch_types, 2);
    for (i = 0; i < ARRAY_SIZE(avr32_cpu_defs); i++) {
        avr32_register_cpudef_type(&avr32_cpu_defs[i]);
    }
}

type_init(avr32_cpu_register_types)

CPU metadata

QEMU still needs to know some meta information about our CPU. We need to create the file cpu-param.h and add the following content:

#ifndef AVR32A_CPU_PARAM_H
#define AVR32A_CPU_PARAM_H

#define TARGET_LONG_BITS 32
#define TARGET_PAGE_BITS 12

#define TARGET_PHYS_ADDR_SPACE_BITS 32
#define TARGET_VIRT_ADDR_SPACE_BITS 32

#endif // AVR32A_CPU_PARAM_H

Here, we define the address space and the length of memory pages.

The last file that we need for now is cpu-qom.h. It will contain the CPU class definition:

#ifndef AVR32A_CPU_QOM_H
#define AVR32A_CPU_QOM_H

#include "hw/core/cpu.h"
#include "qom/object.h"

#define TYPE_AVR32A_CPU "avr32a-cpu"
#define TYPE_AVR32B_CPU "avr32b-cpu"

OBJECT_DECLARE_CPU_TYPE(AVR32ACPU, AVR32ACPUClass, AVR32A_CPU)

typedef struct AVR32ACPUDef AVR32ACPUDef;
AVR32ACPUClass {
    /*< private >*/
    CPUClass parent_class;

    /*< public >*/
    DeviceRealize parent_realize;
    DeviceReset parent_reset;

    AVR32ACPUDef* cpu_def;
};

#endif // AVR32A_CPU_QOM_H

Registers an VMState

QEMU is able to stop emulated machines, make snapshots of their current state, and then resume this state in another instance. We need to define a function that gets and sets the content of the registers and then maps them to the corresponding QEMU bindings. This is done in the file machine.c:

#include "qemu/osdep.h"
#include "cpu.h"
#include "migration/cpu.h"

//We provide the status register to QEMU
static int get_sreg(QEMUFile *f, void *opaque, size_t size,
                    const VMStateField *field)
{
    uint32_t sreg;

    sreg = qemu_get_be32(f);
    return sreg;
}
//We set the status register.
static int put_sreg(QEMUFile *f, void *opaque, size_t size,
                    const VMStateField *field, JSONWriter *vmdesc)
{
    CPUAVR32AState *env = opaque;
    uint32_t sreg = env->sr;

    qemu_put_be32(f, sreg);
    return 0;
}
//The mapping
static const VMStateInfo vms_sreg = {
        .name = "sreg",
        .get = get_sreg,
        .put = put_sreg,
};
The VMState definition
const VMStateDescription vms_avr32_cpu = {
        .name = "cpu",
        .version_id = 1,
        .minimum_version_id = 1,
        .fields = (VMStateField[]) {

                VMSTATE_UINT32_ARRAY(env.r, AVR32ACPU, AVR32A_REG_PAGE_SIZE),

                VMSTATE_SINGLE(env.sr, AVR32ACPU , 0, vms_sreg, uint32_t),

                VMSTATE_END_OF_LIST()
        }
};

The next steps

Now we have to definition of our emulated CPU ready. But how does the magic happen? How does QEMU know what operation needs to be done when it wants to emulate a certain CPU instruction? This question will be the topic of my next article.