Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1367840
  • 博文数量: 488
  • 博客积分: 161
  • 博客等级: 入伍新兵
  • 技术积分: 5064
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-01 07:37
个人简介

只有偏执狂才能生存

文章分类

全部博文(488)

文章存档

2016年(10)

2015年(112)

2014年(66)

2013年(272)

2012年(28)

分类: LINUX

2013-11-03 21:19:21

原文地址:qemu源代码分析 (一) 作者:sigsegv11

                 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驱动目前我们不予讨论。

(原创文章,未经作者同意,请勿转载,谢谢合作)

(未完待续)

阅读(1550) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册