Transaction Level eMulator (TLMu)

Table of Contents

1 Introduction

TLMu extends the QEMU emulation framework making it possible to use QEMU systems:

TLMu can provide either only the CPU cores or it can provide a more complete partial system (e.g, CPU core + internal devices). The CPU core or the partial system can then be integrated with a system written in for example TLM-2.0.

There is good flexibility in how systems can be partitioned. TLMu can use RAM both internal to the TLMu instance and also external (provided by the main emulation system). Different TLMu instances within an emulator can cross access each others RAMs and devices (with some constrains).

TLMu can provide per core execution traces and GDB remote access for easy guest software debugging.

2 Supported Archs

At the moment, the following archs have basic support for TLMu:

I'd expect most (if not all) archs to work with little integration, but I haven't tested them. Also, one needs to decide howto connect the interrupt signals from the external emulator into the TLMu CPU core and write the glue logic.

3 Installation

3.1 Getting the sources

If you are reading this, you probably already cloned the git tree.

Clone the git tree, for example:

% git clone git://github.com/edgarigl/tlmu.git

3.2 Configuring

Create a build directory (or build it in the src dir) and enter it:

% mkdir build-tlmu
% cd build-tlmu

To get a list of supported configuration flags, add –help to the argument list. These are essentially the same as the ones for plain QEMU.

Configure TLMu:

% ../tlmu/configure --extra-cflags=-fPIC

If you want to install debug versions of TLMu, add –disable-strip to configure, e.g:

% ../tlmu/configure --extra-cflags=-fPIC --disable-strip

If you want to save time, and only build for a subset of the archs (e.g CRIS, ARM and MIPS):

% ../tlmu/configure --target-list=cris-softmmu,mipsel-softmmu,arm-softmmu --extra-cflags=-fPIC

3.3 Building

Build tlmu:

% make && make tlmu

Install tlmu:

% make install-tlmu DESTDIR=/tmp/my-tlmu/

Now you should have a TLMu lib per arch, for example:

% ls /tmp/my-tlmu/lib/
libtlmu-arm.so  libtlmu-cris.so  libtlmu-mipsel.so

And also the header files needed to interface with TLMu:

% ls /tmp/my-tlmu/include/tlmu/
tlmu.h  tlmu-qemuif.h

3.4 Running the examples

An easy way to get started is by taking a look at the examples distributed with TLMu. Look at tests/tlmu/*

There is a simple example in pure C and another one that shows integration with System-C TLM-2.0 (C++).

The examples instantiate 3 cores: a CRISv10, an ARM926 and a MIPS 24Kc. Three guest images are provided, <arch>-guest/guest. The guests all interact with a magic device that allows them to print:

Hello, I am the <arch>

and then exit.

Enter the test directory:

% cd build-tlmu/tests/tlmu

Now install a local copy of the tlmu sdk:

% make install-tlmu

Build the C example:

% make c_example

Run the C example

% LD_LIBRARY_PATH=./lib ./c_example
Hello, I am the CRIS
CRIS: STOP: 0
Hello, I am the MIPSEL
MIPS: STOP: 0
Hello, I am the ARM
ARM: STOP: 0

Before trying the System-C example, you will need to edit tests/tlmu/sc_example/Makefile. Set the paths to where your System-C and TLM-2.0 installation is located.

For example:

SYSTEMC = /opt/systemc
TLM2 = /opt/systemc/TLM-2009-07-15

After editing the Makefile, build the System-C example:

% make sc_example

Run the System-C example

% LD_LIBRARY_PATH=./lib ./sc_example/sc_example

             SystemC 2.2.0 --- Apr  1 2011 17:09:54
        Copyright (c) 1996-2006 by all Contributors
                    ALL RIGHTS RESERVED
HHHeeellllllooo,,,   III   aaammm   ttthhheee   MACIRRPMIS
SESTOP:  0 22030 ns

As a short-cut, you can build both examples by doing:

% make sc-all

And run both examples by doing:

% make run-sc-all

4 Internals

4.1 Overview

TLMu consists of a set of libraries with corresponding C header files. The CPU emulators built as shared libraries, one library per supported architecture (e.g libtlmu-arm.so). To load and use these shared emulator libraries, one can use the C-API provided by the libtlmu.a library. libtlmu provides a C interface to load, run, interact with (e.g make bus accesses, raise interrupts, etc) and finally quit each emulator.

4.2 C-API

4.2.1 Creating a TLMu instance

Before performing any TLMu operations, you need to get hold of a TLMu instance. This can be done by simply creating one and initializing it with tlmu_init().

/*
 * Initialize a TLMu instance. Takes an instance name as argument.
 */
void tlmu_init(struct tlmu *t, const char *name);

Example:

{
    /* Create an instance.  */
    struct tlmu t;
    /* and initialize it.  */
    tlmu_init(&ti, "myTLMu");
}

4.2.2 Loading a TLMu emulator

Loading of an emulator is done by calling tlmu_load. The arguments are a pointer to a TLMu instance and the name of the emulator library (e.g libtlmu-cris.so).

/*
 * Load an emulator library.
 *
 * Returns zero on success and non-zero if failing to load the
 * emulator.
 */
int tlmu_load(struct tlmu *t, const char *soname);

Example:

{
    int err;

    err = tlmu_load(&t, "libtlmu-arm.so");
    if (err) {
        fprintf(stderr, "Failed!!!!\n");
    }
}

4.2.3 Setting up the emulator

Setting up the emulator involves configuration of the QEMU arguments, setting various TLMu parameters and registering callbacks for the various events.

4.2.4 TLMu/QEMU argument list

The QEMU argument list is created by series of calls to tlmu_append_arg. The various options are documented in XXXXXXX.

/*
 * Append an argument to the TLMu instances argv list.
 */
void tlmu_append_arg(struct tlmu *t, const char *arg);

Example:

/* Choose a particular ARM model.  */
tlmu_append_arg(t, "-cpu");
tlmu_append_arg(t, "arm926");

/* Pre-load an elf image.  */
tlmu_append_arg(t, "-kernel");
tlmu_append_arg(t, "program.elf");

/* Enable tracing.  */
tlmu_append_arg(t, "-d");
tlmu_append_arg(t, "in_asm,exec,cpu");

4.2.5 Setting the log filename

When using enabling execution traces, it is useful to setup a per instance logfile to avoid having the different TLMu cores stepping on each other.

/*
 * Set the per TLMu instance log filename.
 *
 * t         - The TLMu instance
 * f         - Log filename
 */
void tlmu_set_log_filename(struct tlmu *t, const char *f);

4.2.6 Map RAM areas

Internally, TLMu differentiates pretty heavily between RAMs and other devices. TLMu needs to know if any of the external mappings provided by the main emulator are RAM devices.

At setup time, you'll need to tell TLMu by calling tlmu_map_ram():

/*
 * Tell the TLMu instance that a given memory area is maps to RAM.
 *
 * t         - The TLMu instance
 * name      - An name for the RAM
 * addr      - Base address
 * size      - Size of RAM
 * rw        - Zero if ROM, one if writes are allowed.
 */
void tlmu_map_ram(struct tlmu *t, const char *name,
                uint64_t addr, uint64_t size, int rw);

Example:

tlmu_map_ram(t, "rom", 0x18000000ULL, 128 * 1024, 0);

4.2.7 Registering callbacks

TLMu emulators will occasionally call back into your emulator to get certain things done. For example, when making bus accesses that map to external devices or memory provided by your emulator.

For TLMu emulators to know what functions to call, you need to register them. Similarly, for you (in the case you have multiple TLMu instances) to know which instance the call back belongs to, you need to register a pointer to whatever structure you'd like to be passed at every call back from TLMu.

/*
 * Register a callback function to be called when TLMu emulators need to
 * make bus accesses back into the main emulator.
 *
 * In the callback:
 *  o          - Is the registered instance pointer, see tlm_set_opaque().
 *  clk        - The current TLMu time. (-1 if invalid/unknown).
 *  rw         - 0 for reads, non-zero for write accesses.
 *  data       - Pointer to data
 *  len        - Requested transaction length
 *
 * The callback is expected to return 1 if the accessed unit supports DMI,
 * see tlmu_get_dmi_ptr for more info.
 */
void tlmu_set_bus_access_cb(struct tlmu *t,
                int (*access)(void *o, int64_t clk,
                                int rw, uint64_t addr, void *data, int len));
/*
 * Register a callback for debug accesses. The callback works similarly as
 * the one for tlmu_set_bus_access_cb, but it doesn't have a return value.
 *
 * Debug accesses will be made by various debug units, for example the GDB
 * stub or the tracing units when disassembling guest code.
 */
void tlmu_set_bus_access_dbg_cb(struct tlmu *t,
                void (*access)(void *, int64_t, int, uint64_t, void *, int));
/*
 * Register a callback to be called when the TLMu emulator requests a
 * Direct Memory Interface (DMI) area.
 */
void tlmu_set_bus_get_dmi_ptr_cb(struct tlmu *t,
                        void (*dmi)(void *, uint64_t, struct tlmu_dmi*));
/*
 * Register a callback function to be called at sync points.
 */
void tlmu_set_sync_cb(struct tlmu *t, void (*cb)(void *, int64_t));

Example:

    /* Register our callbacks.  */
    tlmu_set_bus_access_cb(t, tlm_bus_access);
    tlmu_set_bus_access_dbg_cb(t, tlm_bus_access_dbg);
    tlmu_set_bus_get_dmi_ptr_cb(t, tlm_get_dmi_ptr);
    tlmu_set_sync_cb(t, tlm_sync);

4.2.8 Running

Calling tlmu_run will start the TLMu emulator:

    /* Run.  */
    tlmu_run(t);

It is also possible to start the CPU emulator in sleep mode:

    tlmu_set_boot_state(t, TLMU_BOOT_SLEEPING);
    tlmu_run(t);

And later, you can wake it up:

    tlmu_notify_event(t, TLMU_TLM_EVENT_WAKE, NULL);

4.2.9 Timing

The recomended way to run TLMu is by using QEMU's icount feature. Specifically by passing "-icount 1" at setup time.

tlmu_append_arg(t, "-icount");
tlmu_append_arg(t, "1");

TLMu will synchronize at various sync points. These points are:

When TLMu synchronizes it will pass a clock value representing the amount of time passed as seen from within TLMu. When running with -icount 1, the time will be passed in nano seconds driven by an instruction counter that accounts 2ns per instruction. The main emulator can then transform the TLMu specific time into a global time based on the actual speed of the particular TLMu instance.

In some cases, TLMu will hit a sync point but without beeing able to synchronize. In these cases TLMu will pass -1 as the clk. The main emulator should treat -1 as a special case, and ignore the synchronization.

4.2.10 Bus accesses from TLMu

When TLMu cores need to make bus accesses into the main emulator, they do so by calling the bus_access callback or the bus_access_dbg callback. These callbacks can be registered per TLMu instance, see cb_registration for more info on howto register them.

The callback looks like this:

int my_bus_access(void *o, int64_t clk, int rw,
                        uint64_t addr, void *data, int len)

See cb_registration. for more info on what the arguments and return value mean.

4.2.11 Bus accesses into TLMu

The main emulator can also make bus accesses onto the TLMu system. These access are done by calling the tlmu_bus_access() function call.

tlmu_bus_access(t, rw, addr, data, len);

4.2.12 Interrupts

Interrupts are implemented in a machine dependant way. Depending on how you partition your system, interrupts are done differently. For example, if you model a TLMu system with only a CPU core, the system might export very few interrupt lines to the main emulator (e.g modelling only the interrupt signals that enter the CPU core). If you have a TLMu system with internal devices, e.g an interrupt controller, TLMu might export 32 or 64 or even more interrupt lines to the main emulator.

Regardless of how many interrupt lines and their exact meaning, the way the main emulator controls the lines is the same. It is done by notfiying events with the tlmu_notify_event() call.

struct tlmu_irq tirq;

tirq.data = 1; /* Raise interrupt line nr 0. Lower 1 - 31.  */
tirq.addr = 0;
tlmu_notify_event(t, TLMU_TLM_EVENT_IRQ, &tirq);

tirq.data = 3; /* Raise interrupt line nr 32 and 33. Lower 34 - 63.  */
tirq.addr = 4; /* Write to the second irq register.  */
tlmu_notify_event(t, TLMU_TLM_EVENT_IRQ, &tirq);

TLMu exports a set of 32bit registers that represent the interrupt pending bits. With tlmu_notify_event, the main emulator can modify the current state and raise / lower interrupts.

4.2.13 Direct Memory Interface

The direct memory interface allows both TLMu and the main emulator to setup fast access to memory models. If calls to the bus_access callback or to the tlmu_bus_access return 1, it means that the accessed device is a candidate for DMI. To set up the actual mapping, TLMu will call the get_dmi_ptr callback or the main emulator should call the tlmu_get_dmi_ptr() function call.

These calls take a struct tlmu_dmi pointer and fill it out.

struct tlmu_dmi
{
    void *ptr;                   /* Host pointer for direct access.  */
    uint64_t base;               /* Physical address represented by *ptr.  */
    uint64_t size;               /* Size of DMI mapping.  */
    int prot;                    /* Protection bits.  */
    unsigned int read_latency;   /* Read access delay.  */
    unsigned int write_latency;  /* Write access delay.  */
};

/*
 * Try to setup direct memory access to a RAM (or RAM like device).
 *
 * t      - pointer to the TLMu instance
 * dmi    - pointer to a tlmu_dmi structure to fill out.
 *
 * Return 1 if success.
 */
int tlmu_get_dmi_ptr(struct tlmu *t, struct tlmu_dmi *dmi);

4.2.14 Creating QEMU machines with TLMu support

Modifying a QEMU machine to get TLMu connections is fairly easy. You need to include some tlmu header files.

#include "tlm.h"
#include "tlm_mem.h"

Then you need to leave some address space empty, the space you want to pass over to the main emulator. If you want, it can be the entire address space or only portions of it.

Create tlm mappings:

/*
 * Map a TLMu area.
 *
 * env             - CPUState for the connected core.
 * addr            - Base address
 * size            - Size of mapping
 * sync_period_ns  - Sync timer interval
 * cpu_irq         - Interrupt lines
 * nr_irq          - Number of interrupt lines
 */
void tlm_map(CPUState *env, uint64_t addr, uint64_t size,
                           uint64_t sync_period_ns,
                           qemu_irq *cpu_irq, uint32_t nr_irq);

You can create multiple mappings, but only the first last one may connect to interrupts. This limitation might be removed in the future.

Good examples too look at are:

4.3 SystemC TLM-2.0 integration

4.3.1 Overview

An example on howto integrate TLMu into a SystemC TLM-2.0 system is provided in tests/tlmu/sc_example. There are many possible ways to connect TLMu into TLM-2.0, so sc_example is just one example of how it can be done. Let's look at it in more detail.

4.3.2 tlmu_sc.cc

The tlmu_sc class wraps TLMu into a System-C module with a set of TLM-2.0 sockets and methods to interact with the QEMU based emulators.

4.3.3 tlmu_sc TLM-2.0 methods

The most common methods you'll need to use are:

4.3.4 tlmu_sc TLM-2.0 sockets

TLM-2.0 sockets:

For example, when emulating a bare CPU in TLMu, all memory accesses leave TLMu and get issued as transactions on the from_tlmu_sk initiator socket.

If you are emulating a partial TLMu system (a CPU core with a set of peripherals), you can make bus accesses onto the TLMu bus by issuing transactions on the to_tlmu_sk target socket.

If you need to signal an interrupt to a TLMu CPU, you can issue a transaction to the to_tlmu_irq_sk target socket. These transactions will write or read directly to/from the interrupt pending registers. See interrupts for more info.