igb uio小结

前两天对DPDKigb_uio相关的代码做了下分析,先把这期间碰到的一些问题和代码分析记下来,作为一个小总结。

Igb_uio代码相关的可以分为三个部分:igb_uio内核驱动,内核uio框架,uio用户态部分。


Igb_uio内核驱动

Igb_uio驱动主要做的就是注册一个pci设备。但是igbuio_pci_driver对应的保存pci设备信息的id_table指针为空,这样在内核注册此pci设备时,会找不到匹配的设备,就不会调用igb_uio驱动中的探测probe函数,只会在/sys目录下创建Igb_uio相应的目录。


如何probe

在插入igb_uio.ko时是probe不到设备的,那是什么时候侦测到的呢?dpdk提供了一个Python脚本dpdk_nic_bind.py。

python tools/dpdk_nic_bind.py --bind=igb_uio eth1

运行上述命令就是将eth1网卡绑定到igb_uio模块。这时dmesg就会看到igb_uio模块的probe函数执行了,也就是意味着扫描到了匹配的pci设备。


经过分析dpdk_nic_bind.py,此脚本文件在上述情况下主要做了以下几步:

◆获取参数指定的网卡eth1的设备信息。使用lspci–Dvmmn查看。

Slot:   0000:06:00.1                                                                            

Class:  0200

Vendor: 8086

Device: 1521

SVendor:        15d9

SDevice:        1521

Rev:    01

可以查看到slot槽位信息、厂商号vendor ID、设备号device ID等信息。


unbind之前的igb模块。

将前面获取到的eth1对应的slot信息0000:06:00.1值写入igbunbind文件。

echo 0000:06:00.1 >  /sys/bus/pci/drivers/igb/unbind

从内核代码分析此unbind的动作就是将igb模块信息和此pci设备Dev去关联。将dev->driver指针置为空,这个很重要。在内核处理pci设备注册的函数中,就算驱动的vendor IDdevice ID与设备的都匹配上了,如果此设备的dev->driver指针不为空,也不会调用probe函数的。


bind新的igb_uio模块

eth1设备的vendordevice ID信息写入igb_uionew_id文件。

echo 0x8086 0x1521 >  /sys/bus/pci/drivers/igb_uio/new_id

内核中处理此步的函数为store_new_id,此函数中是将写入的vendor和device存入到此driver,也就是igb_uioid_table,然后以此与PCI上的设备进行匹配,这个时候肯定会匹配成功,然后调用igb_uio模块的probe函数进行初始化动作。


初始化分析

一个设备驱动要实现的功能根据实际需要可能千差万别,但是究其本质来说无非两件事情:一个是内存的操作,另外一个就是中断的处理。Igb_uio驱动和igb驱动都是网卡这个PCI设备的驱动,相同点就是要使能PCI设备,分配内存等,不同的就在于对内存和中断的处理方式的差异。


下面看下igb_uio驱动与igb驱动相比的probe函数的不同之处:

◆记录设备的资源

igb_uio模块遍历此PCI设备BAR空间,对应于类型为存储器空间IORESOURCE_MEMBAR,将其物理地址、大小等信息保存到uio_info结构的mem数组中之中;类型为寄存器空间IORESOURCE_IOBAR,将其物理地址、大小等信息保存到uio_info结构的port数组中。


从上图可知,此网卡PCI设备有三个BAR空间,BAR0类型为存储器空间,物理地址为0xfbd00000,大小为128KBAR2类型为寄存器空间,物理地址为0xb000,大小为32字节;BAR3类型为存储器空间,物理地址为0xfbd40000,大小为16K

Igb驱动也会遍历BAR空间,但是它不会记录空间的物理地址,而是调用ioremap函数将物理地址映射为虚拟地址,驱动在内核态读写操作映射出来的虚拟地址。


◆注册一个uio设备

Linux上的驱动设备一般都是运行在内核态的,提供接口函数给用户态函数调用即可。而新引入的UIO技术,则是将驱动的大部分事情移到了用户态。前面讲到probe函数会记录设备的资源,具体而言就是PCI设备BAR空间的物理地址、大小等信息记录下来传给用户态。除了记录BAR空间资源信息,uio框架还会在内核态实现中断处理相关的初始化工作。

igbuio_pci_probe代码片段

/* fill uio infos */  

udev->info.name ="igb_uio"; 

udev->info.version ="0.1"; 

udev->info.handler =igbuio_pci_irqhandler; 

udev->info.irqcontrol =igbuio_pci_irqcontrol;                                                                            

注册的uio设备名为igb_uio,内核态中断处理函数为igbuio_pci_irqhandler,中断控制函数igbuio_pci_irqcontrol。这两个函数有什么用呢?下面在分析UIO注册函数时分析。

igbuio_pci_probe代码片段

switch (igbuio_intr_mode_preferred) { 

case RTE_INTR_MODE_MSIX:  

    msix_entry.entry =0; 

    if (pci_enable_msix(dev,&msix_entry,1)==0) {                                                                        

        udev->info.irq =msix_entry.vector; 

        udev->mode =RTE_INTR_MODE_MSIX; 

        break; 

    }  


case RTE_INTR_MODE_LEGACY:  

    if (pci_intx_mask_supported(dev)) { 

        udev->info.irq_flags =IRQF_SHARED; 

        udev->info.irq =dev->irq; 

        udev->mode =RTE_INTR_MODE_LEGACY; 

        break; 

    }   

变量igbuio_intr_mode_preferred表示中断的模式,它由igb_uio驱动模块的参数intr_mode决定,有MSIX中断和Legacy中断两种模式,默认的是MSIX中断模式。

如果是MSIX中断模式,调用pci_enable_msix函数向PCI子系统申请分配一个MSIX中断;如果分配成功就会初始化uio_infoirq为申请到的中断号。如果是传统的Intx中断模式,调用pci_intx_mask_supported函数读取PCI配置空间,检查是否支持Intx中断。

在对uio_info内存和中断相关的成员初始化之后,就是调用uio_register_device函数来注册uio设备了,下面结合内核代码看下此函数做了些什么。

__uio_register_device代码片段

idev->owner =owner; 

idev->info =info; 

init_waitqueue_head(&idev->wait); 

atomic_set(&idev->event,0);

idev->dev =device_create(&uio_class,parent, 

                            MKDEV(uio_major,idev->minor),idev, 

                            "uio%d",idev->minor); 

 

ret =uio_dev_add_attributes(idev); 

info->uio_dev =idev; 

 

if (info->irq &&(info->irq !=UIO_IRQ_CUSTOM)) { 

    ret =devm_request_irq(idev->dev,info->irq,uio_interrupt,                                                      

    info->irq_flags,info->name,idev); 

}  

上面代码是__uio_register_device函数的主要部分

1、 初始化uio_device结构体指针idev,主要包括等待队列wait、中断事件计数event

次设备号minor等。

2、在/dev目录下创建了一个uio设备,设备名为uio%d%d对应的就是次设备号minor。

3、接着就是调用uio_dev_add_attributes函数在/sys/class/uio/uioX/目录下创建mapsportio接口。前面讲到会遍历此PCI设备的BAR空间,将存储器空间类型的BAR的物理地址等信息存储在uio_infomem数组中,这里就会根据此mem数组在maps目录下为每个寄存器类型的BAR创建一个目录,如下图所示:


前面图1可以看到igb网卡有两个类型为IORESOURCE_MEMBAR,分别为BAR0BAR3,这里就创建了map0map1两个子目录分别对应BAR0BAR3。你可以依次查看对应目录下的nameaddr字段,正好对应于图1中看到的信息。

同理会根据port数组在portio目录下为每个寄存器类型的BAR创建一个子目录,这里就不细说了。

4、最后就是注册中断了,中断的中断号、中断标志等在前面有讲到,这里看下注册的中断处理函数uio_interrupt。

static irqreturn_t uio_interrupt(intirq,void *dev_id) 

{ 

    struct uio_device *idev =(struct uio_device *)dev_id;                                                           

    irqreturn_t ret =idev->info->handler(irq,idev->info); 

  

    if (ret==IRQ_HANDLED) 

        uio_event_notify(idev->info); 

  

    return ret; 

} 

此函数首先调用igb_uio驱动中设置的中断处理函数igbuio_pci_irqhandler来检查中断是不是此设备的中断,如果是就返回IRQ_HANDLED表示需要处理,接着调用函数uio_event_notify来唤醒等待队列wait上进程来处理中断事宜。


没有更多推荐了,返回首页