dpdk uio驱动

关于dpdk的驱动层,一直以来都没有理的很清楚。一是因为本人不是开发驱动的,对驱动知识相当匮乏,二来用dpdk来开发,貌似也不需要过多关注底层驱动逻辑。但是这块不懂的话,总会感觉对dpdk一知半解的,不踏实。所以这篇博客就是通过查阅资料和阅读源码总结出来的,如有理解错误的地方还望各位指正。因为uio是对IO设备而言的,因此本博客中的设备指的是IO设备。

1.linux设备驱动uio机制

        大家都知道,linux操作系统分为两个层级,一个是内核态,一个是用户态。平时编写的软件都是运行在用户态。以一个简单的udp socket通信举例,client1准备向client2发送数据,用户态的程序首先要将数据准备好,然后需要将数据传给网卡设备。但是这个时候问题就来了,我们如何将数据传给网卡设备呢?写过socket的知道,要先创建一个socket句柄,然后bind绑定ip+port,最后再sendto发送数据。这里面的bind和sendto就是内核给我们提供是api,调用这些函数就进行了一次系统调用。系统调用可以简单的理解成一次软中断,内核收到这个中断之后,会执行相应的动作,最终会调用到网卡驱动提供的send等函数。最后将结果返回给用户态的进程。client2如果想接收到client1发送过来的数据,也要进行bind和recvfrom。当网卡收到数据后,产生一次中断,通知内核将网卡的数据转到用户态。这个过程中还包含了内核检查报文头部,判断这些数据是否为client2希望收到的数据。

       系统调用是很耗费cpu性能的,这也就是为什么I/O密集型任务并不适合传统linux架构的原因。幸运的是,一些前辈大牛搞出了uio机制,让驱动大部分功能运行在用户态,只有一小部分运行在内核态,比如中断等。值得注意的是,仅仅是uio驱动还不能实现网卡的收发包,因为这个驱动并没有提供网卡的配置函数和收发包函数。uio驱动的作用是让你在用户态就可以操作网卡设备的内存。dpdk同时还提供了pmd用户态驱动,用户态pmd驱动就是通过uio机制,通过操作网卡的寄存器实现在用户态收发报文。关于用户态pmd驱动,下一篇文章再叙述。

       下面贴一个几乎每个讲解uio机制的文章都会有的一张图:

       注册到uio驱动上的设备,uio驱动会在/sys/class/uio目录下生成相对应的文件夹来记录设备的一些信息,同时会在/dev目录下生成相对应的设备文件。这样用户态就可以通过/sys/class/uio/uio0/maps/map0和/sys/class/uio/uio0/portio/port0来访问这个uio设备。注意/sys/class/uio/uio0/maps/map0下的各个文件,addr文件对应的就是该设备内存的物理地址。后续开发对应网卡的驱动,会读取这个地址,然后通过offset文件的值,获取到设备寄存器的地址。关于这个地址是如何产生的,有兴趣的读者可以参考https://blog.csdn.net/weixin_44936219/article/details/102715837这篇文章。关于如何写一个简单的uio驱动,可以参考https://www.cnblogs.com/allcloud/p/7808776.html这篇文章。

2.dpdk igb_uio驱动的实现

dpdk自己实现了一个uio驱动,名称叫igb_uio驱动。源码路径在lib\librte_eal\linuxapp\igb_uio\igb_uio.c。

定义一个struct pci_deiver的结构体。

static struct pci_driver igbuio_pci_driver = {
	.name = "igb_uio",
	.id_table = NULL,
	.probe = igbuio_pci_probe,
	.remove = igbuio_pci_remove,
};

简单的说明下struct pci_deiver这个结构体。在linux系统中,每个pci驱动都有一个pci_driver实例,用以描述驱动名称,支持的设备信息,以及对应的操作函数;

/*
    描述一个pci设备,每个pci驱动必须创建一个pci_driver实例
*/
struct pci_driver {
    struct list_head node;
    const char *name;  /* 驱动程序名,内核中所有pci驱动程序名都是唯一的 */    
    const struct pci_device_id *id_table;    /* must be non-NULL for probe to be called  pci设备配置信息数组 */    
    int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);    /* New device inserted  设备插入内核时调用 */    
    void (*remove) (struct pci_dev *dev);    /* Device removed (NULL if not a hot-plug capable driver)  设备从内核移除时调用 */*/    
    int  (*suspend) (struct pci_dev *dev, pm_message_t state);    /* Device suspended */
    int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
    int  (*resume_early) (struct pci_dev *dev);   
    int  (*resume) (struct pci_dev *dev);                    /* Device woken up */    
    void (*shutdown) (struct pci_dev *dev);
    int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */    
    const struct pci_error_handlers *err_handler;
    struct device_driver    driver;
    struct pci_dynids dynids;
};

在内核中注册一个pci驱动,要调用内核提供的api:pci_register_driver,入参就是struct pci_driver。当有设备绑定到这个pci驱动且设备的struct pci_device_id在id_table里的时候,内核就会调用probe函数。 igb_uio驱动中,id_table设置为空,所以当我们在内核中加载igb_uio.ko的时候,并不会调用probe函数。只有在我们运行dpdk提供的dpdk-devbind.py脚本绑定网卡的时候,probe函数才会被调用。

static int __init
igbuio_pci_init_module(void)
{
	int ret;

	ret = igbuio_config_intr_mode(intr_mode);
	if (ret < 0)
		return ret;

	return pci_register_driver(&igbuio_pci_driver);
}

static void __exit
igbuio_pci_exit_module(void)
{
	pci_unregister_driver(&igbuio_pci_driver);
}

module_init(igbuio_pci_init_module);
module_exit(igbuio_pci_exit_module);

module_init是注册模块初始化函数,模块加载时会执行这个函数。pci_register_driver就是向内核注册一个pci驱动,名称是struct pci_driver指定的,这里为igb_uio,probe函数为igbuio_pci_probe。

我们来关注下igbuio_pci_probe这个函数。

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;
	dma_addr_t map_dma_addr;
	void *map_addr;
	int err;

	udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL);
	if (!udev)
		return -ENOMEM;

	mutex_init(&udev->lock);
	/*
	 * enable device: ask low-level code to enable I/O and
	 * memory
	 */
	err = pci_enable_device(dev);  //开启设备,
	if (err != 0) {
		dev_err(&dev->dev, "Cannot enable PCI device\n");
		goto fail_free;
	}

	/* enable bus mastering on the device */
	pci_set_master(dev);

	/* remap IO memory */
	err = igbuio_setup_bars(dev, &udev->info);  /* 在这个函数中,会对设备进行一系列配置,最终的结果就是访问设备寄存器不需要持有寄存器的地址,而直接访问内存地址就可以了 */
	if (err != 0)
		goto fail_release_iomem;

	/* set 64-bit DMA mask */
	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;
	}

	/* fill uio infos */
	udev->info.name = "igb_uio";
	udev->info.version = "0.1";
	udev->info.irqcontrol = igbuio_pci_irqcontrol;
	udev->info.open = igbuio_pci_open;
	udev->info.release = igbuio_pci_release;
	udev->info.priv = udev;
	udev->pdev = dev;

	err = sysfs_create_group(&dev->dev.kobj, &dev_attr_grp);
	if (err != 0)
		goto fail_release_iomem;

	/* register uio driver 应该是源码注释写错了,这里注册的是uio设备而非驱动*/
	err = uio_register_device(&dev->dev, &udev->info);
    /* 注册完成之后,可以发现在/dev目录下出现了uioX,在/sys/class/uio目录下出现了uioX文件夹。        
     * 用户态进程可以通过读取/sys/class/uio/uioX/maps/map0目录下的文件来操作设备*/
	if (err != 0)
		goto fail_remove_group;

	pci_set_drvdata(dev, udev);

	/*
	 * Doing a harmless dma mapping for attaching the device to
	 * the iommu identity mapping if kernel boots with iommu=pt.
	 * Note this is not a problem if no IOMMU at all.
	 */
	map_addr = dma_alloc_coherent(&dev->dev, 1024, &map_dma_addr,
			GFP_KERNEL);
	if (map_addr)
		memset(map_addr, 0, 1024);

	if (!map_addr)
		dev_info(&dev->dev, "dma mapping failed\n");
	else {
		dev_info(&dev->dev, "mapping 1K dma=%#llx host=%p\n",
			 (unsigned long long)map_dma_addr, map_addr);

		dma_free_coherent(&dev->dev, 1024, map_addr, map_dma_addr);
		dev_info(&dev->dev, "unmapping 1K dma=%#llx host=%p\n",
			 (unsigned long long)map_dma_addr, map_addr);
	}

	return 0;

fail_remove_group:
	sysfs_remove_group(&dev->dev.kobj, &dev_attr_grp);
fail_release_iomem:
	igbuio_pci_release_iomem(&udev->info);
	pci_disable_device(dev);
fail_free:
	kfree(udev);

	return err;
}

        函数执行完成之后,设备就被绑定到igb_uio驱动上了,后续就是通过pmd驱动来对网卡进行配置、报文读取等。

3.关于外设的物理地址

        有关dpdk uio机制其实已经解读完成,心中还有一个疑惑,就是关于如何理解外设的物理地址。https://blog.csdn.net/baidu_37973494/article/details/82389577这篇文章做了一个比较详细的说明,在此mark一下,学习学习~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值