uv设备
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虚拟设备创建:
- 获取"compatible"属性值的个数count
- 获取虚拟设备类型l4type
- 依次取出"compatible"属性值,结合虚拟设备类型,在支持的虚拟设备列表(静态变量方式保存的Device_type类型的列表types)中查找设备
- 如果找到匹配设备,则调用其create方法创建设备
- 添加到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方法。
- 根据设备树node信息从本地保存的vbus设备信息中查找可用且匹配的设备——Devinfo类型的vd【注2】
- 根据vd的物理设备(io_dev)创建代理设备(Io_proxy),并赋值给vd的proxy成员。
- 遍历vd的硬件资源,注册其中的reg类(寄存器)资源到Guest中。
- 如果存在"smmu-id"属性,则还需做iommu相关处理。
- 将此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翻译的支持下,直接访问设备的寄存器空间。更详细的过程请参考缺页异常的处理流程。