目录
什么是UIO?
UIO驱动与普通驱动的区别
How UIO works
重要的结构体
UIO驱动源码
APP实现
测试
UIO驱动的优缺点
UIO在DPDK中的使用
什么是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, "123456");
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
123456
234567
root@cary:~/uio_test# ./app
The device address 0xffff94752b93800 (lenth 16)
logical address 0x7fa161a08000
234567
345678
root@cary:~/uio_test# ./app
The device address 0xffff94752b93800 (lenth 16)
logical address 0x7ff34f87b000
345678
456789
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");