2009年4月7日火曜日

kernel 2.6.29 と差し替えてみる

QEMU のサイトからダウンロードできる arm-test 0.2 の kernel を差し替えてみる。

http://www.nongnu.org/qemu/arm-test-0.2.tar.gz

kernel.org から最新の 2.6.29 を取ってくる。

http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.29.tar.bz2

クロスコンパイラは、結局 CodeSourcery から持ってきた。

http://www.codesourcery.com/sgpp/lite/arm/portal/subscription?@template=lite

VMWare 上に最低限の Debian 5.0.0 環境を作ってインストール (単に tarball を展開するだけでも OK)。
.bashrc に

export LANG=C
export ARCH=arm
export CROSS_COMPILE=arm-none-linux-gnueabi-
export PATH=/path/to/CodeSourcery/Sourcery_G++_Lite/bin:$PATH

ARCH が大文字じゃないと駄目なので注意(参考サイト [1] では小文字になっててはまった…)。

参考サイト

[1] http://fedoraproject.org/wiki/Architectures/ARM/HowToQemu
[2] http://d.hatena.ne.jp/himainu/20080430


$ cp arch/arm/configs/versatile_defconfig .config
$ make menuconfig

Enable DHCP Support (CONFIG_IP_PNP_DHCP).

Networking -> Networking Support -> Networking Options -> TCP/IP Networking ->

Enable Universal Tun/Tap Driver Support (CONFIG_TUN)

Device Drivers -> Network Device Support ->

Enable ARM EABI Support (CONFIG_AEABI)

Kernel Features ->

Enable tmpfs support (CONFIG_TMPFS)
File Systems -> Pseudo File Systems ->

Enable PCI (CONFIG_PCI)

SCSI LOW level drivers -> CONFIG_SCSI_SYM53C8XX_2

$ make

できた arch/arm/boot/zImage を持ってきて、arm-test 以下に置いたと仮定して。

$ qemu-0.9.1/arm-softmmu/qemu-system-arm.exe -M versatilepb -kernel arm-test/zImage -initrd arm-test/arm_root.img

で起動した。ちょっとフォントが変だけど…

2009年4月3日金曜日

machine number

Linux Kernel は、ブートローダの中でマシンチェックを行っているので、r1 レジスタに適切な machine number を入れておかないとブートできない。

http://www.arm.linux.org.uk/developer/machines/

を見ると ARM Versatile Porting Board (versatile_pb) は 387 と書いてある。

QEMU の hw/versatilepb.c を見ると、vab_init() の中で versatile_init() の最後の引数に 0x183 を指定して呼び出している。まさしく 0x183 = 387。

というわけで、マジックナンバーの謎が解けた。linux kernel のロードのための引数だったのね。static

void vpb_init(int ram_size, int vga_ram_size,
const char *boot_device, DisplayState *ds,
const char *kernel_filename, const char *kernel_cmdline,
const char *initrd_filename, const char *cpu_model)
{
versatile_init(ram_size, vga_ram_size,
boot_device, ds,
kernel_filename, kernel_cmdline,
initrd_filename, cpu_model, 0x183);
}

...

QEMUMachine versatilepb_machine = {
"versatilepb",
"ARM Versatile/PB (ARM926EJ-S)",
vpb_init,
};

2009年2月7日土曜日

QEMU stdvga

http://hrb.osask.jp/wiki/?advance/QEMUVGA

vl.c の中でオプション解析。デフォルトは cirrus_vga_enable になってるみたい。

pci/isa_vga_init() で作ったビデオカードが stdvga 相当になるみたい。
(他はそれぞれ、vga の代わりに cirrus  や vmsvga が付いた初期化関数を使う)

2009年2月5日木曜日

QEMU base emulator

Bishop エミュレータ

やはりこの会社は、Android もやってるみたい。

2009年1月30日金曜日

parent_irq

QEMU の仮想デバイス (例えば i8259) には、parent_irq というメンバーが存在することが多い。

これにより、複数のデバイスの IRQ を繋げることができるようだ。なるほど、よくできてる。

大抵は、初期化の時に cpuirq などの、CPU と直結する IRQ (ハンドラの中で cpu_interrupt() を呼ぶ) を作っておいて、これをトップレベルの parent_irq とする。

2009年1月24日土曜日

ldl_code の続き

grep しても見つからないわけだ。disas_arm_insn 内の ldl_code の正体は、例のこれ。
#define ACCESS_TYPE (NB_MMU_MODES + 1)
#define MEMSUFFIX _code
#define env cpu_single_env

#define DATA_SIZE 1
#include "softmmu_header.h"

#define DATA_SIZE 2
#include "softmmu_header.h"

#define DATA_SIZE 4
#include "softmmu_header.h"

#define DATA_SIZE 8
#include "softmmu_header.h"

#undef ACCESS_TYPE
#undef MEMSUFFIX
#undef env

#endif
これで、softmmu_header.h 内の
/* generic load/store macros */

static inline RES_TYPE glue(glue(ld, USUFFIX), MEMSUFFIX)(target_ulong ptr)
{
    int index;
    RES_TYPE res;
    target_ulong addr;
    unsigned long physaddr;
    int mmu_idx;

    addr = ptr;
    index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
    mmu_idx = CPU_MMU_INDEX;
    if (__builtin_expect(env->tlb_table[mmu_idx][index].ADDR_READ !=
                         (addr & (TARGET_PAGE_MASK | (DATA_SIZE - 1))), 0)) {
        res = glue(glue(__ld, SUFFIX), MMUSUFFIX)(addr, mmu_idx);
    } else {
        physaddr = addr + env->tlb_table[mmu_idx][index].addend;
        res = glue(glue(ld, USUFFIX), _raw)((uint8_t *)physaddr);
    }
    return res;
}
これが tatic inline uint32_t  ldl_code(target_ulong ptr) に特殊化される。
/* generic load/store macros */

static inline uint32_t ldl_code(target_ulong ptr)
{
    int index;
    uint32_t res;
    target_ulong addr;
    unsigned long physaddr;
    int mmu_idx;

    addr = ptr;
    index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
    mmu_idx = CPU_MMU_INDEX;
    if (__builtin_expect(env->tlb_table[mmu_idx][index].ADDR_READ !=
                         (addr & (TARGET_PAGE_MASK | (DATA_SIZE - 1))), 0)) {
        res = __ldl_cmmu(addr, mmu_idx);
    } else {
        physaddr = addr + env->tlb_table[mmu_idx][index].addend;
        res = ldl_raw((uint8_t *)physaddr);
    }
    return res;
}
考えてみれば当たり前。全ての (QEMU 上の仮想 RAM への) メモリアクセスは (QEMU の仮想) MMU 経由で行わないといけないのだから。

2009年1月23日金曜日

disas_arm_insn

target-arm/translate.c の
static void disas_arm_insn(CPUState * env, DisasContext *s)
{
    unsigned int cond, insn, val, op1, i, shift, rm, rs, rn, rd, sh;

    insn = ldl_code(s->pc);
ここで s->pc は、ロードされたターゲットのマシン語の pc だ。

cpu-all.h を見ると。
#define ldl_code(p) ldl_raw(p)
#define ldl_raw(p) ldl_p(laddr((p)))
#define laddr(x) (uint8_t *)(long)(x)
#define ldl_p(p) ldl_le_p(p)  /* !defined(TARGET_WORDS_BIGENDIAN)  */
static inline int ldl_le_p(void *ptr)
{
    return *(uint32_t *)ptr;
}
う~ん、なぜこれで命令が取ってこれるのかよくわからない。
メモリのどこに、ターゲットの命令はロードされてるんだ ?
たぶん phys_ram_base とかだと思っていたんだけど、違うのかな ?

追記

これは CONFIG_USER_ONLY 版のコードなので、全く関係無かった。

しかし、どこに定義があるんだ… grep した限りでは見つけられなかった。

2009年1月22日木曜日

cpu_index

exec.c の cpu_exec_init() を見ればわかるけど、全ての cpu_init()  (実体は cpu_arm_init() などの define) で作った CPU (実体は target/ 以下の struct CPUARMState など) は、全てグローバル変数 first_cpu から作った順番に辿ることができる。

最初の CPU が cpu_index == 0 で、順番に全て番号が付いているので、番号で識別できる。

cpu_single_env も似たような感じに使われているけど、これは cpu-exec.c の cpu_exec 関数の内部だけで有効。

686    /* fail safe : never use cpu_single_env outside cpu_exec() */
687    cpu_single_env = NULL;
688    return ret;
689}
ここらへんからも、QEMU はマルチスレッド対応が考えられていないことがわかる。
/* 'pc' is the host PC at which the exception was raised. 'address' is
   the effective address of the memory exception. 'is_write' is 1 if a
   write caused the exception and otherwise 0'. 'old_set' is the
   signal set which should be restored */
static inline int handle_cpu_signal(unsigned long pc, unsigned long address,
                                    int is_write, sigset_t *old_set,
                                    void *puc)
{
    TranslationBlock *tb;
    int ret;

    if (cpu_single_env)
        env = cpu_single_env; /* XXX: find a correct solution for multithread */

2009年1月19日月曜日

cpu_interrpt() is probably not threadsafe.

う~む。
/* mask must never be zero, except for A20 change call */
void cpu_interrupt(CPUState *env, int mask)
{
。。。
    /* FIXME: This is probably not threadsafe.  A different thread could
       be in the middle of a read-modify-write operation.  */
    env->interrupt_request |= mask;
#if defined(USE_NPTL)
    /* FIXME: TB unchaining isn't SMP safe.  For now just ignore the
       problem and hope the cpu will stop of its own accord.  For userspace
       emulation this often isn't actually as bad as it sounds.  Often
       signals are used primarily to interrupt blocking syscalls.  */
#else
       。。。
}

QEMU internal timer interrupts

cpu_exec の内部では、TB (TranslationBlock) の実行ループが延々と行われている (TB には、ブレークポイントやジャンプ命令は入っていないので、無限ループを実行中でも必ず止まる。止まっては、また実行し、の繰り返し)。

しかし、これだけでは、仮想 CPU の上でエミュレートしている外部ペリフェラル (= 実行中のプログラム) などから割り込みが入らない限り、QEMU の内部の時間を進めることができなくなってしまう。

そこで QEMU は、自ら定期的に割り込み (CPU_INTERRUPT_EXIT) を CPU に入れることにより、cpu_exec 内部の TB (JIT 済みのコード)実行ループから抜けているようだ。これで、QEMU 内部の時間が進み、様々な内部処理を一定間隔で行うことが可能になる。

つまり、TB チェーンの実行を繰り返す小さなループと、定期的に割り込みをチェックして、割り込み要因に合わせて CPU の状態を変化させるための大きなループの、2 つのループが回っている。

exec.c に以下のようなコメントがある。
        /* CPU_INTERRUPT_EXIT isn't a real interrupt.  It just means
           an async event happened and we need to process it.  */
大きいほうのループを動かすため、小さいほうのループに定期的に割り込みを入れる際には、Windows のマルチメディアタイマーを使用しているようだ。

Windows 対応 patch の開発者の方のブログ記事。

(核心部分)

このほかにもたくさん参考になる記事があって、ゲスト OS の時計の進め方を正確にしようと思うと、非常に大変ということがよくわかる。

vl.c を使う (普通に QEMU システム全体を使う) 場合は考えなくても良いけど、仮想 CPU ライブラリ (libqemu.a) の部分だけを使う場合は、ここらへんも考えないとちゃんと動かない。

ちょっと気になった記事。デバッガと繋げる際のはまりポイント。

2009年1月15日木曜日

trunk

QEMU の Subversion から trunk (6292)  を取ってきたら、cc も host-cc も共に、 gcc 3 系列でも 4 系列でも無修正でビルド通って、正常に動いているようだ。素晴らしい。
svn co svn://svn.sv.gnu.org/qemu
$ cd ..
$ mkdir build
$ cd build
$ i686-pc-mingw32-gcc-4.3.2.exe -v
Using built-in specs.
Target: i686-pc-mingw32
Configured with: ../gcc-4.3.2/configure --with-as=/usr/local/bin/as --with-ld=/usr/local/bin/ld --with-gmp=/usr/local --with-mpfr=/usr/local --enable-languages=c,c++ --enable-threads --disable-nls --disable-win32-registry --disable-shared --without-x --enable-hash-synchronization --enable-libstdcxx-debug --enable-version-specific-runtime-libs --without-included-gettext --disable-bootstrap --disable-libssp --disable-libstdcxx-pch
Thread model: win32
gcc version 4.3.2 (GCC)
$ ../trunk/configure --cc=i686-pc-mingw32-gcc-4.3.2.exe --host-cc=i686-pc-
mingw32-gcc-4.3.2.exe --target-list=arm-softmmu
$ make
$ cd ..
$ wget http://bellard.org/qemu/arm-test-0.2.tar.gz
$ tar zxvf arm-test-0.2.tar.gz
$ cd arm-test
$ ../build/arm-softmmu/qemu-system-arm.exe -kernel zImage.integrator -initrd arm_root.img

2009年1月14日水曜日

softmmu_template

template というから、なんか自動生成しているのかと思っていたら、何のことは無かった。

単にプリプロセッサで切り替えているだけだった。
exec.c の一番最後。
#if !defined(CONFIG_USER_ONLY)

#define MMUSUFFIX _cmmu
#define GETPC() NULL
#define env cpu_single_env
#define SOFTMMU_CODE_ACCESS

#define SHIFT 0
#include "softmmu_template.h"

#define SHIFT 1
#include "softmmu_template.h"

#define SHIFT 2
#include "softmmu_template.h"

#define SHIFT 3
#include "softmmu_template.h"

#undef env

#endif
softmmu_template.h
#define DATA_SIZE (1 <<>

#if DATA_SIZE == 8
#define SUFFIX q
#define USUFFIX q
#define DATA_TYPE uint64_t
#elif DATA_SIZE == 4
#define SUFFIX l
#define USUFFIX l
#define DATA_TYPE uint32_t
#elif DATA_SIZE == 2
#define SUFFIX w
#define USUFFIX uw
#define DATA_TYPE uint16_t
#elif DATA_SIZE == 1
#define SUFFIX b
#define USUFFIX ub
#define DATA_TYPE uint8_t
#else
#error unsupported data size
#endif
要するに、0 が byte (uint8_t)、1 が word (uint16_t)、2 が long (uint32_t)、3 が quad word (uint64_t) ということらしい。
#if SHIFT <= 2
    io_mem_write[index][SHIFT](io_mem_opaque[index], physaddr, val);
みたいになっていて、0-1 (byte,word,long) それぞれのハンドラがコールされる。なるほど。

quad word の場合は、エンディアンに合わせて long (io_mem*[2]) のハンドラを 2 回コール。

2009年1月13日火曜日

global variables

io メモリの実体は exec.c に。
CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4];
CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4];
void *io_mem_opaque[IO_MEM_NB_ENTRIES];
物理メモリの実体は、ポインタが exec.c にあって、
int phys_ram_size;
uint8_t *phys_ram_base;

vl.c で確保される。
    /* init the memory */
    phys_ram_size = ram_size + vga_ram_size + MAX_BIOS_SIZE;
    phys_ram_base = qemu_vmalloc(phys_ram_size);
qemu_vmalloc(size) は osdep.c で定義されており、Windows の場合は VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE) が使われる。

io_memory

cpu_register_io_memory が返す整数値は、io メモリ配列の index に関連付けられたディスクリプタ
int cpu_register_io_memory(int io_index,
                           CPUReadMemoryFunc **mem_read,
                           CPUWriteMemoryFunc **mem_write,
                           void *opaque)
{
    int i, subwidth = 0;

    if (io_index <= 0) {
        if (io_mem_nb >= IO_MEM_NB_ENTRIES)
            return -1;
        io_index = io_mem_nb++;
    } else {
        if (io_index >= IO_MEM_NB_ENTRIES)
            return -1;
    }

    for(i = 0;i <>
        if (!mem_read[i] || !mem_write[i])
            subwidth = IO_MEM_SUBWIDTH;
        io_mem_read[io_index][i] = mem_read[i];
        io_mem_write[io_index][i] = mem_write[i];
    }
    io_mem_opaque[io_index] = opaque;
    return (io_index <<>
}
io_index を IO_MEM_SHIFT ぶん右シフトしたものが、io_mem* 配列の index になる。
CPUWriteMemoryFunc **cpu_get_io_memory_write(int io_index)
{
    return io_mem_write[io_index >> IO_MEM_SHIFT];
}

CPUReadMemoryFunc **cpu_get_io_memory_read(int io_index)
{
  
 return io_mem_read[io_index >> IO_MEM_SHIFT];
}
io メモリ配列の 0、1、2、4 は、それぞれ RAM、ROM、UNASSIGNED、NOTDIRTY に決め打たれているので、io_mem_nb は 5 からスタート。
static void io_mem_init(void)
{
    cpu_register_io_memory(IO_MEM_ROM >> IO_MEM_SHIFT, error_mem_read, unassigned_mem_write, NULL);
    cpu_register_io_memory(IO_MEM_UNASSIGNED >> IO_MEM_SHIFT, unassigned_mem_read, unassigned_mem_write, NULL);
    cpu_register_io_memory(IO_MEM_NOTDIRTY >> IO_MEM_SHIFT, error_mem_read, notdirty_mem_write, NULL);
    io_mem_nb = 5;

#if defined(CONFIG_SOFTMMU)
    io_mem_watch = cpu_register_io_memory(-1, watch_mem_read,
                                          watch_mem_write, NULL);
#endif
    /* alloc dirty bits array */
    phys_ram_dirty = qemu_vmalloc(phys_ram_size >> TARGET_PAGE_BITS);
    memset(phys_ram_dirty, 0xff, phys_ram_size >> TARGET_PAGE_BITS);
}
io メモリ配列は最大 IO_MEM_NB_ENTRIES 個で、最後の要素の index + 1 が io_mem_nb 変数。
exec.c
/* io memory support */
CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4];
CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4];
void *io_mem_opaque[IO_MEM_NB_ENTRIES];
static int io_mem_nb;
#if defined(CONFIG_SOFTMMU)
static int io_mem_watch;
#endif
cpu-all.h
/* physical memory access */
#define TLB_INVALID_MASK   (1 <<>
#define IO_MEM_SHIFT       4
#define IO_MEM_NB_ENTRIES  (1 << (TARGET_PAGE_BITS  - IO_MEM_SHIFT))

#define IO_MEM_RAM         (0 <<>
#define IO_MEM_ROM         (1 <<>
#define IO_MEM_UNASSIGNED  (2 <<>
#define IO_MEM_NOTDIRTY    (4 <<>
/* acts like a ROM when read and like a device when written. As an
   exception, the write memory callback gets the ram offset instead of
   the physical address */
#define IO_MEM_ROMD        (1)
#define IO_MEM_SUBPAGE     (2)
#define IO_MEM_SUBWIDTH    (4)
target-arm/cpu.h
#if defined(CONFIG_USER_ONLY)
#define TARGET_PAGE_BITS 12
#else
/* The ARM MMU allows 1k pages.  */
/* ??? Linux doesn't actually use these, and they're deprecated in recent
   architecture revisions.  Maybe a configure option to disable them.  */
#define TARGET_PAGE_BITS 10
#endif
通常は TARGET_PAGE_BITS が 10 なので IO_MEM_NB_ENTRIES は 64 かな。

物理メモリを割り付ける際には、cpu_register_physical_memory(0, ram_size, IO_MEM_RAM); のように、phys_offset を指定して呼び出すが、これが (phys_offset & ~TARGET_PAGE_MASK) != 0 の場合 io memory page と見なされ、start_addr から size までの範囲に io_mem*

TARGET_MAGE_MASK は
cpu-all.h
#define TARGET_PAGE_SIZE (1 <<>
#define TARGET_PAGE_MASK ~(TARGET_PAGE_SIZE - 1)
TARGET_PAGE_SIZE が 1024 で、TARGET_PAGE_MASK は 0 ~ 9 ビット目までのマスク。

実際に page に io_memory を関連付けるのは、subpage_register()。
static int subpage_register (subpage_t *mmio, uint32_t start, uint32_t end,
                             int memory)
{
    int idx, eidx;
    unsigned int i;

    if (start >= TARGET_PAGE_SIZE || end >= TARGET_PAGE_SIZE)
        return -1;
    idx = SUBPAGE_IDX(start);
    eidx = SUBPAGE_IDX(end);
#if defined(DEBUG_SUBPAGE)
    printf("%s: %p start %08x end %08x idx %08x eidx %08x mem %d\n", __func__,
           mmio, start, end, idx, eidx, memory);
#endif
    memory >>= IO_MEM_SHIFT;
    for (; idx <= eidx; idx++) {
        for (i = 0; i <>
            if (io_mem_read[memory][i]) {
                mmio->mem_read[idx][i] = &io_mem_read[memory][i];
                mmio->opaque[idx][0][i] = io_mem_opaque[memory];
            }
            if (io_mem_write[memory][i]) {
                mmio->mem_write[idx][i] = &io_mem_write[memory][i];
                mmio->opaque[idx][1][i] = io_mem_opaque[memory];
            }
        }
    }

    return 0;
}
subpage というのは、おそらく、通常のメモリページ以外のページ (io page など) という意味だと思う。

2009年1月8日木曜日

CPU から I/O 空間 (タイマー) へのアクセス

QEMU では、特定のアドレスにコールバックが結び付けてあって、I/O 空間にアクセスする。

具体的には、I/O メモリにアクセスすると cpu_register_io_memory() に渡したコールバックが実行される。
/* mem_read と mem_write は、byte にアクセスするための関数 (mem_*[0])、
word にアクセスするための関数 (mem_*[1])、そして double word にアクセス
するための関数の配列。関数は NULL 関数ポインタで省略することができる。
登録済みの関数は、後で動的に変更される可能性がある。
もし io_index が非ゼロならば、対応する I/O ゾーンが変更される。もしゼロならば、
新しい I/O ゾーンがアロケートされる。戻り値は cpu_register_physical_memory()
とともに使用することができる。もしエラーの場合は -1 が返る。*/

int cpu_register_io_memory(int io_index,
CPUReadMemoryFunc **mem_read, CPUWriteMemoryFunc **mem_write,
void *opaque);
つまり、全てのメモリアクセスについて、通常のメモリアクセスと I/O メモリのアクセスを判定して、適切に扱われないといけない。

QEMU は JIT 実行を行うので、コールバックを呼び出すためのコードを生成する必要がある。

そのやり方が、けっこう面倒なことをしているのでメモ。

まず、論文を読む。だいたいのことが書いてある。

QEMU, a Fast and Portable Dynamic Translator

現在のリリース最新版 QEMU (0.9.1) の JIT は、
  1. ターゲットマシン命令 (のマイクロオペレーション) をエミュレートする C コード片を書く
  2. GCC でコンパイルして、ホストマシン向けのオブジェクトファイルを作る (dyngen は ELF/PE-COFF/MACH-O に対応)
  3. オブジェクトファイルを dyngen で解析し、ターゲットマシン命令からマイクロオペレーション (ホストマシン命令列) トランスレータが include するヘッダを自動生成する
  • 命令に対応する関数のオブジェクトファイル中の位置などを記録したヘッダ
  • オブジェクトファイルの対応位置から命令列をバッファにコピーするコードが生成されたヘッダ (トランスレータ本体)
という、非常に斬新なやり方で実現されている。

(しかし、そのせいで、GCC の 3.x でしかちゃんと動かないという縛りができてしまっている。trunk のヘッドでは、Tiny Code Generator (TCG) という新しい JIT に差し替えられている)

ここのところが、プリプロセッサやトランスレータを駆使したコードの自動生成が行われまくるので、わかりにくい。

QEMU のビルドディレクトリの、例えば arm-softmmu などの下に自動生成されたヘッダファイル (op.h/gen-op.h/opc.h) が存在するので、確認。

ARM の store 命令を x86 命令列に JIT する時には、gen_op_stl_kernel などが呼ばれて、オペコード列が作られる (gen-op.h)。
static inline void gen_op_stl_kernel(void)
{
*gen_opc_ptr++ = INDEX_op_stl_kernel;
}

オペコード列から、最終的な x86 命令列が作られる。その対応表が opc.h。

opc.h には
...
DEF(stl_user, 0, 80)
...
DEF(stl_kernel, 0, 72)
のようになっている。stl_kernel は、store long の kernel モード時の命令に対応する。72 は、オブジェクトファイル中の関数のサイズ。
これらはトランスレータ本体 translater.c の中で使われる。
enum {
#define DEF(s, n, copy_size) INDEX_op_ ## s,
#include "opc.h"
#undef DEF
NB_OPS,
};

#include "gen-op.h"

そして最終的に op.h では
case INDEX_op_stl_kernel: {
extern void op_stl_kernel();
extern char __stl_mmu;
memcpy(gen_code_ptr, (void *)((char *)&op_stl_kernel+0), 72);
*(uint32_t *)(gen_code_ptr + 46) = (long)(&__stl_mmu) - (long)(gen_code_ptr + 46) + 0 -4;
gen_code_ptr += 72;
}
break;

のように、オブジェクトファイルからバッファの gen_code_ptr 位置に 72 バイトコピーし、___stl_mmu() 関数呼び出しのアドレスを差し替えている。

つまり、バッファの JIT 済みのコードの先頭にジャンプして実行された時、___ldl_mmu() が呼ばれて、その中でメモリの種類の判定や、コールバックの呼び出しなどが行われている。

___stl_mmu なんていう関数の定義は grep しても見つからない。プリプロセッサマクロで関数名が自動生成されている。MMUSUFFIX は softmmu_header.h で定義される。

本体は softmmu_template.h にある。その名のとおりのテンプレートで、アクセスする型のサイズや MMU の種類に応じて関数名が作られる。glue はプリプロセッサでシンボルをくっつけているだけ (foo ## 1 == glue(foo, 1) == foo1)
void REGPARM(2) glue(glue(__st, SUFFIX), MMUSUFFIX)(target_ulong addr,
DATA_TYPE val,
int mmu_idx)
{
target_phys_addr_t physaddr;
target_ulong tlb_addr;
void *retaddr;
int index;

index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
redo:
tlb_addr = env->tlb_table[mmu_idx][index].addr_write;
if ((addr & TARGET_PAGE_MASK) == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
physaddr = addr + env->tlb_table[mmu_idx][index].addend;
if (tlb_addr & ~TARGET_PAGE_MASK) {
/* IO access */
if ((addr & (DATA_SIZE - 1)) != 0)
goto do_unaligned_access;
retaddr = GETPC();
glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr);
} else if (((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
do_unaligned_access:
retaddr = GETPC();
#ifdef ALIGNED_ONLY
do_unaligned_access(addr, 1, mmu_idx, retaddr);
#endif
glue(glue(slow_st, SUFFIX), MMUSUFFIX)(addr, val,
mmu_idx, retaddr);
} else {
/* aligned/unaligned access in the same page */
#ifdef ALIGNED_ONLY
if ((addr & (DATA_SIZE - 1)) != 0) {
retaddr = GETPC();
do_unaligned_access(addr, 1, mmu_idx, retaddr);
}
#endif
glue(glue(st, SUFFIX), _raw)((uint8_t *)(long)physaddr, val);
}
} else {
/* the page is not in the TLB : fill it */
retaddr = GETPC();
#ifdef ALIGNED_ONLY
if ((addr & (DATA_SIZE - 1)) != 0)
do_unaligned_access(addr, 1, mmu_idx, retaddr);
#endif
tlb_fill(addr, 1, mmu_idx, retaddr);
goto redo;
}
}
static 関数の glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr); が呼ばれて (io_writel(...))、最終的に io_write[][](...) の形で、関数ポインタがコールされる。

2009年1月7日水曜日

タイマー割り込みが CPU に通知されるまで

前回の記事の続き。

main_loop_wait (vl.c) の中でイベントの有無がチェックされる。最後に bottom-halves がチェックされる。
void main_loop_wait(int timeout)
{


...

    /* Check bottom-halves last in case any of the earlier events triggered them. */
    qemu_bh_poll();
}

int qemu_bh_poll(void)
{
    QEMUBH *bh, **pbh;
    int ret;

    ret = 0;
    for(;;) {
        pbh = &first_bh;
        bh = *pbh;
        if (!bh)
            break;
        ret = 1;
        *pbh = bh->next;
        bh->scheduled = 0;
        bh->cb(bh->opaque);
    }
    return ret;
}
bh->cb(bh->opaque)  から sp804 という arm のタイマーモジュール (hw/arm_timer.c) に入っていく。cb は QEMUBHFunc * 型 (qemu-common.h:typedef void QEMUBHFunc(void *opaque);) の関数ポインタで、arm_timer_tick() が入ってる。opaque には arm_timer_state * が入ってる。
static void arm_timer_tick(void *opaque)
{
    arm_timer_state *s = (arm_timer_state *)opaque;
    s->int_level = 1;
    arm_timer_update(s);
}

typedef struct {
    ptimer_state *timer;
    uint32_t control;
    uint32_t limit;
    int freq;
    int int_level;
    qemu_irq irq;
} arm_timer_state;

/* Check all active timers, and schedule the next timer interrupt.  */

static void arm_timer_update(arm_timer_state *s)
{
    /* Update interrupts.  */
    if (s->int_level && (s->control & TIMER_CTRL_IE)) {
        qemu_irq_raise(s->irq);
    } else {
        qemu_irq_lower(s->irq);
    }
}
qemu_irq_raise は qemu_set_irq(irq, 1)。そして irq->handler(irq->opaque, irq->n, 1) となる。
handler には sp804_set_irq() 、opaque には sp804_state が入ってる。
typedef struct {
    void *timer[2];
    int level[2];
    uint32_t base;
    qemu_irq irq;
} sp804_state;

/* Merge the IRQs from the two component devices.  */
static void sp804_set_irq(void *opaque, int irq, int level)
{
    sp804_state *s = (sp804_state *)opaque;

    s->level[irq] = level;
    qemu_set_irq(s->irq, s->level[0] || s->level[1]);
}
そしてまた qemu_set_irq から handler が呼び出されるという流れで、ARM の GIC (Generic Interrupt Controller) の gic_set_irq (arm_gic.c) が呼び出される。
/* Process a change in an external IRQ input.  */
static void gic_set_irq(void *opaque, int irq, int level)
{
    gic_state *s = (gic_state *)opaque;
    /* The first external input line is internal interrupt 32.  */
    irq += 32;
    if (level == GIC_TEST_LEVEL(irq, ALL_CPU_MASK))
        return;

    if (level) {
        GIC_SET_LEVEL(irq, ALL_CPU_MASK);
        if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq)) {
            DPRINTF("Set %d pending mask %x\n", irq, GIC_TARGET(irq));
            GIC_SET_PENDING(irq, GIC_TARGET(irq));
        }
    } else {
        GIC_CLEAR_LEVEL(irq, ALL_CPU_MASK);
    }
    gic_update(s);
}
ここで GIC_SET_PENDING で gic_state を変化させて gic_update() に行く。
/* TODO: Many places that call this routine could be optimized.  */
/* Update interrupt status after enabled or pending bits have been changed.  */
static void gic_update(gic_state *s)
{
    int best_irq;
    int best_prio;
    int irq;
    int level;
    int cpu;
    int cm;

    for (cpu = 0; cpu <>
        cm = 1 <<>
        s->current_pending[cpu] = 1023;
        if (!s->enabled || !s->cpu_enabled[cpu]) {
   qemu_irq_lower(s->parent_irq[cpu]);
            return;
        }
        best_prio = 0x100;
        best_irq = 1023;
        for (irq = 0; irq <>
            if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq, cm)) {
                if (GIC_GET_PRIORITY(irq, cpu) <>
                    best_prio = GIC_GET_PRIORITY(irq, cpu);
                    best_irq = irq;
                }
            }
        }
        level = 0;
        if (best_prio <= s->priority_mask[cpu]) {
            s->current_pending[cpu] = best_irq;
            if (best_prio <>running_priority[cpu]) {
                DPRINTF("Raised pending IRQ %d\n", best_irq);
                level = 1;
            }
        }
        qemu_set_irq(s->parent_irq[cpu], level);
    }
}
qemu_set_irq(s->parent_irq[cpu], level); から、parent_irq[0] (CPU 1 つの場合) のハンドラを呼び出して、arm_pic_cpu_handler に行く。opaque は CPUState (ARMCPUState) で、これが最後。
最終的に cpu_interrupt(env, CPU_INTERRUPT_HARD) が呼ばれて、タイマー割り込みが CPU に通知される。

そして再び main_loop で cpu_exec(env) を実行して、その中で割り込み pending が入ってるから longjump で巻き戻して、という流れで割り込みを処理する。

2009年1月5日月曜日

main_loop

QEMU の基本構造は (vl.c)

int main() {
   オプション解析や初期化など。
   main_loop();
}

main_loop() {
  for(;;) {
    if(実行中) {

      for(;;) {
        ret = cpu_exec(); /* Basic Block 実行 */
        if(割り込みがあった) {
           ret = EXCP_INTERRUPT;
           break;
        }
     }

      if(ret == EXCP_HALTED)
        timeout = 10;
      else
         timeout = 0;

      main_loop_wait(timeout);
    }
  }
}

らしい。基本ブロックを実行して main_loop_wait() して、の繰り返し。

cpu_exec(env) {
  if(BB キャッシュあり) {
    そのまま実行 (機械語列に制御を渡す)
  } else {
    BB を JIT して、キャッシュしておく。
  }
}

main_loop_wait() {
    
  /* XXX: need to suppress polling by better using win32 events */
  ret = 0;
  for(pe = first_polling_entry; pe != NULL; pe = pe->next) {
      ret |= pe->func(pe->opaque);
  }

#ifdef _WIN32
  if(ret == 0) {
    ret = WaitForMultipleObjects(....., timeout);
#endif
}

timeout が 0 の場合、条件を調べて即制御を返す。

xen-ioemu

xen は ioemu という、qemu のコードを利用した機能で io 操作のエミュレートを実現しているらしい。