virtio-pci

软件环境:qemu,linux kernel
硬件环境:x86 PC
目标:在host ubuntu20.04上通过qemu运行linux虚拟机

0. 目标

    看virtio代码时,会发现virtio_pci驱动,virtio总线,pci总线,pci设备,virtio驱动,virtio设备,它们到底是一个什么样的关系,
这篇文章尝试理清。

1. VIRTIO框架

  • 从前后端代码角度来看,virtio框架如下
    在这里插入图片描述

     1. 除了前端驱动(在客户机操作系统中实现)和后端驱动(在虚拟机 hypervisor 中实现)之外,virtio 还定义了2个层次来支持客户机到 hypervisor 的通信。
     2. 在顶层(称为 virtio 层)是虚拟队列接口,它在概念上将前端驱动程序附加到后端驱动,驱动可以根据需要使用零个或多个队列。例如,virtio 网络驱动使用两个虚拟队列(一个用于接收,一个用于发送),而 virtio 块驱动只使用一个队列。虚拟队列是虚拟的,它通过 ring 实现,以用于遍历客户机到 hypervisor 的信息。这可以以任何方式来实现,只要 Guest 和 hypervisor 通过相同的方式来实现。
     3. 列出了五个前端驱动程序,分别用于块设备(例如磁盘)、网络设备、PCI 模拟、balloon 驱动(用于动态管理客户机内存使用)和控制台驱动。每个前端驱动在 hypervisor 中都有一个对应的后端驱动。
    
  • 从驱动与设备匹配角度来看,Virtio框架如下

VIRTIO驱动与设备匹配

1.1 virtio_pci

1. virtio_pci分为modern和legacy,当前用modern
2. virtio_pci是一种pci_driver,模块初始化为virtio_pci_driver_init,其向内核注册pci driver,与pci dev配对后进行virtio_pci_probe
  • virtio_pci_probe

    virtio_pci_driver_init -> ... -> virtio_pci_probe -> virtio_pci_modern_probe
    1.创建virtio_pci设备
    2.virtio_pci_modern_probe 填充virtio_pci设备结构体
    3.register_virtio_device注册virtio_pci设备,后续virtio_driver就可以probe了
    
  • virtio_pci_modern_probe

    1.设置pci设备中的device vendor id到virtio_device,用于register_virtio_device注册设备
    2.映射common isr notify_base device区
    3.注册virtio_pci_config_ops, setup_vq,del_vq
    
  • virtio_pci_config_ops

    实现virtio_config_ops定义的回调,由virtblk驱动调用
    
  • IO region

    Virtio规定IO区域:Common, ISR, Device, Notify
    virtio_pci_common_cfg
    映射PCI设备的各个IO配置空间,在virtio_pci_modern中对其映射
    

1.2 IO Region

1.2.1 设备类型

基于PCI总线发现,virtio设备有着专属的Vendor ID(0x1AF4)和特定的Device ID区间(0x1000~0x107F)其中0x1000~0x103F用于传统模式设备,0x1040~0x107F用于现代模式设备。通过Vendor ID可以识别出virtio pci设备,而Device ID则可以指示该virtio pci设备支持的virtio设备类型。
如modern模式中,DEVICE_ID采用0x1000/0x1001+0x40,分别表示net和block设备
其实device id沿用pci_dev->subsystem_device,与legacy一致
如device = 1代表virtio-net设备,vendor = 6900(0x1af4)代表virtio设备
(gdb) p vp_dev->vdev.id
$6 = {device = 1, vendor = 6900}

1.2.2 Modern Virtio-pci设备配置空间

现代virtio-pci设备通过标准的PCI配置空间中的能力列表(capability list),可以指定配置信息的存储位置(使用哪个BAR,从BAR空间开始的偏移地址等)。1.0规范中定义了4种配置信息:通用配置、中断配置、通知配置、设备专属配置。每种配置信息都对应一个capabiility,如果设备不需要某种类型的配置信息,则不提供相应的capability即可。配置信息分别是:
1)common config,通用的配置信息,包括队列的初始化、feature bit、status,对应legacy模式下的common config,必须支持;
2)isr config,中断配置信息;
3)notify config,也是中断相关的配置信息,isr和notify至少需要支持一种;
4)device config,设备自有的配置信息,对应virtio0.95中提到的net/block设备配置信息,有些设备不需要单独的配置信息,则不提供相应的capability;
//qemu相关的实现代码,注册四种配置信息的mem_region并创建相应的capability
virtio_pci_modern_mem_region_map(proxy, &proxy->common, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->isr, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->device, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->notify, &notify.cap);

在这里插入图片描述

1.2.3 Bars

从modern_bars(0)读取是否支持common,isr,notify
/* check for a common config: if not, use legacy mode (bar 0). */
common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
                    IORESOURCE_IO | IORESOURCE_MEM,
                    &vp_dev->modern_bars);

/* If common is there, these should be too... */
isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,
                 IORESOURCE_IO | IORESOURCE_MEM,
                 &vp_dev->modern_bars);
notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,
                    IORESOURCE_IO | IORESOURCE_MEM,
                    &vp_dev->modern_bars);

1.2.4 Virtio common

Qemu中common结构体
/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
    /* About the whole device. */
    uint32_t device_feature_select; /* read-write */
    uint32_t device_feature;        /* read-only */
    uint32_t guest_feature_select;  /* read-write */
    uint32_t guest_feature;     /* read-write */
    uint16_t msix_config;       /* read-write */
    uint16_t num_queues;        /* read-only */
    uint8_t device_status;      /* read-write */
    uint8_t config_generation;      /* read-only */

    /* About a specific virtqueue. */
    uint16_t queue_select;      /* read-write */
    uint16_t queue_size;        /* read-write, power of 2. */
    uint16_t queue_msix_vector; /* read-write */
    uint16_t queue_enable;      /* read-write */
    uint16_t queue_notify_off;  /* read-only */
    uint32_t queue_desc_lo;     /* read-write */
    uint32_t queue_desc_hi;     /* read-write */
    uint32_t queue_avail_lo;        /* read-write */
    uint32_t queue_avail_hi;        /* read-write */
    uint32_t queue_used_lo;     /* read-write */
    uint32_t queue_used_hi;     /* read-write */
};
Virtio_pci_modern_probe映射common后成员

在这里插入图片描述

在setup_vq中会对common进行读写设置

1.2.5 Isr

在Virtio_pci_modern_probe映射isr成员
setup_vq中将其计算赋值给vq->priv

1.2.6 Notify_base

pci_read_config_dword从pci config space读notify_off_multiplier,notify_length,notify_offset
在Virtio_pci_modern_probe映射notify_base成员
setup_vq将notify_base映射对应的vq
vq->priv = (void __force *)vp_dev->notify_base +
            off * vp_dev->notify_offset_multiplier;

1.2.7 Device

在Virtio_pci_modern_probe映射device成员
vp_get从device根据offset读取
vp_set向device的offset设置

1.3 virtio_mmio

qemu_system_aarch64 启动的vm看起来有virtio_mmio节点,实际上没有走virtio_mmio,实际上走的virtio_pci_common
  • virtio_mmio_driver

    virtio_mmio是一种platform_driver,向platform_driver_register
    
static struct platform_driver virtio_mmio_driver = {
      .probe = virtio_mmio_probe,
      .remove = virtio_mmio_remove,
      .driver = {
          .name = "virtio-mmio",
         .of_match_table = virtio_mmio_match, //”virtio,mmio”
        .acpi_match_table = ACPI_PTR(virtio_mmio_acpi_match),
      },
}; 
  • virtio_mmio_probe

    register_virtio_device  //将子设备注册到virtio总线
    
  • virtio_mmio_config_ops

1.4 virtio

  • bus_register

    Virtio 注册virtio-bus(drivers/virtio/virtio.c)
    
static struct bus_type virtio_bus = {
    .name = "virtio",
    .match = virtio_dev_match, //该总线dev和dri匹配规则
    .dev_groups = virtio_dev_groups,
    .uevent = virtio_uevent,
    .probe = virtio_dev_probe, //match成功后内核走总线probe,dri->probe
    .remove = virtio_dev_remove,
}; 
  • register_virtio_driver

    定义register_virtio_driver,用于virtio设备注册,相比于普通drive_register,仅都一个driver.bus成员
    

virtio_bus

1.5 virtio_ring

   定义了virtio_queue的一系列操作

1.6 virtio_blk

此处以virtio_blk为例,对于virtio其他驱动来说也大致相似
  • virtio_blk driver

    register_virtio_driver(&virtio_blk) //首先virtio_blk是一个virtio_driver,由virtio.h定义
    
static struct virtio_driver virtio_blk = {
    .feature_table          = features,
    .feature_table_size     = ARRAY_SIZE(features),
    .feature_table_legacy       = features_legacy,
    .feature_table_size_legacy  = ARRAY_SIZE(features_legacy),
    .driver.name            = KBUILD_MODNAME,
    .driver.owner           = THIS_MODULE,
    .id_table           = id_table,
    .probe              = virtblk_probe,
    .remove             = virtblk_remove,
    .config_changed         = virtblk_config_changed,
#ifdef CONFIG_PM_SLEEP
    .freeze             = virtblk_freeze,
    .restore            = virtblk_restore,
#endif
};
  • virtblk_probe
    在这里插入图片描述

    1.Virtio_blk初始化init调用Register_virtio_driver,走driver的匹配流程,成功先走virtio_bus的probe(virtio_dev_probe),然后走blk驱动的probe(virtblk_probe)virtio_dev_probe -> virtblk_probe
    2.virtio_find_vqs 实现有3种:virtio_pci_modern,virtio_mmio,virtio_pci_legacy
    
  • virtio_mq_ops

    块设备queue操作
    
static const struct blk_mq_ops virtio_mq_ops = {
    .queue_rq   = virtio_queue_rq,
    .commit_rqs = virtio_commit_rqs,
    .complete   = virtblk_request_done,
    .init_request   = virtblk_init_request,
    .map_queues = virtblk_map_queues,
};
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值