これにより、複数のデバイスの IRQ を繋げることができるようだ。なるほど、よくできてる。
大抵は、初期化の時に cpuirq などの、CPU と直結する IRQ (ハンドラの中で cpu_interrupt() を呼ぶ) を作っておいて、これをトップレベルの parent_irq とする。
これで、softmmu_header.h 内の#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
/* 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;}
/* 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;}
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);
#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;}
686 /* fail safe : never use cpu_single_env outside cpu_exec() */687 cpu_single_env = NULL;688 return ret;689}
/* 'pc' is the host PC at which the exception was raised. 'address' isthe effective address of the memory exception. 'is_write' is 1 if awrite caused the exception and otherwise 0'. 'old_set' is thesignal 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 */
/* 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 couldbe 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 theproblem and hope the cpu will stop of its own accord. For userspaceemulation this often isn't actually as bad as it sounds. Oftensignals are used primarily to interrupt blocking syscalls. */#else。。。}
/* CPU_INTERRUPT_EXIT isn't a real interrupt. It just meansan async event happened and we need to process it. */
$ svn co svn://svn.sv.gnu.org/qemu$ cd ..$ mkdir build$ cd build$ i686-pc-mingw32-gcc-4.3.2.exe -vUsing built-in specs.Target: i686-pc-mingw32Configured 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-pchThread model: win32gcc 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
#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
要するに、0 が byte (uint8_t)、1 が word (uint16_t)、2 が long (uint32_t)、3 が quad word (uint64_t) ということらしい。#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
#if SHIFT <= 2io_mem_write[index][SHIFT](io_mem_opaque[index], physaddr, val);
物理メモリの実体は、ポインタが 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];
int phys_ram_size;
uint8_t *phys_ram_base;
/* init the memory */phys_ram_size = ram_size + vga_ram_size + MAX_BIOS_SIZE;phys_ram_base = qemu_vmalloc(phys_ram_size);
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.ccpu-all.h/* 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;#endifsubpage というのは、おそらく、通常のメモリページ以外のページ (io page など) という意味だと思う。/* 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 anexception, the write memory callback gets the ram offset instead ofthe 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 recentarchitecture 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);#endifmemory >>= 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;}
/* 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);
static inline void gen_op_stl_kernel(void)
{
*gen_opc_ptr++ = INDEX_op_stl_kernel;
}
...のようになっている。stl_kernel は、store long の kernel モード時の命令に対応する。72 は、オブジェクトファイル中の関数のサイズ。
DEF(stl_user, 0, 80)
...
DEF(stl_kernel, 0, 72)
enum {
#define DEF(s, n, copy_size) INDEX_op_ ## s,
#include "opc.h"
#undef DEF
NB_OPS,
};
#include "gen-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;
void REGPARM(2) glue(glue(__st, SUFFIX), MMUSUFFIX)(target_ulong addr,static 関数の glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr); が呼ばれて (io_writel(...))、最終的に io_write[][](...) の形で、関数ポインタがコールされる。
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;
}
}
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;}
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);}}
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]);}
/* 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);}
/* 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);}}