用户态驱动程序UIO
UIO(Userspace I/O)是运行在用户空间的I/O技术。Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可。
UIO的内核部分和用户空间的工作
内核空间
UIO的少量运行在内核空间的驱动所做的工作有哪些呢?
(1)分配和记录设备需要的资源和注册uio设备
在设备的探测函数中:
-使能PCI 设备
-申请资源
-读取并记录配置信息
-注册uio设备// uio_register_device()
// uio_8139d_pci_probe & uio_8139d_handler
(2)必须在内核空间实现的小部分中断应答函数
用户空间的关键操作
(1)关键操作
(2)响应硬件中断
有什么优势?
1. 用户空间驱动程序的优点
- 可以和整个C库链接。
- 在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持。如果能在用户态实现驱动,就可以轻松解决这一问题。
- 驱动问题不会导致整个系统挂起。内核态驱动的一些错误常常导致整个系统挂起。
- 用户态的驱动调试方便。
- 可以给出封闭源码的驱动程序,不必采用GPL,更为灵活。
源码简单分析
与其他内核PCI模块开发一样代码结构,UIO驱动实现部分:
关键数据结构:
//dpdk定义的uio pci设备描述结构
struct rte_uio_pci_dev {struct uio_info info; //uio 通用结构struct pci_dev *pdev; //pci设备描述结构enum rte_intr_mode mode; //中断模式
};
struct uio_info {struct uio_device *uio_dev; //uio设备属于const char *name; //名称const char *version; //版本号struct uio_mem mem[MAX_UIO_MAPS];//可映射的内存区域列表,size == 0表示列表结束struct uio_port port[MAX_UIO_PORT_REGIONS]; //网口区域列表long irq; //UIO_IRQ_CUSTOM 中断号unsigned long irq_flags; //请求中断号的标志void *priv; //可选的私有数据irqreturn_t (*handler)(int irq, struct uio_info *dev_info); //中断信息处理int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);//内存映射操作int (*open)(struct uio_info *info, struct inode *inode); //打开int (*release)(struct uio_info *info, struct inode *inode); //释放int (*irqcontrol)(struct uio_info *info, s32 irq_on); //中断控制操作 关闭/打开 当向/dev/uioX中写入值时
};
关键处理函数:
static int __init
igbuio_pci_init_module(void)
{int ret;ret = igbuio_config_intr_mode(intr_mode); //内核insmod时带的参数,中断模式if (ret < 0)return ret;return pci_register_driver(&igbuio_pci_driver);//注册PCI设备,实际调用pci_module_init。
}
关键的pci驱动操作函数,主要是探测和删除
static struct pci_driver igbuio_pci_driver = {.name = "igb_uio", //名称.id_table = NULL,.probe = igbuio_pci_probe, //探测回调函数.remove = igbuio_pci_remove,//删除回调函数
};
关键看下igbuio_pci_probe:
//根据内核版本不同,返回类型不同
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
static int __devinit
#else
static int
#endif
igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{struct rte_uio_pci_dev *udev;struct msix_entry msix_entry;int err;//分配内核空间内存,rte_uio_pci_dev一个设备类型大小udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL);if (!udev)return -ENOMEM;/** 使能设备: 调用更底层的PCI代码使能设备的内存和I/O区域*/err = pci_enable_device(dev);if (err != 0) {dev_err(&dev->dev, "Cannot enable PCI device\n");goto fail_free;}/*预留PCI设备的i/o或内存区域,pci_request_regions这个函数封装了一些PCI驱动相关的内存操作,不深入理解;*/err = pci_request_regions(dev, "igb_uio");if (err != 0) {dev_err(&dev->dev, "Cannot request regions\n");goto fail_disable;}/* 将设备设置层DMA总线主模式 */pci_set_master(dev);/* 重新映射I/O内存,同样详细的封装不做具体理解 */err = igbuio_setup_bars(dev, &udev->info);if (err != 0)goto fail_release_iomem;/* 设定 64-bit DMA mask 若函数返回成功,可以在位于该函数所带参数范围内的任意地址进行DMA操作。*/err = pci_set_dma_mask(dev, DMA_BIT_MASK(64));if (err != 0) {dev_err(&dev->dev, "Cannot set DMA mask\n");goto fail_release_iomem;}//内存范围一致性的处理err = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64));if (err != 0) {dev_err(&dev->dev, "Cannot set consistent DMA mask\n");goto fail_release_iomem;}/* 填充uio信息 */udev->info.name = "igb_uio";udev->info.version = "0.1";udev->info.handler = igbuio_pci_irqhandler;udev->info.irqcontrol = igbuio_pci_irqcontrol;
#ifdef CONFIG_XEN_DOM0/* check if the driver run on Xen Dom0 */if (xen_initial_domain())udev->info.mmap = igbuio_dom0_pci_mmap;
#endifudev->info.priv = udev;udev->pdev = dev;switch (igbuio_intr_mode_preferred) {case RTE_INTR_MODE_MSIX:/* Only 1 msi-x vector needed */msix_entry.entry = 0;if (pci_enable_msix(dev, &msix_entry, 1) == 0) {dev_dbg(&dev->dev, "using MSI-X");udev->info.irq = msix_entry.vector;udev->mode = RTE_INTR_MODE_MSIX;break;}/* fall back to INTX */case RTE_INTR_MODE_LEGACY:if (pci_intx_mask_supported(dev)) {dev_dbg(&dev->dev, "using INTX");udev->info.irq_flags = IRQF_SHARED;udev->info.irq = dev->irq;udev->mode = RTE_INTR_MODE_LEGACY;break;}dev_notice(&dev->dev, "PCI INTX mask not supported\n");/* fall back to no IRQ */case RTE_INTR_MODE_NONE:udev->mode = RTE_INTR_MODE_NONE;udev->info.irq = 0;break;default:dev_err(&dev->dev, "invalid IRQ mode %u",igbuio_intr_mode_preferred);err = -EINVAL;goto fail_release_iomem;}//用特定属性创建sysfs节点组err = sysfs_create_group(&dev->dev.kobj, &dev_attr_grp);if (err != 0)goto fail_release_iomem;/* 注册uio设备 */err = uio_register_device(&dev->dev, &udev->info);if (err != 0)goto fail_remove_group;pci_set_drvdata(dev, udev);dev_info(&dev->dev, "uio device registered with irq %lx\n",udev->info.irq);return 0;
fail_remove_group:sysfs_remove_group(&dev->dev.kobj, &dev_attr_grp);
fail_release_iomem:igbuio_pci_release_iomem(&udev->info);if (udev->mode == RTE_INTR_MODE_MSIX)pci_disable_msix(udev->pdev);pci_release_regions(dev);
fail_disable:pci_disable_device(dev);
fail_free:kfree(udev);return err;
}
DPDK应用层实现
网卡驱动模型一般包含三层,即PCI总线设备、网卡设备以及网卡设备的私有数据结构,即将设备的共性一层层的抽象,PCI总线设备包含网卡设备,网卡设备又包含其私有数据结构。在DPDK中,首先会注册设备驱动,然后查找当前系统有哪些PCI设备,并通过PCI_ID为PCI设备找到对应的驱动,最后调用驱动初始化设备。
一、网卡驱动注册
以e1000网卡驱动为例说明。
在1.8.0版本中,网卡驱动的注册使用了一种奇技淫巧的方法,使用GCC attribute扩展属性的constructor属性,使得网卡驱动的注册在程序MAIN函数之前就执行了。
staticstruct rte_driver pmd_igb_drv ={.type =PMD_PDEV,.init =rte_igb_pmd_init,};staticstruct rte_driver pmd_igbvf_drv ={.type =PMD_PDEV,.init =rte_igbvf_pmd_init,};PMD_REGISTER_DRIVER(pmd_igb_drv);PMD_REGISTER_DRIVER(pmd_igbvf_drv);
其中PMD_REGISTER_DRIVER()宏的定义如下:
#define PMD_REGISTER_DRIVER(d)\void devinitfn_ ##d(void);\void __attribute__((constructor, used)) devinitfn_ ##d(void)\{\rte_eal_driver_register(&d);\}
使用attribute的constructor属性,在MAIN函数执行前,就执行rte_eal_driver_register()函数,将pmd_igb_drv驱动挂到全局dev_driver_list链表上。
原文链接:https://blog.csdn.net/pangyemeng/article/details/78457599
更多DPDK学习资料有需要的可以自行添加进入学习交流君 羊 793599096 免费获取,或自行报名学习,免费订阅,永久学习,关注我持续更新哦!!!
学习地址:http://ke.qq.com/course/5066203?flowToken=1043717