Xen-hypervisor的Emulating Memory-mapped Input/Output (MMIO)实现

前言

对于type1的hypervisor(例如xen),虚拟机是如何访问IO的?下面分成原理部分和实现部分阐述。

原理部分

Stage 2 translation

hypervisor通过阶段2转换(Stage 2 translation)控制虚拟机(VM)中的内存视图。它允许hypervisor控制虚拟机可以访问哪些内存映射的系统资源,以及这些资源在虚拟机的地址空间中出现在哪里。
这种控制内存访问的能力对于隔离和沙盒来说非常重要。阶段2转换可用于确保VM只能看到分配给它的资源,而不能看到分配给其他VM或系统管理程序的资源。
对于存储器地址转换,阶段2转换是转换的第二阶段。为了支持这一点,需要一组新的转换表,称为阶段2表,如下所示:
在这里插入图片描述
操作系统(OS)控制一组转换表,这些转换表从虚拟地址空间映射到它认为的物理地址空间。然而,该过程经历到真实物理地址空间的第二次转换。第二阶段由hypervisor控制。
操作系统控制的翻译称为阶段1翻译,而hypervisor控制的翻译则称为阶段2翻译。OS认为是物理存储器的地址空间被称为中间物理地址(IPA)空间。

Emulating Memory-mapped Input/Output (MMIO)

与物理机器上的物理地址空间一样,虚拟机中的IPA空间包含用于访问内存和外围设备的区域,如下所示:
在这里插入图片描述
虚拟机可以使用外围区域来访问真实的物理外围设备(通常称为直接分配的外围设备)和虚拟外围设备。虚拟外围设备完全由hypervisor在软件中模拟,如下图所示:
在这里插入图片描述
分配的外围设备(assigned peripheral)是已分配给VM并映射到其IPA空间的真实物理设备。这允许在VM内运行的软件直接与外围设备交互。
虚拟外围设备(irtual peripheral)是hypervisor要在软件中模拟的外围设备。相应的第2阶段表条目将被标记为故障。虚拟机中的软件认为它可以直接与外围设备对话,但每次访问都会触发第2阶段的故障,hypervisor会在异常处理程序中模拟外围设备访问。
要模拟外围设备,hypervisor不仅需要知道哪个外围设备被访问,还需要知道该外围设备中的哪个寄存器被访问,访问是读还是写,访问的大小,以及用于传输数据的寄存器。

arm的手册《Exception Model》指南介绍了FAR_ELx寄存器。在处理阶段1故障时,这些寄存器会报告触发异常的虚拟地址。虚拟地址对hypervisor没有帮助,因为hypervisor通常不知道来宾操作系统是如何配置其虚拟地址空间的。对于第2阶段故障,有一个额外的寄存器HPFAR_EL2,它报告中止的地址的IPA。由于IPA空间由hypervisor控制,因此它可以使用这些信息来确定需要模拟的寄存器。

对于触发第2阶段故障的单个通用寄存器加载或存储,提供了额外的综合征信息。该信息包括访问的大小以及源或目的地寄存器,并允许hypervisor确定对虚拟外围设备的访问类型。

下图说明捕获模拟访问的过程:
在这里插入图片描述
此过程在以下步骤中进行了描述:
1.虚拟机中的软件试图访问虚拟外围设备。在本例中,这是一个虚拟UART的接收FIFO。
2.此访问在第2阶段转换时被阻止,导致路由到EL2的中止。

  • 中止使用有关异常的信息填充ESR_EL2,包括访问的字节数、目标寄存器以及它是加载还是存储。
  • 中止还会用中止访问的IPA填充HPFAR_EL2。

3.hypervisor使用来自ESR_EL2和HPFAR_EL2的信息来识别所访问的虚拟外围寄存器。此信息允许hypervisor模拟操作。然后,它通过ERET返回到vCPU。

  • 在LDR之后重新开始执行指令。

实现部分

我们看看Xen是如何实现对MMIO的处理的。

结构体

domain结构体上有vmmio,vmmio负责管理domain的MMIO。其中num_entries代表注册的mmio_handler个数,max_num_entries代表注册的mmio_handler的最多个数,handlers是mmio_handler数组的指针。

struct domain
{
struct arch_domain arch;
}

struct arch_domain
{
 struct vmmio vmmio;
}

struct vmmio {
    int num_entries;
    int max_num_entries;
    rwlock_t lock;
    struct mmio_handler *handlers;
};

mmio_handler如下,定义了起始地址(虚拟机的IPA),size,ops以及priv指针。ops会处理MMIO的读写,priv根据模拟的需要各个驱动自定义。

struct mmio_handler_ops {
    mmio_read_t read;
    mmio_write_t write;
};

struct mmio_handler {
    paddr_t addr;
    paddr_t size;
    const struct mmio_handler_ops *ops;
    void *priv;
};

初始化

创建相应domain的时候会,初始化vmmio。
domain_create
—>arch_domain_create
----->domain_io_init
domain_io_init的实现如下,主要申请了一个数组,用来管理mmio_handler。

int domain_io_init(struct domain *d, int max_count)
{
    rwlock_init(&d->arch.vmmio.lock);
    d->arch.vmmio.num_entries = 0;
    d->arch.vmmio.max_num_entries = max_count;
    d->arch.vmmio.handlers = xzalloc_array(struct mmio_handler, max_count);
    if ( !d->arch.vmmio.handlers )
        return -ENOMEM;

    return 0;
}

通过register_mmio_handler注册的handler来实现 xen在异常处理程序中模拟外围设备访问。
register_mmio_handler函数实现如下,第一个参数是domian结构体,该结构体上的vmmio用来管理需要模拟的IO。第二个参数是模拟IO实现的mmio_handler_ops。第三个参数是虚拟机的IO的IPA,第四个参数是虚拟机的IO的size,第五个参数是private。

void register_mmio_handler(struct domain *d,
                           const struct mmio_handler_ops *ops,
                           paddr_t addr, paddr_t size, void *priv)
{
    struct vmmio *vmmio = &d->arch.vmmio;
    struct mmio_handler *handler;

    BUG_ON(vmmio->num_entries >= vmmio->max_num_entries);

    write_lock(&vmmio->lock);

    handler = &vmmio->handlers[vmmio->num_entries];//vmmio中空闲的handler
   
    handler->ops = ops;
    handler->addr = addr;
    handler->size = size;
    handler->priv = priv;

    vmmio->num_entries++;

    /* Sort mmio handlers in ascending order based on base address */
    sort(vmmio->handlers, vmmio->num_entries, sizeof(struct mmio_handler),//根据IPA进行排序。
         cmp_mmio_handler, swap_mmio_handler);

    write_unlock(&vmmio->lock);
}

首先取vmmio中空闲的handler。其次将ops,addr,size和priv依次赋值给handler。再次根据IPA,handler对进行排序。
这里虚拟机的IPA的值,应该同xen中的PA是值地址相同,xen中模拟虚拟机的IPA的读写。

中断处理

虚拟机对于的IO访问,由于只建立了阶段1转换(Stage 1 translation),所以会产生同步异常,这个异常会被xen捕获(参考原理部分)。调用do_trap_guest_sync处理来自虚拟机的同步异常。do_trap_guest_sync主要根据ESR_EL2的EC(错误类型->Exception Class.)来调用不同的函数处理ISS(Instruction Specific Syndrome)。

void do_trap_guest_sync(struct cpu_user_regs *regs)
{
    const union hsr hsr = { .bits = regs->hsr }; // 读取ESR_EL2寄存器,以获取同步异常产生的类型

    switch ( hsr.ec )
    {
  ...
        case HSR_EC_DATA_ABORT_LOWER_EL:
        perfc_incr(trap_dabt);
        do_trap_stage2_abort_guest(regs, hsr);
        break;
  ...
      }
}

#define HSR_EC_INSTR_ABORT_LOWER_EL 0x20
#define HSR_EC_INSTR_ABORT_CURR_EL  0x21
#define HSR_EC_DATA_ABORT_LOWER_EL  0x24
#define HSR_EC_DATA_ABORT_CURR_EL   0x25

ESR_EL2的EC值,0b100100代表Data Abort from a lower Exception level
在这里插入图片描述
同步异常会读取ESR_EL2寄存器,以获取同步异常产生的类型,即EC值如上图arm手册。根据EC值的不同调用不同的函数处理异常。这里调用do_trap_stage2_abort_guest,去解析ESR_EL2的ISS字段。

static void do_trap_stage2_abort_guest(struct cpu_user_regs *regs,
                                       const union hsr hsr)
{
	    /*
     * The encoding of hsr_iabt is a subset of hsr_dabt. So use
     * hsr_dabt to represent an abort fault.
     */
    const struct hsr_xabt xabt = hsr.xabt;//ISS的DABT和IABT状态标志位。
    int rc;
    vaddr_t gva; //触发同步异常的VA
    paddr_t gpa; //触发同步异常的IPA
    uint8_t fsc = xabt.fsc & ~FSC_LL_MASK;//获取FSC,低两位清空了。
    bool is_data = (hsr.ec == HSR_EC_DATA_ABORT_LOWER_EL);
    mmio_info_t info;
    enum io_state state;

    /*
     * If this bit has been set, it means that this stage-2 abort is caused
     * by a guest external abort. We treat this stage-2 abort as guest SError.
     */
    if ( xabt.eat )
        return __do_trap_serror(regs, true);

    gva = get_hfar(is_data);//FAR_EL2寄存器中读取触发同步异常的VA

    if ( hpfar_is_valid(xabt.s1ptw, fsc) )
        gpa = get_faulting_ipa(gva);//HPFAR_EL2寄存器中读取触发同步异常的IPA
    else
    {
        /*
         * Flush the TLB to make sure the DTLB is clear before
         * doing GVA->IPA translation. If we got here because of
         * an entry only present in the ITLB, this translation may
         * still be inaccurate.
         */
        if ( !is_data )
            flush_guest_tlb_local();

        rc = gva_to_ipa(gva, &gpa, GV2M_READ);
        /*
         * We may not be able to translate because someone is
         * playing with the Stage-2 page table of the domain.
         * Return to the guest.
         */
        if ( rc == -EFAULT )
            return; /* Try again */
    }
.....
 	switch ( fsc )
 		case FSC_FLT_TRANS:
    {
    	info.gpa = gpa;
    	info.dabt = hsr.dabt;//参考hsr_dabt结构体,ISS的DABT的标志位。
     	state = try_handle_mmio(regs, &info);//cpu_user_regs,IPA和dabt做参数
......
}

do_trap_stage2_abort_guest通过读取ESR_EL2的ISS,确认引起Data Abort from a lower Exception level的同步异常的具体原因。然后根据具体异常在调用对应的处理函数。通过FAR_EL2寄存器中读取触发同步异常的VA,通过HPFAR_EL2寄存器中读取触发同步异常的IPA,传递给try_handle_mmio函数。

#define FSC_FLT_TRANS  (0x04)

   /* Contain the common bits between DABT and IABT */
    struct hsr_xabt {
        unsigned long fsc:6;    /* Fault status code */
        unsigned long pad1:1;   /* Not common */
        unsigned long s1ptw:1;  /* Stage 2 fault during stage 1 translation */
        unsigned long pad2:1;   /* Not common */
        unsigned long eat:1;    /* External abort type */
        unsigned long fnv:1;    /* FAR not Valid */
        unsigned long pad3:14;  /* Not common */
        unsigned long len:1;    /* Instruction length */
        unsigned long ec:6;     /* Exception Class */
    } xabt;
    
       struct hsr_dabt {
        unsigned long dfsc:6;  /* Data Fault Status Code */
        unsigned long write:1; /* Write / not Read */
        unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */
        unsigned long cache:1; /* Cache Maintenance */
        unsigned long eat:1;   /* External Abort Type */
        unsigned long fnv:1;   /* FAR not Valid */
#ifdef CONFIG_ARM_32
        unsigned long sbzp0:5;
#else
        unsigned long sbzp0:3;
        unsigned long ar:1;    /* Acquire Release */
        unsigned long sf:1;    /* Sixty Four bit register */
#endif
        unsigned long reg:5;   /* Register */
        unsigned long sign:1;  /* Sign extend */
        unsigned long size:2;  /* Access Size */
        unsigned long valid:1; /* Syndrome Valid */
        unsigned long len:1;   /* Instruction length */
        unsigned long ec:6;    /* Exception Class */
    } dabt; /* HSR_EC_DATA_ABORT_* */

Data Abort 的ISS标志位。xen代码中用hsr_dabt结构体表示。
在这里插入图片描述
其中bit位描述如下:
ISV:表示ISS包含的指令异常的解析是有效的。
SAS:异常发升的时候访问内存的大小。
SSE:数据是否需要符号扩展。
SRT:目标寄存器Rt。
SF:是32位还是64位load/store指令.
AR:指令是否包含acquire/release屏障语义
VNCR:是否来自嵌套虚拟化的访问。
SET:同步异常类型。
FnV:FAR寄存器是否有效。
EA:外部中止类型。
CM:Cache maintenance产生的数据异常。
S1PTW:对于stage 2 映射,指示错误发生在stage 1阶段还是stage 2阶段。
WnR:读或者写操作除法的异常。
DFSC:数据故障状态码
对于DFSC产生的具体原因。在获取FSC,低两位清空了,所以FSC_FLT_TRANS (0x04)对应Translation fault, level 0-level 3.
在这里插入图片描述

try_handle_mmio的实现如下:

enum io_state try_handle_mmio(struct cpu_user_regs *regs,
                              mmio_info_t *info)
{
    struct vcpu *v = current;
    const struct mmio_handler *handler = NULL;
    int rc;

    ASSERT(info->dabt.ec == HSR_EC_DATA_ABORT_LOWER_EL);

    if ( !(info->dabt.valid || (info->dabt_instr.state == INSTR_CACHE)) )
    {
        ASSERT_UNREACHABLE();
        return IO_ABORT;
    }

    handler = find_mmio_handler(v->domain, info->gpa);//根据gpa找到domain的vmmio上注册的handler,参考register_mmio_handler函数。
    if ( !handler )
    {
        rc = try_fwd_ioserv(regs, v, info);
        if ( rc == IO_HANDLED )
            return handle_ioserv(regs, v);

        return rc;
    }

    /*
     * When the data abort is caused due to cache maintenance and the address
     * belongs to an emulated region, Xen should ignore this instruction.
     */
    if ( info->dabt_instr.state == INSTR_CACHE )
        return IO_HANDLED;

    /*
     * At this point, we know that the instruction is either valid or has been
     * decoded successfully. Thus, Xen should be allowed to execute the
     * instruction on the emulated MMIO region.
     */
    if ( info->dabt.write ) //写操作还是读操作?
        return handle_write(handler, v, info);
    else
        return handle_read(handler, v, info);
}

find_mmio_handler根据gpa找到domain的vmmio上注册的handler,根据操作的类型调用handle_write和handle_read函数。
最后handle_read和handle_write会调用handler的ops来时先读写。参数vcpu,mmio_info_t和handler->priv会传递给ops。

static enum io_state handle_read(const struct mmio_handler *handler,
                                 struct vcpu *v,
                                 mmio_info_t *info)
{
    const struct hsr_dabt dabt = info->dabt;
    struct cpu_user_regs *regs = guest_cpu_user_regs();//获取压栈的VCPU寄存器的cpu_user_regs
    /*
     * Initialize to zero to avoid leaking data if there is an
     * implementation error in the emulation (such as not correctly
     * setting r).
     */
    register_t r = 0;

    if ( !handler->ops->read(v, info, &r, handler->priv) )//调用注册handler中的read函数,读取的值存到变量r中
        return IO_ABORT;

    r = sign_extend(dabt, r);
//获取ESR_EL2的ISS的SRT:目标寄存器Rt比特位,即hsr_dabt结构体的reg。最为写回的寄存器。
//由于当前进入xen的异常处理,guest os的VCPU寄存器的保存在cpu_user_regs,修改cpu_user_regs中对应的寄存器,返回guest os就会生效。
    set_user_reg(regs, dabt.reg, r);

    return IO_HANDLED;
}

static enum io_state handle_write(const struct mmio_handler *handler,
                                  struct vcpu *v,
                                  mmio_info_t *info)
{
    const struct hsr_dabt dabt = info->dabt;
    struct cpu_user_regs *regs = guest_cpu_user_regs();
    int ret;
//调用注册handler中的write函数,将hsr_dabt结构体的reg写入到对应的PA
    ret = handler->ops->write(v, info, get_user_reg(regs, dabt.reg),
                              handler->priv);
    return ret ? IO_HANDLED : IO_ABORT;
}

参考资料
learn_the_architecture_-_aarch64_virtualization_102142_0100_03_en.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值