Linux UIO驱动

目录

什么是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驱动的开发也需要一定的专业技能和经验,需要了解硬件和操作系统的底层知识。

Linux UIO驱动_第1张图片

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef CONFIG_XEN_DOM0
#include 
#endif
#include 

#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");

你可能感兴趣的:(linux,服务器,驱动开发)