searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

dpdk-vdpa对virtio寄存器的初始化和读写分析

2023-08-16 03:40:17
108
0

一、vdpa中virtio寄存器读写的初始化

ifcvf_init_hw
    // 读取pci配置空间中cap起始指针的偏移,大小为一个字节的内容,即首个capability的位置
    // 1. PCI_CAPABILITY_LIST为0x34,实际读取的时候,会将这个0x34(基于pci config空间的偏移),
    //    加上vfio config region基于device fd的偏移,从device fd读取该偏移位置的内容.
    // 2. pos返回的内容,就是在上面计算的偏移的位置读取的内容。与内核态驱动读取这个pos的位置的内容,应该是相同的。
    //    用户态这里的读取经过了vfio的转换。得到的都是基于pci配置空间开始的偏移。
    // 3. 后面迭代读取各个capability的时候,使用的pos(cap.cap_next)都会经过上面1中偏移的计算,然后去读取pci配置空间的cap内容。
    PCI_READ_CONFIG_BYTE(dev, &pos, PCI_CAPABILITY_LIST);    
        rte_pci_read_config(dev, val, 1, where)    
            pci_vfio_read_config(device, buf, len, offset);
                // 这里返回的是device fd
                fd = rte_intr_dev_fd_get(dev->intr_handle);    
                // VFIO_PCI_CONFIG_REGION_INDEX为7,表示第8个region,固定为pci配置空间
                // 1. 初始化的时候会通过系统调用:
                //     ioctl(vfio_dev_fd, VFIO_DEVICE_GET_REGION_INFO, ri)    
                //     来读取各个region的信息初始化pdev->region[]. 其中ri->index会先初始化为要读取的region的index
                // 2. 这里直接返回初始化过的该region的size和该region从device fd开始的偏移
                pci_vfio_get_region(dev, VFIO_PCI_CONFIG_REGION_INDEX, &size, &offset)    
                    *size   = pdev->region[index].size;
                    *offset = pdev->region[index].offset;
                // 从device fd开始指定偏移,读取len大小的数据到buf。
                // 1. 这里buf结构是struct ifcvf_pci_cap,即virtio cap的结构。
                // 2. 指定偏移,包括region基于device fd的偏移offset,和基于pci配置空间开始位置的偏移offs
                pread64(fd, buf, len, offset + offs)
    // 迭代读取pci配置空间中的各个cap内容,根据cap的offset返回virtio各个cap在相应bar偏移后的地址。
    // 1. 根据cap.cfg_type筛选virtio特定的cap,得到对应cap的bar和bar中的offset
    // 2. 基于已经初始化的bar的起始地址,即hw->mem_resource[bar].addr,再根据1中得到的offset偏移,得到该cap在bar中的偏移地址。
    // 3. 后面读取virtio各个capability中的寄存器的时候,直接基于这里初始化的比如hw->common_cfg记录的起始地址,
    //    再根据寄存器在bar中的偏移,去读取指定寄存器。下面的lm_cfg和mq_cfg也是如此。
    // 4. hw->common_cfg等cap指针变量,起始就是对应cap的数据结构,里面包含了各个寄存器,读写各个寄存器的时候直接变量引用即可。
    //    cap的结构指针在下面。
    while (pos) {       
        PCI_READ_CONFIG_RANGE(dev, (u32 *)&cap, sizeof(cap), pos);
            // 这个函数跟上面首次读取cap指针使用的函数是一样的。都是基于device fd开始,
            //     vfio config region偏移加上这里指定的where偏移,当做偏移总和,读取指定size大小的内容到val
            rte_pci_read_config(dev, val, size, where);
    
        switch (cap.cfg_type) {
            case IFCVF_PCI_CAP_COMMON_CFG:
                hw->common_cfg = get_cap_addr(hw, &cap);
                    u8 bar = cap->bar;
                    u32 offset = cap->offset;
                    return hw->mem_resource[bar].addr + offset;
            case IFCVF_PCI_CAP_NOTIFY_CFG:
            case IFCVF_PCI_CAP_ISR_CFG:    
            case IFCVF_PCI_CAP_DEVICE_CFG:   
        }
        pos = cap.cap_next;
    }          
    // 获取热迁移相关的cap在bar中的偏移地址。
    // 1. 这两个cap没有通过pci配置空间中的迭代cap查找,而是直接写死的bar index和offset。
    hw->lm_cfg = hw->mem_resource[4].addr;
    hw->mq_cfg = hw->mem_resource[4].addr + IFCVF_MQ_OFFSET;
    
    // 至此,virtio寄存器读取需要的初始化完成。vdpa中这部分是在vdpa启动,调用ifcvf_probe初始化virtio-pci设备层的时候执行。

下面是涉及到的数据结构

struct ifcvf_hw {
        u64    req_features;
        u8     notify_region;
        u32    notify_off_multiplier;
        struct ifcvf_pci_common_cfg *common_cfg;
        union {
                struct ifcvf_net_config *net_cfg;
                struct virtio_blk_config *blk_cfg;
                void *dev_cfg;
        };
        u8     *isr;
        u16    *notify_base;
        u16    *notify_addr[IFCVF_MAX_QUEUES * 2];
        u8     *lm_cfg;
        u8     *mq_cfg;
        struct vring_info vring[IFCVF_MAX_QUEUES * 2];
        u8 nr_vring;
        int device_type;
        struct ifcvf_pci_mem_resource mem_resource[IFCVF_PCI_MAX_RESOURCE];
};

struct ifcvf_pci_common_cfg {
        /* About the whole device. */
        u32 device_feature_select;
        u32 device_feature;
        u32 guest_feature_select;
        u32 guest_feature;
        u16 msix_config;
        u16 num_queues;
        u8 device_status;
        u8 config_generation;
        
        /* About a specific virtqueue. */
        u16 queue_select;
        u16 queue_size;
        u16 queue_msix_vector;
        u16 queue_enable;
        u16 queue_notify_off;
        u32 queue_desc_lo;
        u32 queue_desc_hi;
        u32 queue_avail_lo; 
        u32 queue_avail_hi;
        u32 queue_used_lo;
        u32 queue_used_hi;
};

 

二、vdpa对virtio寄存器的读写操作

update_datapath         
    vdpa_ifcvf_start           
        ifcvf_start_hw(&internal->hw); 
            // 1. reset、IFCVF_CONFIG_STATUS_ACK、IFCVF_CONFIG_STATUS_DRIVER、IFCVF_CONFIG_STATUS_FEATURES_OK、IFCVF_CONFIG_STATUS_DRIVER_OK,
            //    均是通过直接写hw->common_cfg->device_status变量,该变量映射的硬件设备该寄存器。
            // 2. feature和其他commoncfg里的寄存器也是通过相同的方式,直接写的变量。
            // 3. 对于mq_cfg则是直接以变量u8的方式直接读写它映射的地址。
            //    根据num queue,设置给mq_cfg。如果是blk,则直接写入num queue,如果是net则写入queue pair数
            // 4. 对于cfg->queue_desc_lo、cfg->queue_size、lm_cfg的last_avail_idx和last_used_idx、cfg->queue_enable等等,
            //    则是先从vhost_virtqueue中获取到,再设置给硬件寄存器。
            // 5. 对于每个队列的queue_msix_vector,则是从1写入queue0, 2写入queue1依次类推,不区分blk和net。最后一个ctrlq的queue_msix_vector应该是没有写。
            //    内核态驱动中,会将这里填入queue_msix_vector的值跟实际设备的中断号关联起来。
            // 6. 对于每个队列的cfg->queue_notify_off,则是读取到notify_off中,然后进行下面计算得到当前队列的notify地址:
            //    hw->notify_addr[i] = (void *)((u8 *)hw->notify_base + notify_off * hw->notify_off_multiplier);
            // 
            //    hw->notify_off_multiplier是从notify的pci配置空间的capability中读取出来的,如下:
            //    rte_pci_read_config(dev, &hw->notify_off_multiplier, 4, pos + sizeof(cap));
            //    即,从notify这个cap的起始位置pos,往后偏移一个cap结构的偏移,读取4个字节内容赋值给hw->notify_off_multiplier
            //
            //    hw->notify_base是根据notify cap在pci配置空间中指示的bar和offset,从对应映射的bar内存地址加偏移得到的地址,即:
            //    hw->notify_base = get_cfg_addr(dev, &cap);
            ifcvf_reset(hw);
                ifcvf_set_status(hw, 0);
                    IFCVF_WRITE_REG8(status, &hw->common_cfg->device_status);
                        rte_write8((val), (reg))
                            rte_write8_relaxed(value, addr);
                                *(volatile uint8_t *)addr = value;
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_ACK);
                status |= ifcvf_get_status(hw);
                ifcvf_set_status(hw, status);
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_DRIVER);  
            ifcvf_config_features(hw)
                host_features = ifcvf_get_features(hw);    // 获取hw的feature
                hw->req_features &= host_features;         // 跟当前软件的feature取交集
                ifcvf_set_features(hw, hw->req_features);    // 设置给hw的feature
                    struct ifcvf_pci_common_cfg *cfg = hw->common_cfg;
                    IFCVF_WRITE_REG32(0, &cfg->guest_feature_select);
                    IFCVF_WRITE_REG32(features & ((1ULL << 32) - 1), &cfg->guest_feature);
            
                    IFCVF_WRITE_REG32(1, &cfg->guest_feature_select);
                    IFCVF_WRITE_REG32(features >> 32, &cfg->guest_feature);
                ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_FEATURES_OK);
                
            ifcvf_hw_enable(hw)
                IFCVF_WRITE_REG16(0, &cfg->msix_config);
                cfg = hw->common_cfg;
                lm_cfg = hw->lm_cfg;
                ifcvf_enable_mq(hw); 
                    // 根据num queue,设置给mq_cfg。如果是blk,则直接写入num queue,如果是net则写入queue pair数
                    mq_cfg = hw->mq_cfg;
                    *(u32 *)mq_cfg = nr_queue;
                    *(u32 *)mq_cfg = nr_queue / 2;
                for (i = 0; i < hw->nr_vring; i++) {
                    IFCVF_WRITE_REG16(i, &cfg->queue_select);
                    io_write64_twopart(hw->vring[i].desc, &cfg->queue_desc_lo, &cfg->queue_desc_hi);  
                    io_write64_twopart(hw->vring[i].avail, &cfg->queue_avail_lo, &cfg->queue_avail_hi); 
                    io_write64_twopart(hw->vring[i].used, &cfg->queue_used_lo,  &cfg->queue_used_hi);
                    IFCVF_WRITE_REG16(hw->vring[i].size, &cfg->queue_size);
                    if (lm_cfg) {
                        // 对于blk和net,分别将后端的avail 和used idx写到lm_cfg中。
                        // 对于blk,每个vq的两个idx,独占一个IFCVF_LM_CFG_SIZE
                        // 对于net,rx和tx一对vq,共享一个IFCVF_LM_CFG_SIZE,rxvq占前4个字节,txvq占靠后4个字节
                        *(u32 *)(lm_cfg + IFCVF_LM_RING_STATE_OFFSET + i * IFCVF_LM_CFG_SIZE) = 
                                                (u32)hw->vring[i].last_avail_idx |  
                                                ((u32)hw->vring[i].last_used_idx << 16);
                        *(u32 *)(lm_cfg + IFCVF_LM_RING_STATE_OFFSET + (i / 2) * IFCVF_LM_CFG_SIZE + 
                                                (i % 2) * 4) =   
                                                (u32)hw->vring[i].last_avail_idx |
                                                ((u32)hw->vring[i].last_used_idx << 16)
                    IFCVF_WRITE_REG16(i + 1, &cfg->queue_msix_vector);  
                    notify_off = IFCVF_READ_REG16(&cfg->queue_notify_off);
                    hw->notify_addr[i] = (void *)((u8 *)hw->notify_base + notify_off * hw->notify_off_multiplier);
                    IFCVF_WRITE_REG16(1, &cfg->queue_enable);                                                                                                                     
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_DRIVER_OK)    
    // 将每个队列的notify对应的eventfd,即kickfd,添加到poll轮询中,等待qemu端触发notify。可读的时候,表示收到了notify,
    // 然后根据触发notify的队列,得到硬件设备的notify地址,并将队列index写入该地址,通知硬件设备。                         
    ret = setup_notify_relay(internal);
                                                                                                                                         
        rte_ctrl_thread_create(&internal->tid, name, NULL, notify_relay,
        // 创建线程notify_relay,里面poll监听所有kickfd的notify事件,
        // 如果有,则提取对应的qid,并写一下该queue的notify,通知给硬件
            // 将每个vq的kickfd添加到
            q_num = rte_vhost_get_vring_num(internal->vid);
            for (qid = 0; qid < q_num; qid++) {
                ev.events = EPOLLIN | EPOLLPRI;
                rte_vhost_get_vhost_vring(internal->vid, qid, &vring);
                ev.data.u64 = qid | (uint64_t)vring.kickfd << 32;
                epoll_ctl(epfd, EPOLL_CTL_ADD, vring.kickfd, &ev)
            }
            for (;;) {
                nfds = epoll_wait(epfd, events, q_num, -1);  
                for (i = 0; i < nfds; i++) {
                    qid = events[i].data.u32;
                    kickfd = (uint32_t)(events[i].data.u64 >> 32);
                    nbytes = read(kickfd, &buf, 8);     // while (1)循环检测是否有返回值
                    ifcvf_notify_queue(hw, qid);
                        IFCVF_WRITE_REG16(qid, hw->notify_addr[qid]);     
            }              
            
    // 至此virtio设备就初始化完成了,后面可以进行正常的io和收发包,这里省去了每个queue的callfd的中断初始化,
    // 这部分初始化的核心位于内核态vfio-pci的实现中,所以先跳过,简单来说,就是将每个queue的callfd填充到一个irq_set->data[]中,
    // 然后调用下面函数通知给vfio:
    //    ioctl(internal->vfio_dev_fd, VFIO_DEVICE_SET_IRQS, irq_set);     
    // 需要留意的是irq_set->data[]的第一个,存放的是config中断的callfd,后面依次是queue0、queue1等等的callfd。
    // 这个顺序跟内核态驱动,注册中断的顺序是一致的,中断申请的时候,第一个中断分配给了config中断,后面依次分配给了queue0、queue1等。
    // 这里通过vfio-pci绑定中断eventfd,也是这个顺序。内核态初始化的时候,按照config、queue0、queue1...顺序先申请好了中断号,
    // 然后这里按照同样顺序,根据传入的irq_set->data[]的index顺序,将各个callfd eventfd绑定到同样顺序位置对应的中断,即调用request_irq函数,
    // 将中断号绑定中断处理函数vfio_msihandler和对应的传入的eventfd,这样,当设备触发中断的时候,在vfio_msihandler处理函数中,会调用
    // eventfd_signal(trigger, 1)通知用户态poll对应的eventfd,进而通过qemu触发到guest。 
    
    // device的msi_list链接的各个msi_desc的先后顺序,跟这里的irq_set->data[]的index先后顺序是一一对应的。根据这个可以把vector和irq关联起来。
0条评论
0 / 1000
h****n
6文章数
1粉丝数
h****n
6 文章 | 1 粉丝
原创

dpdk-vdpa对virtio寄存器的初始化和读写分析

2023-08-16 03:40:17
108
0

一、vdpa中virtio寄存器读写的初始化

ifcvf_init_hw
    // 读取pci配置空间中cap起始指针的偏移,大小为一个字节的内容,即首个capability的位置
    // 1. PCI_CAPABILITY_LIST为0x34,实际读取的时候,会将这个0x34(基于pci config空间的偏移),
    //    加上vfio config region基于device fd的偏移,从device fd读取该偏移位置的内容.
    // 2. pos返回的内容,就是在上面计算的偏移的位置读取的内容。与内核态驱动读取这个pos的位置的内容,应该是相同的。
    //    用户态这里的读取经过了vfio的转换。得到的都是基于pci配置空间开始的偏移。
    // 3. 后面迭代读取各个capability的时候,使用的pos(cap.cap_next)都会经过上面1中偏移的计算,然后去读取pci配置空间的cap内容。
    PCI_READ_CONFIG_BYTE(dev, &pos, PCI_CAPABILITY_LIST);    
        rte_pci_read_config(dev, val, 1, where)    
            pci_vfio_read_config(device, buf, len, offset);
                // 这里返回的是device fd
                fd = rte_intr_dev_fd_get(dev->intr_handle);    
                // VFIO_PCI_CONFIG_REGION_INDEX为7,表示第8个region,固定为pci配置空间
                // 1. 初始化的时候会通过系统调用:
                //     ioctl(vfio_dev_fd, VFIO_DEVICE_GET_REGION_INFO, ri)    
                //     来读取各个region的信息初始化pdev->region[]. 其中ri->index会先初始化为要读取的region的index
                // 2. 这里直接返回初始化过的该region的size和该region从device fd开始的偏移
                pci_vfio_get_region(dev, VFIO_PCI_CONFIG_REGION_INDEX, &size, &offset)    
                    *size   = pdev->region[index].size;
                    *offset = pdev->region[index].offset;
                // 从device fd开始指定偏移,读取len大小的数据到buf。
                // 1. 这里buf结构是struct ifcvf_pci_cap,即virtio cap的结构。
                // 2. 指定偏移,包括region基于device fd的偏移offset,和基于pci配置空间开始位置的偏移offs
                pread64(fd, buf, len, offset + offs)
    // 迭代读取pci配置空间中的各个cap内容,根据cap的offset返回virtio各个cap在相应bar偏移后的地址。
    // 1. 根据cap.cfg_type筛选virtio特定的cap,得到对应cap的bar和bar中的offset
    // 2. 基于已经初始化的bar的起始地址,即hw->mem_resource[bar].addr,再根据1中得到的offset偏移,得到该cap在bar中的偏移地址。
    // 3. 后面读取virtio各个capability中的寄存器的时候,直接基于这里初始化的比如hw->common_cfg记录的起始地址,
    //    再根据寄存器在bar中的偏移,去读取指定寄存器。下面的lm_cfg和mq_cfg也是如此。
    // 4. hw->common_cfg等cap指针变量,起始就是对应cap的数据结构,里面包含了各个寄存器,读写各个寄存器的时候直接变量引用即可。
    //    cap的结构指针在下面。
    while (pos) {       
        PCI_READ_CONFIG_RANGE(dev, (u32 *)&cap, sizeof(cap), pos);
            // 这个函数跟上面首次读取cap指针使用的函数是一样的。都是基于device fd开始,
            //     vfio config region偏移加上这里指定的where偏移,当做偏移总和,读取指定size大小的内容到val
            rte_pci_read_config(dev, val, size, where);
    
        switch (cap.cfg_type) {
            case IFCVF_PCI_CAP_COMMON_CFG:
                hw->common_cfg = get_cap_addr(hw, &cap);
                    u8 bar = cap->bar;
                    u32 offset = cap->offset;
                    return hw->mem_resource[bar].addr + offset;
            case IFCVF_PCI_CAP_NOTIFY_CFG:
            case IFCVF_PCI_CAP_ISR_CFG:    
            case IFCVF_PCI_CAP_DEVICE_CFG:   
        }
        pos = cap.cap_next;
    }          
    // 获取热迁移相关的cap在bar中的偏移地址。
    // 1. 这两个cap没有通过pci配置空间中的迭代cap查找,而是直接写死的bar index和offset。
    hw->lm_cfg = hw->mem_resource[4].addr;
    hw->mq_cfg = hw->mem_resource[4].addr + IFCVF_MQ_OFFSET;
    
    // 至此,virtio寄存器读取需要的初始化完成。vdpa中这部分是在vdpa启动,调用ifcvf_probe初始化virtio-pci设备层的时候执行。

下面是涉及到的数据结构

struct ifcvf_hw {
        u64    req_features;
        u8     notify_region;
        u32    notify_off_multiplier;
        struct ifcvf_pci_common_cfg *common_cfg;
        union {
                struct ifcvf_net_config *net_cfg;
                struct virtio_blk_config *blk_cfg;
                void *dev_cfg;
        };
        u8     *isr;
        u16    *notify_base;
        u16    *notify_addr[IFCVF_MAX_QUEUES * 2];
        u8     *lm_cfg;
        u8     *mq_cfg;
        struct vring_info vring[IFCVF_MAX_QUEUES * 2];
        u8 nr_vring;
        int device_type;
        struct ifcvf_pci_mem_resource mem_resource[IFCVF_PCI_MAX_RESOURCE];
};

struct ifcvf_pci_common_cfg {
        /* About the whole device. */
        u32 device_feature_select;
        u32 device_feature;
        u32 guest_feature_select;
        u32 guest_feature;
        u16 msix_config;
        u16 num_queues;
        u8 device_status;
        u8 config_generation;
        
        /* About a specific virtqueue. */
        u16 queue_select;
        u16 queue_size;
        u16 queue_msix_vector;
        u16 queue_enable;
        u16 queue_notify_off;
        u32 queue_desc_lo;
        u32 queue_desc_hi;
        u32 queue_avail_lo; 
        u32 queue_avail_hi;
        u32 queue_used_lo;
        u32 queue_used_hi;
};

 

二、vdpa对virtio寄存器的读写操作

update_datapath         
    vdpa_ifcvf_start           
        ifcvf_start_hw(&internal->hw); 
            // 1. reset、IFCVF_CONFIG_STATUS_ACK、IFCVF_CONFIG_STATUS_DRIVER、IFCVF_CONFIG_STATUS_FEATURES_OK、IFCVF_CONFIG_STATUS_DRIVER_OK,
            //    均是通过直接写hw->common_cfg->device_status变量,该变量映射的硬件设备该寄存器。
            // 2. feature和其他commoncfg里的寄存器也是通过相同的方式,直接写的变量。
            // 3. 对于mq_cfg则是直接以变量u8的方式直接读写它映射的地址。
            //    根据num queue,设置给mq_cfg。如果是blk,则直接写入num queue,如果是net则写入queue pair数
            // 4. 对于cfg->queue_desc_lo、cfg->queue_size、lm_cfg的last_avail_idx和last_used_idx、cfg->queue_enable等等,
            //    则是先从vhost_virtqueue中获取到,再设置给硬件寄存器。
            // 5. 对于每个队列的queue_msix_vector,则是从1写入queue0, 2写入queue1依次类推,不区分blk和net。最后一个ctrlq的queue_msix_vector应该是没有写。
            //    内核态驱动中,会将这里填入queue_msix_vector的值跟实际设备的中断号关联起来。
            // 6. 对于每个队列的cfg->queue_notify_off,则是读取到notify_off中,然后进行下面计算得到当前队列的notify地址:
            //    hw->notify_addr[i] = (void *)((u8 *)hw->notify_base + notify_off * hw->notify_off_multiplier);
            // 
            //    hw->notify_off_multiplier是从notify的pci配置空间的capability中读取出来的,如下:
            //    rte_pci_read_config(dev, &hw->notify_off_multiplier, 4, pos + sizeof(cap));
            //    即,从notify这个cap的起始位置pos,往后偏移一个cap结构的偏移,读取4个字节内容赋值给hw->notify_off_multiplier
            //
            //    hw->notify_base是根据notify cap在pci配置空间中指示的bar和offset,从对应映射的bar内存地址加偏移得到的地址,即:
            //    hw->notify_base = get_cfg_addr(dev, &cap);
            ifcvf_reset(hw);
                ifcvf_set_status(hw, 0);
                    IFCVF_WRITE_REG8(status, &hw->common_cfg->device_status);
                        rte_write8((val), (reg))
                            rte_write8_relaxed(value, addr);
                                *(volatile uint8_t *)addr = value;
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_ACK);
                status |= ifcvf_get_status(hw);
                ifcvf_set_status(hw, status);
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_DRIVER);  
            ifcvf_config_features(hw)
                host_features = ifcvf_get_features(hw);    // 获取hw的feature
                hw->req_features &= host_features;         // 跟当前软件的feature取交集
                ifcvf_set_features(hw, hw->req_features);    // 设置给hw的feature
                    struct ifcvf_pci_common_cfg *cfg = hw->common_cfg;
                    IFCVF_WRITE_REG32(0, &cfg->guest_feature_select);
                    IFCVF_WRITE_REG32(features & ((1ULL << 32) - 1), &cfg->guest_feature);
            
                    IFCVF_WRITE_REG32(1, &cfg->guest_feature_select);
                    IFCVF_WRITE_REG32(features >> 32, &cfg->guest_feature);
                ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_FEATURES_OK);
                
            ifcvf_hw_enable(hw)
                IFCVF_WRITE_REG16(0, &cfg->msix_config);
                cfg = hw->common_cfg;
                lm_cfg = hw->lm_cfg;
                ifcvf_enable_mq(hw); 
                    // 根据num queue,设置给mq_cfg。如果是blk,则直接写入num queue,如果是net则写入queue pair数
                    mq_cfg = hw->mq_cfg;
                    *(u32 *)mq_cfg = nr_queue;
                    *(u32 *)mq_cfg = nr_queue / 2;
                for (i = 0; i < hw->nr_vring; i++) {
                    IFCVF_WRITE_REG16(i, &cfg->queue_select);
                    io_write64_twopart(hw->vring[i].desc, &cfg->queue_desc_lo, &cfg->queue_desc_hi);  
                    io_write64_twopart(hw->vring[i].avail, &cfg->queue_avail_lo, &cfg->queue_avail_hi); 
                    io_write64_twopart(hw->vring[i].used, &cfg->queue_used_lo,  &cfg->queue_used_hi);
                    IFCVF_WRITE_REG16(hw->vring[i].size, &cfg->queue_size);
                    if (lm_cfg) {
                        // 对于blk和net,分别将后端的avail 和used idx写到lm_cfg中。
                        // 对于blk,每个vq的两个idx,独占一个IFCVF_LM_CFG_SIZE
                        // 对于net,rx和tx一对vq,共享一个IFCVF_LM_CFG_SIZE,rxvq占前4个字节,txvq占靠后4个字节
                        *(u32 *)(lm_cfg + IFCVF_LM_RING_STATE_OFFSET + i * IFCVF_LM_CFG_SIZE) = 
                                                (u32)hw->vring[i].last_avail_idx |  
                                                ((u32)hw->vring[i].last_used_idx << 16);
                        *(u32 *)(lm_cfg + IFCVF_LM_RING_STATE_OFFSET + (i / 2) * IFCVF_LM_CFG_SIZE + 
                                                (i % 2) * 4) =   
                                                (u32)hw->vring[i].last_avail_idx |
                                                ((u32)hw->vring[i].last_used_idx << 16)
                    IFCVF_WRITE_REG16(i + 1, &cfg->queue_msix_vector);  
                    notify_off = IFCVF_READ_REG16(&cfg->queue_notify_off);
                    hw->notify_addr[i] = (void *)((u8 *)hw->notify_base + notify_off * hw->notify_off_multiplier);
                    IFCVF_WRITE_REG16(1, &cfg->queue_enable);                                                                                                                     
            ifcvf_add_status(hw, IFCVF_CONFIG_STATUS_DRIVER_OK)    
    // 将每个队列的notify对应的eventfd,即kickfd,添加到poll轮询中,等待qemu端触发notify。可读的时候,表示收到了notify,
    // 然后根据触发notify的队列,得到硬件设备的notify地址,并将队列index写入该地址,通知硬件设备。                         
    ret = setup_notify_relay(internal);
                                                                                                                                         
        rte_ctrl_thread_create(&internal->tid, name, NULL, notify_relay,
        // 创建线程notify_relay,里面poll监听所有kickfd的notify事件,
        // 如果有,则提取对应的qid,并写一下该queue的notify,通知给硬件
            // 将每个vq的kickfd添加到
            q_num = rte_vhost_get_vring_num(internal->vid);
            for (qid = 0; qid < q_num; qid++) {
                ev.events = EPOLLIN | EPOLLPRI;
                rte_vhost_get_vhost_vring(internal->vid, qid, &vring);
                ev.data.u64 = qid | (uint64_t)vring.kickfd << 32;
                epoll_ctl(epfd, EPOLL_CTL_ADD, vring.kickfd, &ev)
            }
            for (;;) {
                nfds = epoll_wait(epfd, events, q_num, -1);  
                for (i = 0; i < nfds; i++) {
                    qid = events[i].data.u32;
                    kickfd = (uint32_t)(events[i].data.u64 >> 32);
                    nbytes = read(kickfd, &buf, 8);     // while (1)循环检测是否有返回值
                    ifcvf_notify_queue(hw, qid);
                        IFCVF_WRITE_REG16(qid, hw->notify_addr[qid]);     
            }              
            
    // 至此virtio设备就初始化完成了,后面可以进行正常的io和收发包,这里省去了每个queue的callfd的中断初始化,
    // 这部分初始化的核心位于内核态vfio-pci的实现中,所以先跳过,简单来说,就是将每个queue的callfd填充到一个irq_set->data[]中,
    // 然后调用下面函数通知给vfio:
    //    ioctl(internal->vfio_dev_fd, VFIO_DEVICE_SET_IRQS, irq_set);     
    // 需要留意的是irq_set->data[]的第一个,存放的是config中断的callfd,后面依次是queue0、queue1等等的callfd。
    // 这个顺序跟内核态驱动,注册中断的顺序是一致的,中断申请的时候,第一个中断分配给了config中断,后面依次分配给了queue0、queue1等。
    // 这里通过vfio-pci绑定中断eventfd,也是这个顺序。内核态初始化的时候,按照config、queue0、queue1...顺序先申请好了中断号,
    // 然后这里按照同样顺序,根据传入的irq_set->data[]的index顺序,将各个callfd eventfd绑定到同样顺序位置对应的中断,即调用request_irq函数,
    // 将中断号绑定中断处理函数vfio_msihandler和对应的传入的eventfd,这样,当设备触发中断的时候,在vfio_msihandler处理函数中,会调用
    // eventfd_signal(trigger, 1)通知用户态poll对应的eventfd,进而通过qemu触发到guest。 
    
    // device的msi_list链接的各个msi_desc的先后顺序,跟这里的irq_set->data[]的index先后顺序是一一对应的。根据这个可以把vector和irq关联起来。
文章来自个人专栏
系统问题调试
5 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0