uvmm中设备管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gaojy19881225/article/details/82053154

1.设备分类

Uvmm中管理的设备分为两类:透传设备和虚拟设备(模拟设备)。在虚拟机中通过Device_repository类管理,其结构类图如下:

2.设备获取

所有的设备(包括虚拟和透传设备)通过解析dtb文件得到,然后加入到Device_repository类的_devices成员变量中进行保存。_devices成员是Dt_device结构类型,其包含了三个成员,path:node的全路径;phandle:node的phandle;dev:Device引用。

3.设备挂载

透传设备在io server模块中挂载在vbus总线上;虚拟机在create_default_devices()时会在本地保留一份vbus设备信息在Devinfo类型的_devices成员变量中(详见vbus机制分析)。Virt_bus类结构图如下:

Devinfo结构也包含三个成员,io_dev:从io server获取的物理设备;dev_info,物理设备信息;proxy:物理设备代理(最终会跟Dt_device结构的dev设备关联,见下面透传设备初始化分析)。

虚拟设备没有挂载在任何总线上,而是通过静态变量方式保存在Device_type类型的列表types中。Device_type类图如下:

目前,arm架构的uvmm中预定义了如下的虚拟设备:

static Vdev::Device_type t5 = { "arm,gic-v3",nullptr, &f_gicv3 };

static Vdev::Device_type t1 = { "arm,cortex-a9-gic", nullptr, &f };

static Vdev::Device_type t2 = { "arm,cortex-a15-gic", nullptr, &f };

static Vdev::Device_type t3 = { "arm,cortex-a7-gic", nullptr, &f };

static Vdev::Device_type t4 = { "arm,gic-400", nullptr, &f };

static Vdev::Device_type tt1 = { "arm,armv7-timer", nullptr, &ftimer };

static Vdev::Device_type tt2 = { "arm,armv8-timer", nullptr, &ftimer };

static Vdev::Device_type t = { "syscon-l4vmm", nullptr, &f };

static Device_type t = { "l4vmm,l4-mmio", nullptr, &f };

static Device_type t = { "virtio,mmio", "console", &f };

static Device_type t = { "virtio-dev,mmio", nullptr, &f };

static Device_type t = { "virtio,mmio", "proxy", &f };

 

4.设备创建

设备初始化通过解析dtb文件完成,其中包括对透传设备和虚拟设备的初始化。虚拟机在遍历解析dtb节点过程中,发现是设备节点,则调用Vdev::Factory::create_dev方法来创建设备。其流程是:

首先尝试创建虚拟设备;如果创建失败,则尝试创建透传设备;如果仍然失败,则将节点status属性置为disabled。

4.1虚拟设备创建:

  1. 获取"compatible"属性值的个数count
  2. 获取虚拟设备类型l4type
  3. 依次取出"compatible"属性值,结合虚拟设备类型,在支持的虚拟设备列表(静态变量方式保存的Device_type类型的列表types)中查找设备
  4. 如果找到匹配设备,则调用其create方法创建设备
  5. 添加到Device_repository类的_devices成员变量中【注1】

 static cxx::Ref_ptr<Device> create_vdev(Device_lookup const *devs,

                                          Dt_node const &node)

  {

    char const *const comp = "compatible";

    int count = node.stringlist_count(comp);   //1

    if (count <= 0)

      return nullptr;

 

    int l4type_len;

    char const *l4type = node.get_prop<char>("l4vmm,vdev", &l4type_len);  //2

 

    for (int i = 0; i < count; ++i)

      {

        int cid_len;

        char const *cid = node.stringlist_get(comp, i, &cid_len);

        auto const *t = Device_type::find(cid, cid_len, l4type, l4type_len);   //3

        if (t)

          return t->f->create(devs, node);  //4

      }

 

    return nullptr;

  }

 

【注1】上面第5步在虚拟设备和透传设备的通用流程里,创建设备成功后,会将设备添加到Device_repository类的_devices成员变量中,代码如下:

if (dev)

    {

      vm_instance.add_device(node, dev);  //5

      return true;

    }

 

4.2透传设备创建:

虚拟设备创建失败后,如果透传属性(pass_thru)被设置,会尝试创建透传设备。

if (pass_thru)

      return pass_thru->create(devs, node);

静态成员变量pass_thru在类Factory的子类F的构造函数中初始化:

F() { pass_thru = this; }

而在Vdev域中静态实例化了F:

static F f;

所以透传设备的初始化会调用类F的create方法进行设备创建。下面具体分析透传设备的create方法。

  1. 根据设备树node信息从本地保存的vbus设备信息中查找可用且匹配的设备——Devinfo类型的vd【注2】
  2. 根据vd的物理设备(io_dev)创建代理设备(Io_proxy),并赋值给vd的proxy成员。
  3. 遍历vd的硬件资源,注册其中的reg类(寄存器)资源到Guest中。
  4. 如果存在"smmu-id"属性,则还需做iommu相关处理。
  5. 将此proxy 设备返回,添加到Device_repository类的_devices成员变量中【注3】

cxx::Ref_ptr<Device> create(Device_lookup const *devs,

                              Dt_node const &node) override

  {

    ……

    auto *vbus = devs->vbus().get();

    auto *vd = vbus->find_unassigned_dev(node);    //1

    if (!vd)

      return nullptr;

 

    auto proxy = make_device<Io_proxy>(vd->io_dev);  //2

    vd->proxy = proxy;                            //2

 

    int name_len;

    char const *bus_name = node.get_prop<char>("l4vmm,vbus", &name_len);

    if (bus_name && !strcmp(bus_name, "pci")) {

      ……  //pci总线目前只有x86架构才有,略过

    }

    else{

      for (unsigned i = 0; i < vd->dev_info.num_resources; ++i){

        ……

        devs->vmm()->register_mmio_device(handler, node, id);  //3

      }

 

      int prop_sz;

      auto prop_start = node.get_prop<fdt32_t>("smmu-id", &prop_sz);

      if(prop_start)

        {

          auto src_id = node.get_prop_val(prop_start, prop_sz, 0);

          devs->ram()->bind_kern_iommu(src_id);  //4

        }

    }

    return proxy;  //5

  }

【注2】匹配设备是指"compatible"属性兼容,且"reg"属性的起始地址相同。详见find_unassigned_dev函数实现。

【注3】参照【注1】

5.设备初始化

在设备创建之后通过Vm::init_devices()来初始化所有创建的设备。以透传设备Io_proxy::init_device()为例,其逻辑在于初始化设备的中断:包括依据设备"interrupt-parent"属性,查找设备的Irq控制器对象(即Gic::Dist实例对象);之后通过bind_irq()绑定设备的Irq到内核Irq_sender对象(具体流程参考中断虚拟化)。

6.设备寄存器资源管理

设备irq资源在init_devices()中会进行初始化,并绑定到内核对象。而reg(寄存器)资源则在设备create的时候就进行了处理:

devs->vmm()->register_mmio_device(handler, node, id);

UVMM设计了Mmio_device类来管理设备的寄存器空间。Mmio_device相关类图如下:

Uvmm的虚拟机实例Vmm::Vm vm_instance成员变量_vmm是Vmm::Guest *类型,Guest继承自Generic_guest,Generic_guest的一个成员变量Vm_mem _memmap就是一个设备与region的map对象:

typedef std::map<Region, cxx::Ref_ptr<Vmm::Mmio_device>> Vm_mem;

reg资源处理在设备create时,首先创建了一个Ds_handler对象,然后调用虚拟机Guest对象的register_mmio_device方法,将设备和region的对应关系添加到了map对象_memmap中。当虚拟机第一次访问设备寄存器空间时,虚拟机由于缺页异常而陷出到内核并进一步转发到UVMM来处理,进而由UVMM来模拟设备寄存器的访问。Ds_handler类实现了access()方法,将虚拟机试图访问的IPA地址映射成寄存器的物理地址,并通知虚拟机重新尝试访问设备寄存器。当虚拟机重新访问设备寄存器时,由于MMU已经映射好了虚拟机IPA到PA的地址映射,因此虚拟机能够在MMU的Stage2翻译的支持下,直接访问设备的寄存器空间。更详细的过程请参考缺页异常的处理流程。

 

猜你喜欢

转载自blog.csdn.net/gaojy19881225/article/details/82053154