igb uio小结

igb uio小结前两天对 DPDK 的 igb uio 相关的代码做了下分析 先把这期间碰到的一些问题和代码分析记下来 作为一个小总结 Igb uio 代码相关的可以分为三个部分 igb uio 内核驱动 内核 uio 框架 uio 用户态部分 Igb uio 内核驱动 Igb uio 驱动主要做的就是注册一个 pci 设备 但是 igbuio pci driver 对应的保存 pci 设备信息的 id table 指针为空

前两天对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数组中。

igb uio小结

从上图可知,此网卡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创建一个目录,如下图所示:

igb uio小结

前面图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上进程来处理中断事宜。









版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214223.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午4:42
下一篇 2026年3月18日 下午4:43


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号