|
用户态驱动程序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 = &#34;igb_uio&#34;, //名称
.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, &#34;Cannot enable PCI device\n&#34;);
goto fail_free;
}
/*
预留PCI设备的i/o或内存区域,pci_request_regions这个函数封装了一些PCI驱动相关的内存操作,不深入理解;
*/
err = pci_request_regions(dev, &#34;igb_uio&#34;);
if (err != 0) {
dev_err(&dev->dev, &#34;Cannot request regions\n&#34;);
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, &#34;Cannot set DMA mask\n&#34;);
goto fail_release_iomem;
}
//内存范围一致性的处理
err = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64));
if (err != 0) {
dev_err(&dev->dev, &#34;Cannot set consistent DMA mask\n&#34;);
goto fail_release_iomem;
}
/* 填充uio信息 */
udev->info.name = &#34;igb_uio&#34;;
udev->info.version = &#34;0.1&#34;;
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;
#endif
udev->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, &#34;using MSI-X&#34;);
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, &#34;using INTX&#34;);
udev->info.irq_flags = IRQF_SHARED;
udev->info.irq = dev->irq;
udev->mode = RTE_INTR_MODE_LEGACY;
break;
}
dev_notice(&dev->dev, &#34;PCI INTX mask not supported\n&#34;);
/* 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, &#34;invalid IRQ mode %u&#34;,
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, &#34;uio device registered with irq %lx\n&#34;,
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设备找到对应的驱动,最后调用驱动初始化设备。
学习地址:Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家 (更多DPDK学习资料有需要的可以自行报名学习,免费订阅,永久学习)或者点击这里加Q群免费领取,关注我持续更新哦!!
相关视频推荐
零拷贝的实现 用户态协议栈/网卡/协议栈/dpdk/源码/虚拟化/DDoS/UDP
tcp/ip详解150行代码拉开协议栈实现的篇章/以太网协议/UDP/netmap/协议栈/柔性数组/ICMP/ARP 一、网卡驱动注册
以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
|
|