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

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

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基本信息 原书名: Essential Linux Device Drivers 原出版社: Prentice Hall 作者: (印)Sreekrishnan Venkateswaran 译者: 宋宝华 何昭然 史海滨 吴国成 丛书名: 图灵程序设计丛书 操作系统 出版社:人民邮电出版社 ISBN:9787115221674 出版日期:2010 年6月 页码:468 内容简介   本书是linux设备驱动程序开发领域的权威著作。全书基于2.6内核,不仅透彻讲解了基本概念和技术,更深入探讨了其他书没有涵盖或浅尝辄止的许多重要主题和关键难点,如pcmcia、i2c和usb等外部总线以及视频、音频、无线连网和闪存等驱动程序的开发,并讲解了相关的内核源码文件,给出了完整的开发实例。   本书适合中高级linux开发人员阅读。 目录 第1章 引言1 1.1 演进1 1.2 gnu copyleft2 1.3 kernel.org2 1.4 邮件列表和论坛3 1.5 linux发行版3 1.6 查看源代码4 1.7 编译内核7 1.8 可加载的模块8 1.9 整装待发9 第2章 内核11 2.1 启动过程11 2.1.1 bios-provided physical ram map12 2.1.2 758mb lowmem available14 2.1.3 kernel command line: ro root=/dev/hda114 2.1.4 calibrating delay...1197.46 .bogomips (lpj=2394935)15 2.1.5 checking hlt instruction16 2.1.6 net: registered protocol family 217 2.1.7 freeing initrd memory: 387k freed17 2.1.8 io scheduler anticipatory registered (default)18 2.1.9 setting up standard pci resources18 2.1.10 ext3-fs: mounted filesystem19 2.1.11 init: version 2.85 booting19 2.2 内核模式和用户模式20 2.3 进程上下文和中断上下文20 2.4 内核定时器21 2.4.1 hz和jiffies21 2.4.2 长延时22 2.4.3 短延时24 2.4.4 pentium时间戳计数器24 2.4.5 实时钟25 2.5 内核中的并发26 2.5.1 自旋锁和互斥体26 2.5.2 原子操作30 2.5.3 读—写锁31 2.5.4 调试32 2.6 proc文件系统32 2.7 内存分配33 2.8 查看源代码34 第3章 内核组件37 3.1 内核线程37 3.1.1 创建内核线程37 3.1.2 进程状态和等待队列41 3.1.3 用户模式辅助程序42 3.2 辅助接口43 3.2.1 链表44 3.2.2 散列链表49 3.2.3 工作队列49 3.2.4 通知链51 3.2.5 完成接口54 3.2.6 kthread辅助接口56 3.2.7 错误处理助手57 3.3 查看源代码58 第4章 基本概念61 4.1 设备和驱动程序介绍61 4.2 中断处理63 4.2.1 中断上下文63 4.2.2 分配irq号64 4.2.3 设备实例:导航杆65 4.2.4 softirq和tasklet68 4.3 linux设备模型71 4.3.1 udev71 4.3.2 sysfs、kobject和设备类73 4.3.3 热插拔和冷插拔76 4.3.4 微码下载76 4.3.5 模块自动加载77 4.4 内存屏障78 4.5 电源管理79 4.6 查看源代码79 第5章 字符设备驱动程序81 5.1 字符设备驱动程序基础81 5.2 设备实例:系统cmos82 5.2.1 驱动程序初始化83 5.2.2 打开与释放86 5.2.3 数据交换88 5.2.4 查找92 5.2.5 控制94 5.3 检测数据可用性95 5.3.1 轮询95 5.3.2 fasync98 5.4 和并行端口交互99 5.5 rtc子系统108 5.6 伪字符驱动程序109 5.7 混杂驱动程序110 5.8 字符设备驱动程序注意事项115 5.9 查看源代码115 第6章 串行设备驱动程序118 6.1 层次架构119 6.2 uart驱动程序121 6.2.1 设备实例:手机122 6.2.2 rs-485132 6.3 tty驱动程序132 6.4 线路规程134 6.5 查看源代码141 第7章 输入设备驱动程序143 7.1 输入事件驱动程序144 7.2 输入设备驱动程序150 7.2.1 serio150 7.2.2 键盘150 7.2.3 鼠标152 7.2.4 触摸控制器157 7.2.5 加速度传感器158 7.2.6 输出事件158 7.3 调试159 7.4 查看源代码160 第8章 i2c协议161 8.1 i2c/smbus是什么161 8.2 i2c核心162 8.3 总线事务164 8.4 设备实例:eeprom164 8.4.1 初始化165 8.4.2 探测设备167 8.4.3 检查适配器的功能169 8.4.4 访问设备169 8.4.5 其他函数170 8.5 设备实例:实时时钟171 8.6 i2c-dev174 8.7 使用lm-sensors监控硬件174 8.8 spi总线174 8.9 1-wire总线176 8.10 调试176 8.11 查看源代码176 第9章 pcmcia和cf179 9.1 pcmcia/cf是什么179 9.2 linux-pcmcia子系统181 9.3 主机控制器驱动程序183 9.4 pcmcia核心183 9.5 驱动程序服务183 9.6 客户驱动程序183 9.6.1 数据结构184 9.6.2 设备实例:pcmcia卡185 9.7 将零件组装在一起188 9.8 pcmcia存储189 9.9 串行pcmcia189 9.10 调试191 9.11 查看源代码191 第10章 pci193 10.1 pci系列193 10.2 寻址和识别195 10.3 访问pci198 10.3.1 配置区198 10.3.2 i/o和内存199 10.4 dma200 10.5 设备实例:以太网—调制解调器卡203 10.5.1 初始化和探测203 10.5.2 数据传输209 10.6 调试214 10.7 查看源代码214 第11章 usb216 11.1 usb体系架构216 11.1.1 总线速度218 11.1.2 主机控制器218 11.1.3 传输模式219 11.1.4 寻址219 11.2 linux-usb子系统220 11.3 驱动程序的数据结构221 11.3.1 usb_device结构体221 11.3.2 urb222 11.3.3 管道223 11.3.4 描述符结构223 11.4 枚举225 11.5 设备实例:遥测卡225 11.5.1 初始化和探测过程226 11.5.2 卡寄存器的访问230 11.5.3 数据传输233 11.6 类驱动程序236 11.6.1 大容量存储设备236 11.6.2 usb-串行端口转换器241 11.6.3 人机接口设备243 11.6.4 蓝牙243 11.7 gadget驱动程序243 11.8 调试244 11.9 查看源代码245 第12章 视频驱动程序247 12.1 显示架构247 12.2 linux视频子系统249 12.3 显示参数251 12.4 帧缓冲api252 12.5 帧缓冲驱动程序254 12.6 控制台驱动程序265 12.6.1 设备实例:手机266 12.6.2 启动logo270 12.7 调试270 12.8 查看源代码271 第13章 音频驱动程序273 13.1 音频架构273 13.2 linux声音子系统275 13.3 设备实例:mp3播放器277 13.3.1 驱动程序函数和结构体278 13.3.2 alsa编程287 13.4 调试288 13.5 查看源代码289 第14章 块设备驱动程序291 14.1 存储技术291 14.2 linux块i/o层295 14.3 i/o调度器295 14.4 块驱动程序数据结构和方法296 14.5 设备实例:简单存储控制器298 14.5.1 初始化299 14.5.2 块设备操作301 14.5.3 磁盘访问302 14.6 高级主题304 14.7 调试306 14.8 查看源代码306 第15章 网络接口卡308 15.1 驱动程序数据结构308 15.1.1 套接字缓冲区309 15.1.2 网络设备接口310 15.1.3 激活311 15.1.4 数据传输311 15.1.5 看门狗311 15.1.6 统计312 15.1.7 配置313 15.1.8 总线相关内容314 15.2 与协议层会话314 15.2.1 接收路径314 15.2.2 发送路径315 15.2.3 流量控制315 15.3 缓冲区管理和并发控制315 15.4 设备实例:以太网nic316 15.5 isa网络驱动程序321 15.6 atm321 15.7 网络吞吐量322 15.7.1 驱动程序性能322 15.7.2 协议性能323 15.8 查看源代码324 第16章 linux无线设备驱动326 16.1 蓝牙327 16.1.1 bluez328 16.1.2 设备实例:cf卡329 16.1.3 设备实例:usb适配器330 16.1.4 rfcomm331 16.1.5 网络332 16.1.6 hid334 16.1.7 音频334 16.1.8 调试334 16.1.9 关于源代码334 16.2 红外335 16.2.1 linux-irda335 16.2.2 设备实例:超级i/o芯片337 16.2.3 设备实例:ir dongle338 16.2.4 ircomm340 16.2.5 联网340 16.2.6 irda套接字341 16.2.7 lirc341 16.2.8 查看源代码342 16.3 wifi343 16.3.1 配置343 16.3.2 设备驱动程序346 16.3.3 查看源代码347 16.4 蜂窝网络347 16.4.1 gprs347 16.4.2 cdma349 16.5 当前趋势350 第17章 存储技术设备352 17.1 什么是闪存352 17.2 linux-mtd子系统353 17.3 映射驱动程序353 17.4 nor芯片驱动程序358 17.5 nand芯片驱动程序359 17.6 用户模块361 17.6.1 块设备模拟361 17.6.2 字符设备模拟361 17.6.3 jffs2362 17.6.4 yaffs2363 17.7 mtd工具363 17.8 配置mtd363 17.9 xip364 17.10 fwh364 17.11 调试367 17.12 查看源代码367 第18章 嵌入式linux369 18.1 挑战369 18.2 元器件选择370 18.3 工具链371 18.4 bootloader372 18.5 内存布局374 18.6 内核移植375 18.7 嵌入式驱动程序376 18.7.1 闪存377 18.7.2 uart377 18.7.3 按钮和滚轮378 18.7.4 pcmcia/cf378 18.7.5 sd/mmc378 18.7.6 usb378 18.7.7 rtc378 18.7.8 音频378 18.7.9 触摸屏379 18.7.10 视频379 18.7.11 cpld/fpga379 18.7.12 连接性379 18.7.13 专用领域电子器件380 18.7.14 更多驱动程序380 18.8 根文件系统380 18.8.1 nfs挂载的根文件系统381 18.8.2 紧凑型中间件382 18.9 测试基础设施383 18.10 调试383 18.10.1 电路板返工384 18.10.2 调试器385 第19章 用户空间的驱动程序386 19.1 进程调度和响应时间387 19.1.1 原先的调度器387 19.1.2 o(1)调度器387 19.1.3 cfs388 19.1.4 响应时间388 19.2 访问i/o区域390 19.3 访问内存区域393 19.4 用户模式scsi395 19.5 用户模式usb397 19.6 用户模式i2c400 19.7 uio401 19.8 查看源代码402 第20章 其他设备和驱动程序403 20.1 ecc报告403 20.2 频率调整407 20.3 嵌入式控制器408 20.4 acpi408 20.5 isa与mca410 20.6 火线410 20.7 智能输入/输出411 20.8 业余无线电411 20.9 voip411 20.10 高速互联412 20.10.1 infiniband413 20.10.2 rapidio413 20.10.3 光纤通道413 20.10.4 iscsi413 第21章 调试设备驱动程序414 21.1 kdb414 21.1.1 进入调试器415 21.1.2 kdb415 21.1.3 kgdb417 21.1.4 gdb420 21.1.5 jtag调试器421 21.1.6 下载423 21.2 内核探测器423 21.2.1 kprobe423 21.2.2 jprobe427 21.2.3 返回探针429 21.2.4 局限性431 21.2.5 查看源代码431 21.3 kexec与kdump431 21.3.1 kexec432 21.3.2 kdump与kexec协同工作432 21.3.3 kdump433 21.3.4 查看源代码437 21.4 性能剖析437 21.4.1 利用oprofile剖析内核性能438 21.4.2 利用gprof剖析应用程序性能440 21.5 跟踪441 21.6 ltp444 21.7 uml444 21.8 诊断工具444 21.9 内核修改配置选项444 21.10 测试设备445 第22章 维护与发布446 22.1 代码风格446 22.2 修改标记446 22.3 版本控制447 22.4 一致性检查447 22.5 构建脚本448 22.6 可移植代码450 第23章 结束语451 23.1 流程一览表451 23.2 下一步该做什么452 附录a linux汇编453 附录b linux与bios457 附录c seq文件461

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值