qemu源代码分析
第一部分 qemu oldworld mac(heathrow)的初始化
约定:
qemu版本: 0.10.5
分析的目标机器: heathrow (oldmac)
目标处理器架构: PPC(包括7x0/750, BookE, FSL-BookE)
我们从hw/ppc_oldworld.c开始,函数:
static void ppc_heathrow_init(...);
其中的一个重要的函数:
void cpu_register_physical_memory(target_phys_addr_t start_addr, ram_addr_t size, ram_addr_t phys_offset);
/* register physical memory. 'size' must be a multiple of the target
page size. If (phys_offset & ~TARGET_PAGE_MASK) != 0, then it is an
io memory page. The address used when calling the IO function is
the offset from the start of the region, plus region_offset. Both
start_region and regon_offset are rounded down to a page boundary
before calculating this offset. This should not be a problem unless
the low bits of start_addr and region_offset differ. */
void cpu_register_physical_memory_offset(target_phys_addr_t start_addr,
ram_addr_t size,
ram_addr_t phys_offset,
ram_addr_t region_offset)
{
target_phys_addr_t addr, end_addr;
PhysPageDesc *p;
CPUState *env;
ram_addr_t orig_size = size;
void *subpage;
#ifdef USE_KQEMU
/* XXX: should not depend on cpu context */
env = first_cpu;
if (env->kqemu_enabled) {
kqemu_set_phys_mem(start_addr, size, phys_offset);
}
#endif
if (kvm_enabled())
kvm_set_phys_mem(start_addr, size, phys_offset);
if (phys_offset == IO_MEM_UNASSIGNED) {
region_offset = start_addr;
}
region_offset &= TARGET_PAGE_MASK;
size = (size + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK;
end_addr = start_addr + (target_phys_addr_t)size;
for(addr = start_addr; addr != end_addr; addr += TARGET_PAGE_SIZE) {
p = phys_page_find(addr >> TARGET_PAGE_BITS);
if (p && p->phys_offset != IO_MEM_UNASSIGNED) {
ram_addr_t orig_memory = p->phys_offset;
target_phys_addr_t start_addr2, end_addr2;
int need_subpage = 0;
CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr, end_addr2,
need_subpage);
if (need_subpage || phys_offset & IO_MEM_SUBWIDTH) {
if (!(orig_memory & IO_MEM_SUBPAGE)) {
subpage = subpage_init((addr & TARGET_PAGE_MASK),
&p->phys_offset, orig_memory,
p->region_offset);
} else {
subpage = io_mem_opaque[(orig_memory & ~TARGET_PAGE_MASK)
>> IO_MEM_SHIFT];
}
subpage_register(subpage, start_addr2, end_addr2, phys_offset,
region_offset);
p->region_offset = 0;
} else {
p->phys_offset = phys_offset;
if ((phys_offset & ~TARGET_PAGE_MASK) <= IO_MEM_ROM ||
(phys_offset & IO_MEM_ROMD))
phys_offset += TARGET_PAGE_SIZE;
}
} else {
p = phys_page_find_alloc(addr >> TARGET_PAGE_BITS, 1);
p->phys_offset = phys_offset;
p->region_offset = region_offset;
if ((phys_offset & ~TARGET_PAGE_MASK) <= IO_MEM_ROM ||
(phys_offset & IO_MEM_ROMD)) {
phys_offset += TARGET_PAGE_SIZE;
} else {
target_phys_addr_t start_addr2, end_addr2;
int need_subpage = 0;
CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr,
end_addr2, need_subpage);
if (need_subpage || phys_offset & IO_MEM_SUBWIDTH) {
subpage = subpage_init((addr & TARGET_PAGE_MASK),
&p->phys_offset, IO_MEM_UNASSIGNED,
addr & TARGET_PAGE_MASK);
subpage_register(subpage, start_addr2, end_addr2,
phys_offset, region_offset);
p->region_offset = 0;
}
}
}
region_offset += TARGET_PAGE_SIZE;
}
/* since each CPU stores ram addresses in its TLB cache, we must
reset the modified entries */
/* XXX: slow ! */
for(env = first_cpu; env != NULL; env = env->next_cpu) {
tlb_flush(env, 1);
}
}
==> phys_page_find() ==> phys_page_find_alloc(target_phys_addr_t index, int alloc);
static PhysPageDesc *phys_page_find_alloc(target_phys_addr_t index, int alloc)
{
void **lp, **p;
PhysPageDesc *pd;
p = (void **)l1_phys_map;
#if TARGET_PHYS_ADDR_SPACE_BITS > 32
#if TARGET_PHYS_ADDR_SPACE_BITS > (32 + L1_BITS)
#error unsupported TARGET_PHYS_ADDR_SPACE_BITS
#endif
lp = p + ((index >> (L1_BITS + L2_BITS)) & (L1_SIZE - 1));
p = *lp;
if (!p) {
/* allocate if not found */
if (!alloc)
return NULL;
p = qemu_vmalloc(sizeof(void *) * L1_SIZE);
memset(p, 0, sizeof(void *) * L1_SIZE);
*lp = p;
}
#endif
lp = p + ((index >> L2_BITS) & (L1_SIZE - 1));
pd = *lp;
if (!pd) {
int i;
/* allocate if not found */
if (!alloc)
return NULL;
pd = qemu_vmalloc(sizeof(PhysPageDesc) * L2_SIZE);
*lp = pd;
for (i = 0; i < L2_SIZE; i++) {
pd[i].phys_offset = IO_MEM_UNASSIGNED;
pd[i].region_offset = (index + i) << TARGET_PAGE_BITS;
}
}
return ((PhysPageDesc *)pd) + (index & (L2_SIZE - 1));
}
这段代码为qemu为target的页表管理的实现。
其中L1_BITS, L2_BITS, L1_SIZE, L2_SIZE分别一级页表以及二级页表的bits与size.
TARGET_PAGE_BITS为target的page需要的bits。
以传统的二级页表为例,他们的分配分别为:
L1_BITS = 10
L2_BITS = 10
PAGE_BITS = 12
l1_phys_map为一级页表的首地址。
在函数开始的时候
p = (void**)l1_phys_map;
因此
lp = p + ((index >> L2_BITS) & (L1_SIZE - 1));
pd = *lp;
lp为一级页表的offset,pd则为pgd[offset]。如果alloc==0,这个函数将返回:
return ((PhysPageDesc *)pd) + (index & (L2_SIZE - 1));
也就是二级页表的地址,也就是pte entry的地址;否则当搜索的pte entry不存在是,自动分配内存给要求的pte entry,并返回这个pte entry。
需要注意的是当TARGET_PHYS_ADDR_SPACE_BITS > 32时,通常我们需要三级页表,如对ppc64,TARGET_PHYS_ADDR_SPACE_BITS == 42,因此我们需要多做一个页表搜索才能得到pte entry的地址。
lp = p + ((index >> (L1_BITS + L2_BITS)) & (L1_SIZE - 1));
p = *lp;
L1_BITS,L2_BITS这里其实相当于第二级与第三级页表的bits。
index传进来的时候是一个地址右移了PAGE_BITS,经过上面的操作后就变成了三级页表的第一级的offset了(由于代码里写的是L1_BITS, L2BITS,我们可以假定第一级为L0_BITS)。
如果不考虑特殊情况,函数cpu_register_physical_memory()做的事情到此已经结束,即:搜索(分配)根据addr与size相关参数确定的pte entry。其中最重要的是我们知道qemu模拟了目标架构的MMU(softmmu).
// 考虑特殊情况(FIXME)
当ppc_heathrow_init()时,我们分别注册了以下内存区域:
ram_offset = qemu_ram_alloc(ram_size);
cpu_register_physical_memory(0, ram_size, ram_offset);
bios_offset = qemu_ram_alloc(BIOS_SIZE);
cpu_register_physical_memory(PROM_ADDR, BIOS_SIZE, bios_offset | IO_MEM_ROM);
重要函数:
/* return < 0 if error, otherwise the number of bytes loaded in memory */
int load_elf(const char *filename, int64_t address_offset,
uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr)
==> load_elf_32() or load_elf_64()
我们以load_elf_32为例(ppc heathrow),这个函数在文件elf_ops.h中定义;实际上这个文件做了一点小小的手段,导致我们定义SZ=32或SZ=64是,#include这个文件两次,我们就生成了需要的相关_xxx_32或是_xxx_64函数(都是静态的)。如:
static int glue(load_elf, SZ)(int fd, int64_t address_offset,
int must_swab, uint64_t *pentry,
uint64_t *lowaddr, uint64_t *highaddr)
==>
static int load_elf_32(...) or
static int load_elf_64(...)
为了便于阅读,我们假定SZ=32,将上面的函数略做替代之后,得到:
static int load_elf_32(int fd, int64_t address_offset,
int must_swab, uint64_t *pentry,
uint64_t *lowaddr, uint64_t *highaddr)
{
struct elfhdr ehdr;
struct elf_phdr *phdr = NULL, *ph;
int size, i, total_size;
elf_word mem_size;
uint64_t addr, low = 0, high = 0;
uint8_t *data = NULL;
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
goto fail;
if (must_swab) {
bswap_ehdr_32(&ehdr);
}
if (ELF_MACHINE != ehdr.e_machine)
goto fail;
if (pentry)
*pentry = (uint64_t)(elf_sword)ehdr.e_entry;
load_symbols_32(&ehdr, fd, must_swab);
size = ehdr.e_phnum * sizeof(phdr[0]);
lseek(fd, ehdr.e_phoff, SEEK_SET);
phdr = qemu_mallocz(size);
if (!phdr)
goto fail;
if (read(fd, phdr, size) != size)
goto fail;
if (must_swab) {
for(i = 0; i < ehdr.e_phnum; i++) {
ph = &phdr[i];
bswap_phdr_32(ph);
}
}
total_size = 0;
for(i = 0; i < ehdr.e_phnum; i++) {
ph = &phdr[i];
if (ph->p_type == PT_LOAD) {
mem_size = ph->p_memsz;
/* XXX: avoid allocating */
data = qemu_mallocz(mem_size);
if (ph->p_filesz > 0) {
if (lseek(fd, ph->p_offset, SEEK_SET) < 0)
goto fail;
if (read(fd, data, ph->p_filesz) != ph->p_filesz)
goto fail;
}
/* address_offset is hack for kernel images that are
linked at the wrong physical address. */
addr = ph->p_paddr + address_offset;
cpu_physical_memory_write_rom(addr, data, mem_size);
total_size += mem_size;
if (!low || addr < low)
low = addr;
if (!high || (addr + mem_size) > high)
high = addr + mem_size;
qemu_free(data);
data = NULL;
}
}
qemu_free(phdr);
if (lowaddr)
*lowaddr = (uint64_t)(elf_sword)low;
if (highaddr)
*highaddr = (uint64_t)(elf_sword)high;
return total_size;
fail:
qemu_free(data);
qemu_free(phdr);
return -1;
}
了解一点loader与ELF文件格式的都会知道,ELF文件的PT_LOAD字段要求被载入内存,这通常包含.text与.data字段,对于我们比较了解的x86来说,.text字段通常要求在0x0804_8000加载,因此loader会在loader这个ELF文件(executable)的时候请求在0x0804_8000映射内存,这个通常由一个mmap()调用达到目的。对于PPC来说,这个地址通常是0x1000_0000,.data字段通常在.text字段(连续)之后,当然ELF文件会要求load的时候被对齐(如对其到系统的页面大小4kb)。我们可以通过readelf -l /path/to/executable来获取相关信息,ELF entry通常就是_start函数开始的地址(当然是链接后的)。通过load_symbols_32(),qemu还加载了相应的.sym, .strtab字段。
函数load_elf() (load_elf_32)返回了@lowaddr与@highaddr,当一个ELF文件有多个PT_LOAD字段时,@lowaddr为所有映射字段地址里最低的,同理@highaddr为所有映射字段里面地址最高的结束为止 (addr + mem_size)。
与普通的loader不同的是,qemu loader无法将ELF文件要求映射的地址按其要求映射。qemu是一个普通的进程,可能无法再将ELF可执行文件映射到默认的entry地址(如主机为x86, 0x0804_8000已无法继续映射)。上面我们提到qemu模拟了目标机器的MMU(softmmu, 具有分页机制),因此类似一个正常的loader(通过mmap要求os映射到指定地址),我们需要请求qemu的MMU将目标ELF文件要求映射的地址做好映射。相关的工作由以下函数完成:
void cpu_physical_memory_write_rom(target_phys_addr_t addr,
const uint8_t *buf, int len)
{
int l;
uint8_t *ptr;
target_phys_addr_t page;
unsigned long pd;
PhysPageDesc *p;
while (len > 0) {
page = addr & TARGET_PAGE_MASK;
l = (page + TARGET_PAGE_SIZE) - addr;
if (l > len)
l = len;
p = phys_page_find(page >> TARGET_PAGE_BITS);
if (!p) {
pd = IO_MEM_UNASSIGNED;
} else {
pd = p->phys_offset;
}
if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM &&
(pd & ~TARGET_PAGE_MASK) != IO_MEM_ROM &&
!(pd & IO_MEM_ROMD)) {
/* do nothing */
} else {
unsigned long addr1;
addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK);
/* ROM/RAM case */
ptr = phys_ram_base + addr1;
memcpy(ptr, buf, l);
}
len -= l;
buf += l;
addr += l;
}
}
注意qemu仅仅映射了PT_LOAD字段,解析了符号表,返回了entry函数地址(以及设定@lowaddr/highaddr),但目前为止并未执行任何动作。
函数QEMUMachine->init (heathrow_init)在文件vl.c:main()中被调用(qemu 0.10.5, #5599):
machine->init(ram_size, vga_ram_size, boot_devices,
kernel_filename, kernel_cmdline, initrd_filename, cpu_model);
current_machine = machine;
我们可能有点奇怪qemu加载的ELF文件(可能为linux kernel或是firmware)是在哪里执行的?ppc_oldworld.c:ppc_heathrow_init.c()中有两处调用了load_elf(),第一次加载firmware,第二次加载linux kernel(如果有-kernel选项的话):
/* Load OpenBIOS (ELF) */
bios_size = load_elf(buf, 0, NULL, NULL, NULL);
...
kernel_size = load_elf(kernel_filename, kernel_base, NULL, &lowaddr, NULL);
两次加载的entry_point都没有指定(同加载一个正常的用户空间ELF文件不一样)。那heathrow是如何启动起来的?回答这个问题之前我们需要了解一点CPU初始化的知识。由于不了解heathrow(oldmac, 740?),以Freescale MPC85xx为例,CPU(指MPC85xx)上电的一刻,ip指向的位置为0xFFFF_FFFC(当然是虚拟地址,BookE PPC并没有所谓的“实”模式);CPU默认的ROM地址为0xFF80_0000,也就是4G的最后8M,我们可以不全部使用这8M,如u-boot就只使用了最后的512KB。但如果想让firmware工作正常,我们需要在0xFFFF_FFFC(在boot rom中)放一条跳转指令。以u-boot为例,这个指令将跳转至0xFFFF_E000,即最后一页。这里我们需要特别小心:由于CPU刚上电,几乎一切都没有被初始化;并且对于BookE(如MPC85xx, PPC 44x)的PPC,默认地址转换(即MMU)处于开启状态,因此CPU必须(默认行为)在TLB寄存器里在存储最后一页的映射。我们开始能使用的地址空间(包括指令,数据)就只有这一页(4KB)。在正确的初始化完CPU之前,试图访问这一页之外的数据会触发Data TLB Error或Instruction TLB Error异常,但这时候异常向量表并未被正确的设置,结果是很显然的(更多信息会在后续的u-boot初始化分析中给出)。
回到ppc_oldworld.c,ppc_heathrow_init()中有如下调用:
/* init CPUs */
if (cpu_model == NULL)
cpu_model = "G3";
for (i = 0; i < smp_cpus; i++) {
env = cpu_init(cpu_model);
if (!env) {
fprintf(stderr, "Unable to find PowerPC CPU definition\n");
exit(1);
}
/* Set time-base frequency to 16.6 Mhz */
cpu_ppc_tb_init(env, 16600000UL);
env->osi_call = vga_osi_call;
qemu_register_reset(&cpu_ppc_reset, env);
envs[i] = env;
}
注意函数ppc_init(const char* model),这将调用cpu_ppc_init():
CPUPPCState *cpu_ppc_init (const char *cpu_model)
{
CPUPPCState *env;
const ppc_def_t *def;
def = cpu_ppc_find_by_name(cpu_model);
if (!def)
return NULL;
env = qemu_mallocz(sizeof(CPUPPCState));
cpu_exec_init(env);
ppc_translate_init();
env->cpu_model_str = cpu_model;
cpu_ppc_register_internal(env, def);
cpu_ppc_reset(env);
if (kvm_enabled())
kvm_init_vcpu(env);
return env;
}
cpu_ppc_find_by_name()将导致qemu搜索注册的ppc_def数组(target-ppc/translate_init.c,后面会继续分析这个文件)。这个数据定义了所有类型(qemu支持的)的PPC的属性(如指令兼容类型,MMU类型,异常类型等等),目前对我们最重要的是里面的init_proc()函数指针。
cpu_ppc_register_internal(env, def)会调用注册的这个init_proc()函数指针,对heathrow来说,这个函数是init_proc_740() (target-ppc/translate_init.c)。其中调用了后面我们会提到的函数:init_excp_7x0()。
cpu_exec_init()并为做太多(我们关心的)工作,ppc_translate_init()主要用于初始化qemu的后端(我们将host machine的环境定义为前端,qemu的中间环境定义为中端,target machine成为后端)的相关机器代码定义及寄存器。因此目前对我们来说更重要的是函数: cpu_ppc_reset()。
void cpu_ppc_reset (void *opaque)
{
CPUPPCState *env = opaque;
target_ulong msr;
if (qemu_loglevel_mask(CPU_LOG_RESET)) {
qemu_log("CPU Reset (CPU %d)\n", env->cpu_index);
log_cpu_state(env, 0);
}
msr = (target_ulong)0;
if (0) {
/* XXX: find a suitable condition to enable the hypervisor mode */
msr |= (target_ulong)MSR_HVB;
}
msr |= (target_ulong)0 << MSR_AP; /* TO BE CHECKED */
msr |= (target_ulong)0 << MSR_SA; /* TO BE CHECKED */
msr |= (target_ulong)1 << MSR_EP;
#if defined (DO_SINGLE_STEP) && 0
/* Single step trace mode */
msr |= (target_ulong)1 << MSR_SE;
msr |= (target_ulong)1 << MSR_BE;
#endif
#if defined(CONFIG_USER_ONLY)
msr |= (target_ulong)1 << MSR_FP; /* Allow floating point usage */
msr |= (target_ulong)1 << MSR_VR; /* Allow altivec usage */
msr |= (target_ulong)1 << MSR_SPE; /* Allow SPE usage */
msr |= (target_ulong)1 << MSR_PR;
#else
env->nip = env->hreset_vector | env->excp_prefix;
if (env->mmu_model != POWERPC_MMU_REAL)
ppc_tlb_invalidate_all(env);
#endif
env->msr = msr & env->msr_mask;
hreg_compute_hflags(env);
env->reserve = (target_ulong)-1ULL;
/* Be sure no exception or interrupt is pending */
env->pending_interrupts = 0;
env->exception_index = POWERPC_EXCP_NONE;
env->error_code = 0;
/* Flush all TLBs */
tlb_flush(env, 1);
}
注意这几行:
env->nip = env->hreset_vector | env->excp_prefix;
if (env->mmu_model != POWERPC_MMU_REAL)
ppc_tlb_invalidate_all(env);
其中env->hreset_vector与excp_prefix分别定义为:
static void init_excp_7x0 (CPUPPCState *env)
{
#if !defined(CONFIG_USER_ONLY)
env->excp_vectors[POWERPC_EXCP_RESET] = 0x00000100;
env->excp_vectors[POWERPC_EXCP_MCHECK] = 0x00000200;
env->excp_vectors[POWERPC_EXCP_DSI] = 0x00000300;
env->excp_vectors[POWERPC_EXCP_ISI] = 0x00000400;
env->excp_vectors[POWERPC_EXCP_EXTERNAL] = 0x00000500;
env->excp_vectors[POWERPC_EXCP_ALIGN] = 0x00000600;
env->excp_vectors[POWERPC_EXCP_PROGRAM] = 0x00000700;
env->excp_vectors[POWERPC_EXCP_FPU] = 0x00000800;
env->excp_vectors[POWERPC_EXCP_DECR] = 0x00000900;
env->excp_vectors[POWERPC_EXCP_SYSCALL] = 0x00000C00;
env->excp_vectors[POWERPC_EXCP_TRACE] = 0x00000D00;
env->excp_vectors[POWERPC_EXCP_PERFM] = 0x00000F00;
env->excp_vectors[POWERPC_EXCP_IABR] = 0x00001300;
env->excp_vectors[POWERPC_EXCP_SMI] = 0x00001400;
env->excp_vectors[POWERPC_EXCP_THERM] = 0x00001700;
env->excp_prefix = 0x00000000UL;
/* Hardware reset vector */
env->hreset_vector = 0xFFFFFFFCUL;
#endif
}
细心的读者可能会发现这里的excp_vector都是(虚)低地址,而CPU初始化的时候能访问的仅仅最后一页而已。注意函数cpu_ppc_reset()中用这么一行(对MPC85xx是MSR.MSR_IS):
msr |= (target_ulong)1 << MSR_EP;
这个表示发生exception时,exception vector会与上这个值,即最终的exception vector实际为 excp_vectors | ((MSR_EP? 0xFFFF_0000): 0x0000_0000)。因此CPU未完全初始化完成的时候(可能已经能使用超过最后4KB的限制,但RAM未必已经成功初始化,因此内存尚不可用,通常用L1 cache模拟内存),异常也可以正确的被处理。操作体统如Linux在自身初始化异常/中断的时候会将MSR.MSR_EP重置为0,因此Linux如果发生异常/中断,可以在低地址被处理(Linux开始加载的时候RAM已经完全可用了)。
(关于更多的细节,后续在分析u-boot初始化的时候会给出)
函数cpu_ppc_reset()最后做了一些类似善后的工作,如tlb flush,注意这里并没有cache flush/invalidate,这是因为qemu并不做cache模拟。
到目前为止还没有完全解释cpu_ppc_reset之后如何与firmware连接在一起,再次留意文件ppc_oldworld.c:ppc_heathrow_init()中的这一条语句:
bios_size = load_elf(buf, 0, NULL, NULL, NULL);
并没有太多特殊的地方,不过可以相信的是load的这个firmware一定也是经过特殊处理过的(后续我们会分析u-boot,相对来说更清晰更容易理解)。
再次回到ppc_heathrow_init()函数,函数load_image()仅仅将所要求的文件全部载入缓冲区。
...
/* Register 2 MB of ISA IO space */
isa_mmio_init(0xfe000000, 0x00200000);
=>
void isa_mmio_init(target_phys_addr_t base, target_phys_addr_t size)
{
if (!isa_mmio_iomemtype) {
isa_mmio_iomemtype = cpu_register_io_memory(0, isa_mmio_read,
isa_mmio_write, NULL);
}
cpu_register_physical_memory(base, size, isa_mmio_iomemtype);
}
首先isa_mmio_init()调用了cpu_register_io_memory()分配了一个I/O区域的index,然后通过调用cpu_register_physical_memory(),注册内存区域@0xFE00_0000,size=2M到这个I/O MEM index。最重要的,将MMIO内存通过qemu softmmu注册到指定的区域。
...
for (i = 0; i < smp_cpus; i++) {
switch (PPC_INPUT(env)) {
case PPC_FLAGS_INPUT_6xx:
heathrow_irqs[i] = heathrow_irqs[0] + (i * 1);
heathrow_irqs[i][0] =
((qemu_irq *)env->irq_inputs)[PPC6xx_INPUT_INT];
break;
default:
cpu_abort(env, "Bus model not supported on OldWorld Mac machine\n");
exit(1);
}
}
/* init basic PC hardware */
if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) {
cpu_abort(env, "Only 6xx bus is supported on heathrow machine\n");
exit(1);
}
pic = heathrow_pic_init(&pic_mem_index, 1, heathrow_irqs);
注意heathrow_irqs[i][0] = ((qemu_irq*)env->irq_inputs)[PPC6xx_INPUT_INT];这一行。PPC_INPUT_INT就是PPC处理器的外部中断(级联/cascade到了第一个heathrow PIC的最低中断位)。因此通过这种方式,我们将heathrow PIC挂到了PPC的外部中断之上。通过heathrow_pic_init(),初始化目标的中断处理例程,注意heathrow_pic_init()返回的是一个qemu_irq。
注意这段代码:
/* update the CPU irq state */
static void heathrow_pic_update(HeathrowPICS *s)
{
if (check_irq(&s->pics[0]) || check_irq(&s->pics[1])) {
qemu_irq_raise(s->irqs[0]);
} else {
qemu_irq_lower(s->irqs[0]);
}
}
其中s->irqs为heathrow的所有PIC中断向量,包含两个PIC控制器,共支持64个中断,s->irqs[0]即为最低位的中断,亦即级联到CPU中断控制器的INT中断的那一位;heathrow_pic_update()会分发中断至上一层的中断控制器。heathrow_pic_init()之后,中断处理大概可以表示为下图:
+------+------+------+------+-----+
| HRST | SRST | ... | INT | ... |
+------+------+------+------+-----+
|
+------+------+-----+------+
|PIC 0 |PIC 1 | ... |PIC 63|
+------+------+-----+------+
当qemu_irq_raise()/qemu_irq_lower()的时候,相应的中断事件会被cascade的内部中断控制器处理,见函数hw/ppc.c:ppc6xx_set_irq():
...
case PPC6xx_INPUT_INT:
/* Level sensitive - active high */
LOG_IRQ("%s: set the external IRQ state to %d\n",
__func__, level);
ppc_set_irq(env, PPC_INTERRUPT_EXT, level);
...
qemu_irq *heathrow_pic_init(int *pmem_index,
int nb_cpus, qemu_irq **irqs)
{
HeathrowPICS *s;
s = qemu_mallocz(sizeof(HeathrowPICS));
/* only 1 CPU */
s->irqs = irqs[0];
*pmem_index = cpu_register_io_memory(0, pic_read, pic_write, s);
register_savevm("heathrow_pic", -1, 1, heathrow_pic_save,
heathrow_pic_load, s);
qemu_register_reset(heathrow_pic_reset, s);
heathrow_pic_reset(s);
return qemu_allocate_irqs(heathrow_pic_set_irq, s, 64);
}
(这里再次调用了cpu_register_io_memory()分配了一个IO MEM index。)
qemu_register_reset()注册了中断控制器的reset()函数。并通过显示调用heathrow_pic_reset(),初始化了(两个)PIC中断控制器。注意传递进来的@irqs为前面的heathrow_irqs,这个从env->irq_inputs中拷贝而来,而env->irq_inputs在调用cpu_ppc_init()=>init_proc_750()=>ppc6xx_irq_init()时被初始化。(target-ppc/translate_init.c:init_proc_750())。
static void heathrow_pic_reset(void *opaque)
{
HeathrowPICS *s = opaque;
heathrow_pic_reset_one(&s->pics[0]);
heathrow_pic_reset_one(&s->pics[1]);
s->pics[0].level_triggered = 0;
s->pics[1].level_triggered = 0x1ff00000;
}
由此我们也可以注意到PIC 0均为level triggered,PIC1的有9(可能是最后9个)个不是level triggered(如edge triggered,详情请参见hw/heathrow_pic.c)。
最后heathrow_pic_init()调用了qemu_allocate_irqs()分配了qemu的(虚拟)IRQ。
qemu虚拟的设备如ne2k_pci(hw/ne2000.c)在产生中断时调用qemu_set_irq(),由于我们调用了qemu_allocate_irqs,因此最终调用到了相应中断控制器(heathrow PIC)的代码,从而完成了对外部中断的虚拟。
后面涉及的vga与相关PCI,IDE驱动目前我们不予讨论。
(原创文章,未经作者同意,请勿转载,谢谢合作)
(未完待续)
阅读(4276) | 评论(0) | 转发(0) |