大家好,欢迎来到IT知识分享网。
目录
什么是UIO?
UIO(User-space I/O)驱动是一种特殊的Linux内核驱动,允许设备和用户空间之间进行直接的交互,而不需要通过传统的字符设备或块设备接口。UIO驱动在Linux内核版本2.6.18及以上的版本中被引入。使用UIO驱动可以对硬件进行快速的数据传输和处理,并且可以通过用户空间的应用程序来控制设备。
UIO驱动通常由两部分组成:内核模块和用户空间应用程序。内核模块负责管理设备的硬件资源,包括访问需要的寄存器和中断处理。用户空间应用程序使用UIO接口来注册设备和申请IO内存,然后可以使用mmap()系统调用将IO内存映射到应用程序的地址空间中。这样,应用程序就可以直接读写设备的寄存器和内存了。
UIO驱动的使用具有很大的灵活性和可扩展性。开发人员可以根据实际需求自定义UIO驱动来支持各种设备的控制和数据传输。同时,UIO驱动的开发也需要一定的专业技能和经验,需要了解硬件和操作系统的底层知识。
UIO驱动与普通驱动的区别
UIO驱动和普通驱动的区别主要在于它们的设计目的和使用方式上。
设计目的不同
普通驱动是为了提供系统级别的设备访问接口而设计的,它通常会暴露硬件的物理特性和控制方式,供应用程序或其他驱动程序使用。而UIO驱动则是为了提供简单、通用的用户空间设备接口而设计的,它可以隐藏底层硬件设备的细节,提供一组易于使用的API供用户程序或库调用。
使用方式不同
普通驱动一般需要编写内核模块,并在内核启动时加载,它会注册一个设备节点,并提供对该设备的控制和访问接口。而UIO驱动则可以在用户空间中运行,不需要编写内核模块,只需要使用系统提供的用户空间库即可。用户程序可以通过mmap系统调用将UIO设备的内存映射到自己的地址空间中,然后就可以直接访问设备的内存和寄存器了。
支持的设备类型不同
普通驱动可以支持各种类型的设备,包括网络设备、存储设备、图形设备等等,它们通常有专门的驱动程序来管理。而UIO驱动则更适用于一些简单的设备,如FPGA、微控制器等,这些设备不需要复杂的驱动程序,只需要提供一组简单的寄存器接口即可。
总之,UIO驱动和普通驱动各有其优缺点,开发者应根据实际需求选择适合的驱动类型。
How UIO works
每个UIO设备都可以通过设备文件和多个sysfs属性文件访问。第一个设备的设备文件将被称为“/dev/uio0”,后续设备将被称为“/dev/uio1”、“/dev/uio2”等等。
“/dev/uioX”用于访问卡的地址空间。使用:c:func:mmap()
访问卡的寄存器或RAM位置。
通过从“/dev/uioX”读取来处理中断。可以在“/dev/uioX”上通过c:func:read()阻塞读取
中断发生时。还可以在“/dev/uioX”上使用:c:func:select()
等待中断。从“/dev/uioX”读取的整数值表示总中断计数,判断是否错过了某些中断。
对于某些具有多个内部中断源但没有单独的IRQ屏蔽和状态寄存器的硬件,如果内核处理程序通过写入芯片的IRQ寄存器来禁用它们,用户空间可能无法确定中断源。在这种情况下,内核必须完全禁用IRQ以保持芯片的寄存器不受干扰。现在用户空间部分可以确定中断的原因,但它不能重新启用中断。另一个角落案例是芯片,其中重新启用中断是对组合的IRQ状态/确认寄存器进行read-modify-write。如果同时发生新的中断,这将是一种竞争。
为解决这些问题,UIO还实现了一个write()函数。它通常不被使用,对于只有一个中断源或具有单独的IRQ屏蔽和状态寄存器的硬件,它可以被忽略。如果驱动程序实现的:c:func:irqcontrol()
函数,向“/dev/uioX”写入一个32位值(0是禁用、1是启用中断)。如果没有实现:c:func:irqcontrol()
,则:c:func:write()
将返回“-ENOSYS”。
为了正确处理中断,自定义内核模块可以提供其自己的中断处理程序。它将自动由内置的处理程序调用。
对于不生成中断但需要轮询的卡,设置定时器触发中断处理程序。这种中断模拟是通过从定时器的事件处理程序:c:func:uio_event_notify()
来完成的。
每个驱动程序都提供用于读取或写入变量的属性,这些属性通过sysfs文件访问,自定义内核驱动程序模块可以将其自己的属性添加到由uio驱动程序。
UIO框架提供以下标准属性:
- “name”:设备的名称
- “version”:版本
- “event”:自上次读取设备节点以来驱动程序处理的总中断数。
每个UIO设备都可以使一个或多个内存区域可用于内存映射。这是必要的,因为某些工业I/O卡需要访问驱动程序中的多个PCI内存区域。
每个映射在sysfs中都有自己的目录,第一个映射显示为“/sys/class/uio/uioX/maps/map0/”。后续映射创建目录“map1/”,“map2/”等等。
每个“mapX/”目录包含四个只读文件,显示内存的属性:
- “name”:字符串标识符
- “addr”:可以映射的内存地址。
- “size”:由addr指向的内存的大小,以字节为单位。
- “offset”:c:func:
mmap()
返回实际设备内存的偏移量(以字节为单位)。如果设备的内存不对齐,则这很重要。请记住,由:c:func:mmap()
返回的指针始终对齐,始终添加偏移量是非常好习惯。
root@cary:~/uio_test# ls /dev/uio0 -alh crw------- 1 root root 240, 0 5月 30 11:42 /dev/uio0 root@cary:~/uio_test# tree /sys/class/uio/uio0/ -L 3 /sys/class/uio/uio0/ ├── dev ├── device -> ../../../uio_test ├── event ├── maps │ └── map0 │ ├── addr │ ├── name │ ├── offset │ └── size ├── name ├── power │ ├── async │ ├── autosuspend_delay_ms │ ├── control │ ├── runtime_active_kids │ ├── runtime_active_time │ ├── runtime_enabled │ ├── runtime_status │ ├── runtime_suspended_time │ └── runtime_usage ├── subsystem -> ../../../../../class/uio ├── uevent └── version
从用户空间,不同的映射通过调整:c:func:mmap()
调用的“offset”参数来区分。要映射映射N的内存,您必须使用N倍的页面大小作为偏移量:
offset = N * getpagesize();
有时硬件具有类似内存的区域,但是无法使用此处描述的技术进行映射,但仍然可以从用户空间访问它们。最常见的示例是x86 ioport。在x86系统上,用户空间可以使用:c:func:ioperm()
、:c:func:iopl()
、:c:func:inb()
、:c:func:outb()
等函数访问这些ioport区域。
由于这些ioport区域无法映射,因此它们不会像上面描述的正常内存一样显示在“/sys/class/uio/uioX/maps/”下。如果不了解硬件提供的端口区域的信息,那么用户空间部分的驱动程序将很难找出哪些端口属于哪个UIO设备。
为解决这种情况,添加了新目录“/sys/class/uio/uioX/portio/”。如果驱动程序要将有关一个或多个端口区域的信息传递给用户空间,则会显示该目录。如果是这种情况,将出现名为“port0”、“port1”等的子目录,位于“/sys/class/uio/uioX/portio/”下方。
每个“portX/”目录包含四个只读文件,显示端口区域的名称、起始位置、大小和类型:
- “name”:此端口区域的字符串标识符。该字符串是可选的,可以为空。驱动程序可以将其设置为使用户空间更容易找到特定的端口区域。
- “start”:此区域的第一个端口。
- “size”:此区域中的端口数。
- “porttype”:描述端口类型的字符串。
/ * struct uio_port - description of a UIO port region * @name: name of the port region for identification * @start: start of port region * @size: size of port region * @porttype: type of port (see UIO_PORT_* below) * @portio: for use by the UIO core only. */ struct uio_port { const char *name; unsigned long start; unsigned long size; int porttype; struct uio_portio *portio; };
重要的结构体
/ * struct uio_info - UIO device capabilities * @uio_dev: the UIO device this info belongs to * @name: device name * @version: device driver version * @mem: list of mappable memory regions, size==0 for end of list * @port: list of port regions, size==0 for end of list * @irq: interrupt number or UIO_IRQ_CUSTOM * @irq_flags: flags for request_irq() * @priv: optional private data * @handler: the device's irq handler * @mmap: mmap operation for this uio device * @open: open operation for this uio device * @release: release operation for this uio device * @irqcontrol: disable/enable irqs when 0/1 is written to /dev/uioX */ struct uio_info { struct uio_device *uio_dev; const char *name; const char *version; struct uio_mem mem[MAX_UIO_MAPS]; struct uio_port port[MAX_UIO_PORT_REGIONS]; long irq; 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); }; / * struct uio_mem - description of a UIO memory region * @name: name of the memory region for identification * @addr: address of the device's memory rounded to page * size (phys_addr is used since addr can be * logical, virtual, or physical & phys_addr_t * should always be large enough to handle any of * the address types) * @offs: offset of device memory within the page * @size: size of IO (multiple of page size) * @memtype: type of memory addr points to * @internal_addr: ioremap-ped version of addr, for driver internal use * @map: for use by the UIO core only. */ struct uio_mem { const char *name; phys_addr_t addr; unsigned long offs; resource_size_t size; int memtype; void __iomem *internal_addr; struct uio_map *map; }; #define MAX_UIO_MAPS 5 struct uio_portio; / * struct uio_port - description of a UIO port region * @name: name of the port region for identification * @start: start of port region * @size: size of port region * @porttype: type of port (see UIO_PORT_* below) * @portio: for use by the UIO core only. */ struct uio_port { const char *name; unsigned long start; unsigned long size; int porttype; struct uio_portio *portio; }; /* defines for uio_mem->memtype */ #define UIO_MEM_NONE 0 #define UIO_MEM_PHYS 1 #define UIO_MEM_LOGICAL 2 #define UIO_MEM_VIRTUAL 3 /* defines for uio_port->porttype */ #define UIO_PORT_NONE 0 #define UIO_PORT_X86 1 #define UIO_PORT_GPIO 2 #define UIO_PORT_OTHER 3
UIO驱动源码
可在虚拟机上运行,代码中写了详细的解释
#include <linux/module.h> #include <linux/uio_driver.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/vmalloc.h> #include <linux/platform_device.h> #define DRV_NAME "uio_test" #define MEM_SIZE 0x1000 /* struct uio_info 是 UIO 驱动中定义设备资源的数据结构,也是 UIO 驱动设备的主要参数之一,包括设备名称、设备版本、中断类型、内存映射等信息。 下面是 struct uio_info 结构体的具体字段: const char* name: 设备名称,字符串类型,比如 "uio_mydevice" const char* version: 设备版本,字符串类型,比如 "1.01" int irq: 设备使用的中断号,中断类型可以是UIO_IRQ_NONE, UIO_IRQ_EDGE, UIO_IRQ_LEVEL。 int irq_flags: 中断的处理方法 struct uio_mem* mem: 包含所需的内存资源的数组,可以是多个内存区域,每个内存块包括物理地址start、大小size、权限memtype等信息。 int memtype: 位于进程地址空间的内存区域的类型。可以是UIO_MEM_PHYS (物理内存),UIO_MEM_LOGICAL (逻辑内存),UIO_MEM_VIRTUAL (虚拟内存)。 void (*irqcontrol)(struct uio_info*, bool): 指向向文件操作提供中断的函数。 int (*open)(struct uio_info*, struct inode*): 打开设备的函数。 int (*release)(struct uio_info*, struct inode*): 关闭设备的函数。 int (*mmap)(struct uio_info*, struct vm_area_struct*): 内存映射的函数。 int (*ioctl)(struct uio_info*, unsigned int command, unsigned long argument): 设备控制函数。 int (*irqhandler)(struct uio_info*, int irqs): 中断处理函数,对于需要驱动处理的中断使用。 */ static struct uio_info uio_test = { .name = "uio_device", .version = "0.0.1", .irq = UIO_IRQ_NONE, }; static void uio_release(struct device *dev) { struct uio_device *uio_dev = dev_get_drvdata(dev); uio_unregister_device(uio_dev->info); kfree(uio_dev); } static int uio_mmap(struct file *filp, struct vm_area_struct * vma) { /* vm_area_struct结构体的主要成员变量如下: vm_start:虚拟内存区域的起始地址。 vm_end:虚拟内存区域的结束地址。 vm_next:链表中下一个虚拟内存区域的指针。 vm_flags:虚拟内存区域的标志,用于指定该区域的访问权限、映射方式等信息。 vm_page_prot:虚拟内存区域对应的物理内存页的保护属性。 vm_ops:虚拟内存区域的操作函数指针,用于操作该区域的相关操作。 vm_file:指向该虚拟内存区域对应的文件对象,如果该内存区域没有对应的文件,则为NULL。 vm_private_data:指向该虚拟内存区域私有数据的指针,可以用于存储和传递一些附加信息。 vm_area_struct结构体的主要作用是表示进程的虚拟内存空间,并为操作系统内存管理提供了一些必要的信息, 如虚拟地址范围、保护属性、映射方式等。它也为进程提供了一些操作虚拟内存的接口,如访问、分配、释放等。在进程创建、分配内存、映射文件等操作时, 都需要使用vm_area_struct结构体来描述进程虚拟内存的状态。 */ struct uio_info *info = filp->private_data; /*virt_to_page()将虚拟地址转换为一个指向相应页面描述符的指针,并使用page_to_pfn()获取该页面描述符对应的页框号*/ unsigned long pfn = page_to_pfn(virt_to_page(info->mem[0].addr)); /*PFN_PHYS()将页框号转换为相应的物理地址*/ unsigned long phys = PFN_PHYS(pfn); /*uio_info结构体中第一个内存区域的大小*/ unsigned long size = info->mem[0].size; /* remap_pfn_range函数用于将一段物理地址空间映射到进程的虚拟地址空间,并返回映射后的虚拟地址。 vma 是 vm_area_struct 结构体指针,表示进程的一段虚拟地址空间。 vma->vm_start 表示用户空间地址的起始地址。 phys >> PAGE_SHIFT 表示设备地址的起始页号。PAGE_SHIFT 表示的是系统页面大小的偏移量,通常为12位(2 ^ 12 = 4KB)。 这是因为物理地址的低 12 位表示页面的偏移量,需要去除才能得到页面的编号。 size 表示映射空间的大小。 vma->vm_page_prot 表示页保护标志,具体指定对应页的访问权限。 */ if (remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT, size, vma->vm_page_prot)) { return -EAGAIN; } return 0; } static const struct file_operations uio_fops = { .owner = THIS_MODULE, .mmap = uio_mmap, }; char test_arr[PAGE_SIZE] = {0}; static ssize_t get_uio_info(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", test_arr); } static ssize_t set_uio_info(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { snprintf(test_arr, PAGE_SIZE, "%s\n", buf); return count; } static DEVICE_ATTR(uio_info, 0600, get_uio_info, set_uio_info); static struct attribute *uio_sysfs_attrs[] = { &dev_attr_uio_info.attr, NULL, }; static struct attribute_group uio_attr_group = { .attrs = uio_sysfs_attrs, }; static int uio_probe(struct platform_device *pdev) { struct uio_device *uio_dev; /* uio_device结构体是Linux内核中的一个结构体,用于表示用户空间IO设备 struct uio_device { struct device dev; // 继承自struct device,表示内核中的设备 struct uio_info *info; // 表示uio设备的信息 struct list_head list; // 用于将uio_device结构体连接到uio设备链表中 struct module *owner; // 表示该设备所属的内核模块 int minor; // 表示uio设备的次设备号 struct cdev cdev; // 表示该设备的字符设备描述符 struct class *class; // 表示该设备所属的类别 unsigned int event; // 表示该设备的事件标志 int irq; // 表示该设备的中断号 }; */ int err; void *p; uio_dev = kzalloc(sizeof(struct uio_device), GFP_KERNEL); if (uio_dev == NULL) { return -ENOMEM; } p = kmalloc(MEM_SIZE, GFP_KERNEL); strcpy(p, ""); uio_test.mem[0].name = "uio_mem", uio_test.mem[0].addr = (unsigned long)p; uio_test.mem[0].memtype = UIO_MEM_LOGICAL; uio_test.mem[0].size = MEM_SIZE; uio_dev->info = &uio_test; uio_dev->dev.parent = &pdev->dev; err = uio_register_device(&pdev->dev, uio_dev->info); if (err) { kfree(uio_dev); return err; } if (sysfs_create_group(&pdev->dev.kobj, &uio_attr_group)) { printk(KERN_ERR "Cannot create sysfs for system uio\n"); return err; } //dev_set_drvdata(pdev, uio_dev); return 0; } static int uio_remove(struct platform_device *pdev) { struct uio_device *uio_dev = platform_get_drvdata(pdev); sysfs_remove_group(&uio_dev->dev.kobj, &uio_attr_group); uio_unregister_device(uio_dev->info); //dev_set_drvdata(uio_dev, NULL); kfree(uio_dev); return 0; } static struct platform_device *uio_test_dev; static struct platform_driver uio_driver = { .probe = uio_probe, .remove = uio_remove, .driver = { .name = DRV_NAME, }, }; static int __init uio_init(void) { uio_test_dev = platform_device_register_simple(DRV_NAME, -1, NULL, 0); return platform_driver_register(&uio_driver); } static void __exit uio_exit(void) { platform_device_unregister(uio_test_dev); platform_driver_unregister(&uio_driver); } module_init(uio_init); module_exit(uio_exit); MODULE_AUTHOR("Arron Wu"); MODULE_DESCRIPTION("UIO driver"); MODULE_LICENSE("GPL");
APP实现
#include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <errno.h> #include <string.h> #define UIO_DEV "/dev/uio0" #define UIO_ADDR "/sys/class/uio/uio0/maps/map0/addr" #define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size" static char uio_addr_buf[16], uio_size_buf[16]; int main(void) { int uio_size; void* uio_addr, *access_address; int uio_fd = open(UIO_DEV, O_RDWR); int addr_fd = open(UIO_ADDR, O_RDONLY); int size_fd = open(UIO_SIZE, O_RDONLY); if( addr_fd < 0 || size_fd < 0 || uio_fd < 0) { fprintf(stderr, "mmap: %s\n", strerror(errno)); exit(-1); } read(addr_fd, uio_addr_buf, sizeof(uio_addr_buf)); read(size_fd, uio_size_buf, sizeof(uio_size_buf)); uio_addr = (void*)strtoul(uio_addr_buf, NULL, 0); uio_size = (int)strtol(uio_size_buf, NULL, 0); access_address = mmap(NULL, uio_size, PROT_READ | PROT_WRITE, MAP_SHARED, uio_fd, 0); if ( access_address == (void*) -1) { printf("mmap: %s\n", strerror(errno)); exit(-1); } printf("The device address %p (lenth %d)\n" "logical address %p\n", uio_addr, uio_size, access_address); for(int i = 0; i<6; i++) { printf("%c", ((char *)access_address)[i]); ((char *)access_address)[i] += 1; } printf("\n"); for(int i = 0; i<6; i++) printf("%c", ((char *)access_address)[i]); printf("\n"); munmap(access_address, uio_size); return 0; }
测试
root@cary:~/uio_test# ./app The device address 0xffff94752b93800 (lenth 16) logical address 0x7ff1890bf000 root@cary:~/uio_test# ./app The device address 0xffff94752b93800 (lenth 16) logical address 0x7fa161a08000 root@cary:~/uio_test# ./app The device address 0xffff94752b93800 (lenth 16) logical address 0x7ff34f87b000
UIO驱动的优缺点
优点:
系统资源占用小:UIO驱动是Linux内核模块,运行时只在内核空间中工作,不会占用过多的系统资源。
可移植性高:UIO驱动并不依赖于任何特定的硬件平台,因此它具有很高的可移植性,可以应用于多种不同的硬件平台。
稳定可靠:UIO驱动是通过较为简单的接口与硬件设备进行交互的,因此其代码简单、易于维护,不易出现问题,具有稳定可靠的特点。
缺点:
自由性较低:由于UIO驱动只能与特定的硬件设备进行交互,因此在自由度方面比较低,无法进行太多的自定义化操作。
实现复杂度高:与其他驱动相比,UIO驱动的实现复杂度较高,需要一定的开发技能和经验。
编写难度大:UIO驱动的编写需要掌握较多的底层硬件知识,需要有较高水平的代码开发能力。
UIO在DPDK中的使用
dpdk igb_uio源码如下
/*- * GPL LICENSE SUMMARY * * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * The full GNU General Public License is included in this distribution * in the file called LICENSE.GPL. * * Contact Information: * Intel Corporation */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/device.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/uio_driver.h> #include <linux/io.h> #include <linux/msi.h> #include <linux/version.h> #include <linux/slab.h> #ifdef CONFIG_XEN_DOM0 #include <xen/xen.h> #endif #include <rte_pci_dev_features.h> #include "compat.h" #ifdef RTE_PCI_CONFIG #define PCI_SYS_FILE_BUF_SIZE 10 #define PCI_DEV_CAP_REG 0xA4 #define PCI_DEV_CTRL_REG 0xA8 #define PCI_DEV_CAP_EXT_TAG_MASK 0x20 #define PCI_DEV_CTRL_EXT_TAG_SHIFT 8 #define PCI_DEV_CTRL_EXT_TAG_MASK (1 << PCI_DEV_CTRL_EXT_TAG_SHIFT) #endif / * A structure describing the private information for a uio device. */ struct rte_uio_pci_dev { struct uio_info info; struct pci_dev *pdev; enum rte_intr_mode mode; }; static char *intr_mode = NULL; static enum rte_intr_mode igbuio_intr_mode_preferred = RTE_INTR_MODE_MSIX; static inline struct rte_uio_pci_dev * igbuio_get_uio_pci_dev(struct uio_info *info) { return container_of(info, struct rte_uio_pci_dev, info); } /* sriov sysfs */ static ssize_t show_max_vfs(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, 10, "%u\n", pci_num_vf(container_of(dev, struct pci_dev, dev))); } static ssize_t store_max_vfs(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int err = 0; unsigned long max_vfs; struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); if (0 != kstrtoul(buf, 0, &max_vfs)) return -EINVAL; if (0 == max_vfs) pci_disable_sriov(pdev); else if (0 == pci_num_vf(pdev)) err = pci_enable_sriov(pdev, max_vfs); else /* do nothing if change max_vfs number */ err = -EINVAL; return err ? err : count; } #ifdef RTE_PCI_CONFIG static ssize_t show_extended_tag(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pci_dev = container_of(dev, struct pci_dev, dev); uint32_t val = 0; pci_read_config_dword(pci_dev, PCI_DEV_CAP_REG, &val); if (!(val & PCI_DEV_CAP_EXT_TAG_MASK)) /* Not supported */ return snprintf(buf, PCI_SYS_FILE_BUF_SIZE, "%s\n", "invalid"); val = 0; pci_bus_read_config_dword(pci_dev->bus, pci_dev->devfn, PCI_DEV_CTRL_REG, &val); return snprintf(buf, PCI_SYS_FILE_BUF_SIZE, "%s\n", (val & PCI_DEV_CTRL_EXT_TAG_MASK) ? "on" : "off"); } static ssize_t store_extended_tag(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct pci_dev *pci_dev = container_of(dev, struct pci_dev, dev); uint32_t val = 0, enable; if (strncmp(buf, "on", 2) == 0) enable = 1; else if (strncmp(buf, "off", 3) == 0) enable = 0; else return -EINVAL; pci_cfg_access_lock(pci_dev); pci_bus_read_config_dword(pci_dev->bus, pci_dev->devfn, PCI_DEV_CAP_REG, &val); if (!(val & PCI_DEV_CAP_EXT_TAG_MASK)) { /* Not supported */ pci_cfg_access_unlock(pci_dev); return -EPERM; } val = 0; pci_bus_read_config_dword(pci_dev->bus, pci_dev->devfn, PCI_DEV_CTRL_REG, &val); if (enable) val |= PCI_DEV_CTRL_EXT_TAG_MASK; else val &= ~PCI_DEV_CTRL_EXT_TAG_MASK; pci_bus_write_config_dword(pci_dev->bus, pci_dev->devfn, PCI_DEV_CTRL_REG, val); pci_cfg_access_unlock(pci_dev); return count; } static ssize_t show_max_read_request_size(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pci_dev = container_of(dev, struct pci_dev, dev); int val = pcie_get_readrq(pci_dev); return snprintf(buf, PCI_SYS_FILE_BUF_SIZE, "%d\n", val); } static ssize_t store_max_read_request_size(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct pci_dev *pci_dev = container_of(dev, struct pci_dev, dev); unsigned long size = 0; int ret; if (0 != kstrtoul(buf, 0, &size)) return -EINVAL; ret = pcie_set_readrq(pci_dev, (int)size); if (ret < 0) return ret; return count; } #endif static DEVICE_ATTR(max_vfs, S_IRUGO | S_IWUSR, show_max_vfs, store_max_vfs); #ifdef RTE_PCI_CONFIG static DEVICE_ATTR(extended_tag, S_IRUGO | S_IWUSR, show_extended_tag, store_extended_tag); static DEVICE_ATTR(max_read_request_size, S_IRUGO | S_IWUSR, show_max_read_request_size, store_max_read_request_size); #endif static struct attribute *dev_attrs[] = { &dev_attr_max_vfs.attr, #ifdef RTE_PCI_CONFIG &dev_attr_extended_tag.attr, &dev_attr_max_read_request_size.attr, #endif NULL, }; static const struct attribute_group dev_attr_grp = { .attrs = dev_attrs, }; /* * It masks the msix on/off of generating MSI-X messages. */ static void igbuio_msix_mask_irq(struct msi_desc *desc, int32_t state) { u32 mask_bits = desc->masked; unsigned offset = desc->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL; if (state != 0) mask_bits &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT; else mask_bits |= PCI_MSIX_ENTRY_CTRL_MASKBIT; if (mask_bits != desc->masked) { writel(mask_bits, desc->mask_base + offset); readl(desc->mask_base); desc->masked = mask_bits; } } / * This is the irqcontrol callback to be registered to uio_info. * It can be used to disable/enable interrupt from user space processes. * * @param info * pointer to uio_info. * @param irq_state * state value. 1 to enable interrupt, 0 to disable interrupt. * * @return * - On success, 0. * - On failure, a negative value. */ static int igbuio_pci_irqcontrol(struct uio_info *info, s32 irq_state) { struct rte_uio_pci_dev *udev = igbuio_get_uio_pci_dev(info); struct pci_dev *pdev = udev->pdev; pci_cfg_access_lock(pdev); if (udev->mode == RTE_INTR_MODE_LEGACY) pci_intx(pdev, !!irq_state); else if (udev->mode == RTE_INTR_MODE_MSIX) { struct msi_desc *desc; list_for_each_entry(desc, &pdev->msi_list, list) igbuio_msix_mask_irq(desc, irq_state); } pci_cfg_access_unlock(pdev); return 0; } / * This is interrupt handler which will check if the interrupt is for the right device. * If yes, disable it here and will be enable later. */ static irqreturn_t igbuio_pci_irqhandler(int irq, struct uio_info *info) { struct rte_uio_pci_dev *udev = igbuio_get_uio_pci_dev(info); /* Legacy mode need to mask in hardware */ if (udev->mode == RTE_INTR_MODE_LEGACY && !pci_check_and_mask_intx(udev->pdev)) return IRQ_NONE; /* Message signal mode, no share IRQ and automasked */ return IRQ_HANDLED; } #ifdef CONFIG_XEN_DOM0 static int igbuio_dom0_mmap_phys(struct uio_info *info, struct vm_area_struct *vma) { int idx; idx = (int)vma->vm_pgoff; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); #ifdef HAVE_PTE_MASK_PAGE_IOMAP vma->vm_page_prot.pgprot |= _PAGE_IOMAP; #endif return remap_pfn_range(vma, vma->vm_start, info->mem[idx].addr >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); } / * This is uio device mmap method which will use igbuio mmap for Xen * Dom0 environment. */ static int igbuio_dom0_pci_mmap(struct uio_info *info, struct vm_area_struct *vma) { int idx; if (vma->vm_pgoff >= MAX_UIO_MAPS) return -EINVAL; if (info->mem[vma->vm_pgoff].size == 0) return -EINVAL; idx = (int)vma->vm_pgoff; switch (info->mem[idx].memtype) { case UIO_MEM_PHYS: return igbuio_dom0_mmap_phys(info, vma); case UIO_MEM_LOGICAL: case UIO_MEM_VIRTUAL: default: return -EINVAL; } } #endif /* Remap pci resources described by bar #pci_bar in uio resource n. */ static int igbuio_pci_setup_iomem(struct pci_dev *dev, struct uio_info *info, int n, int pci_bar, const char *name) { unsigned long addr, len; void *internal_addr; if (sizeof(info->mem) / sizeof(info->mem[0]) <= n) return -EINVAL; addr = pci_resource_start(dev, pci_bar); len = pci_resource_len(dev, pci_bar); if (addr == 0 || len == 0) return -1; internal_addr = ioremap(addr, len); if (internal_addr == NULL) return -1; info->mem[n].name = name; info->mem[n].addr = addr; info->mem[n].internal_addr = internal_addr; info->mem[n].size = len; info->mem[n].memtype = UIO_MEM_PHYS; return 0; } /* Get pci port io resources described by bar #pci_bar in uio resource n. */ static int igbuio_pci_setup_ioport(struct pci_dev *dev, struct uio_info *info, int n, int pci_bar, const char *name) { unsigned long addr, len; if (sizeof(info->port) / sizeof(info->port[0]) <= n) return -EINVAL; addr = pci_resource_start(dev, pci_bar); len = pci_resource_len(dev, pci_bar); if (addr == 0 || len == 0) return -EINVAL; info->port[n].name = name; info->port[n].start = addr; info->port[n].size = len; info->port[n].porttype = UIO_PORT_X86; return 0; } /* Unmap previously ioremap'd resources */ static void igbuio_pci_release_iomem(struct uio_info *info) { int i; for (i = 0; i < MAX_UIO_MAPS; i++) { if (info->mem[i].internal_addr) iounmap(info->mem[i].internal_addr); } } static int igbuio_setup_bars(struct pci_dev *dev, struct uio_info *info) { int i, iom, iop, ret; unsigned long flags; static const char *bar_names[PCI_STD_RESOURCE_END + 1] = { "BAR0", "BAR1", "BAR2", "BAR3", "BAR4", "BAR5", }; iom = 0; iop = 0; for (i = 0; i != sizeof(bar_names) / sizeof(bar_names[0]); i++) { if (pci_resource_len(dev, i) != 0 && pci_resource_start(dev, i) != 0) { flags = pci_resource_flags(dev, i); if (flags & IORESOURCE_MEM) { ret = igbuio_pci_setup_iomem(dev, info, iom, i, bar_names[i]); if (ret != 0) return ret; iom++; } else if (flags & IORESOURCE_IO) { ret = igbuio_pci_setup_ioport(dev, info, iop, i, bar_names[i]); if (ret != 0) return ret; iop++; } } } return (iom != 0) ? ret : -ENOENT; } #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; udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL); if (!udev) return -ENOMEM; /* * 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; } /* * reserve device's PCI memory regions for use by this * module */ err = pci_request_regions(dev, "igb_uio"); if (err != 0) { dev_err(&dev->dev, "Cannot request regions\n"); goto fail_disable; } /* 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.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; } err = sysfs_create_group(&dev->dev.kobj, &dev_attr_grp); if (err != 0) goto fail_release_iomem; /* register uio driver */ 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; } static void igbuio_pci_remove(struct pci_dev *dev) { struct uio_info *info = pci_get_drvdata(dev); struct rte_uio_pci_dev *udev = igbuio_get_uio_pci_dev(info); if (info->priv == NULL) { pr_notice("Not igbuio device\n"); return; } sysfs_remove_group(&dev->dev.kobj, &dev_attr_grp); uio_unregister_device(info); igbuio_pci_release_iomem(info); if (udev->mode == RTE_INTR_MODE_MSIX) pci_disable_msix(dev); pci_release_regions(dev); pci_disable_device(dev); pci_set_drvdata(dev, NULL); kfree(info); } static int igbuio_config_intr_mode(char *intr_str) { if (!intr_str) { pr_info("Use MSIX interrupt by default\n"); return 0; } if (!strcmp(intr_str, RTE_INTR_MODE_MSIX_NAME)) { igbuio_intr_mode_preferred = RTE_INTR_MODE_MSIX; pr_info("Use MSIX interrupt\n"); } else if (!strcmp(intr_str, RTE_INTR_MODE_LEGACY_NAME)) { igbuio_intr_mode_preferred = RTE_INTR_MODE_LEGACY; pr_info("Use legacy interrupt\n"); } else { pr_info("Error: bad parameter - %s\n", intr_str); return -EINVAL; } return 0; } static struct pci_driver igbuio_pci_driver = { .name = "igb_uio", .id_table = NULL, .probe = igbuio_pci_probe, .remove = igbuio_pci_remove, }; 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_param(intr_mode, charp, S_IRUGO); MODULE_PARM_DESC(intr_mode, "igb_uio interrupt mode (default=msix):\n" " " RTE_INTR_MODE_MSIX_NAME " Use MSIX interrupt\n" " " RTE_INTR_MODE_LEGACY_NAME " Use Legacy interrupt\n" "\n"); MODULE_DESCRIPTION("UIO driver for Intel IGB PCI cards"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Intel Corporation");
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/129231.html