微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 88|回复: 0

DPDK——UIO 驱动认识(一)

[复制链接]

2

主题

3

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2022-9-21 02:23:33 | 显示全部楼层 |阅读模式
用户态驱动程序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;
#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, "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设备找到对应的驱动,最后调用驱动初始化设备。
学习地址: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

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-7 04:52 , Processed in 0.094660 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表