原文网址:https://lwn.net/Articles/198202
原文作者:corbet
原文时间:2006年9月4日
考虑到性能和可控性,设备驱动程序通常在内核态实现。但用户态设备驱动程序也有自身的考量。例如,内核不太愿意接纳代码量很大的驱动程序,例如X.org图形桌面的项目;另外,有些驱动开发者不愿意接受GPL开源规则。目前,在用户空间实现设备驱动程序还是比较方便的,例如USB驱动;但对于某些设备,在用户态实现驱动还存在一些挑战,例如PCI设备就很少有用户态驱动。
为了方便用户态驱动开发,Thomas Gleixner编写了一套接口模块。这套接口模块代码量很少,且而且是运行在内核态下的。使用这套接口,程序员就很容易开发用户空间的PCI设备驱动。
第一步要注册驱动程序操作的设备,最终会在IIO(industrial I/O)模块框架层初始化该设备对应的iio_device结构体:
struct iio_device {
char *name;
char *version;
unsigned long physaddr; // IO内存区的起始物理地址
void *virtaddr; // IO内存区的内核态起始虚拟地址
// 如果virtaddr为NULL, 那么内核就将这个区理解为IO端口区
unsigned long size; // IO内存区的长度
long irq; // 设备的中断号
irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *regs);//内核态中断处理函数
ssize_t (*event_write)(struct file *filep, const char __user * buf,
size_t count, loff_t *ppos);
struct file_operations *fops; // iio模块提供的文件操作方法,iio_open/read/mmap等
void *priv;
/* ... */
};
通过IIO模块提供的文件操作方法,用户空间设备驱动可以对设备内存进行读/写操作,也可以将设备内存直接映射到用户空间。
上面所说的和/dev/mem的机制没有太大区别。主要的区别在于,内核态PCI驱动部分会使能PCI设备,并做一些必要的初始化工作。用户态PCI设备驱动真正棘手的还是中断的处理。目前还不能在用空间直接实现中断处理函数,IIO补丁也没有真正做到。相反,用户驱动开发者还需要实现一个最精简的内核态中断处理过程。【也就是说,驱动程序分成两部分:一部分是内核态部分,非常精简的;另一部分是用户态部分,也是驱动的主体】
每个设备都要求中断处理对自己的中断进行确认;因此内核必须快速地响应中断,并对中断进行确认,否则在获得确认之前设备就会一直保持中断状态。相对来说,在中断确认之后,中断处理过程中的后续其他操作就显得没有那么紧迫。所以IIO驱动对中断进行了确认,其他工作就可以在用户空间完成。
为了能让驱动能感知到中断的发生,IIO模块提供了两个机制:
1)为设备创建一个对应的事件设备。只要中断发生,用户态驱动程序就可以从事件设备中读到一个字节,否则读操作一直被阻塞。复杂场景可以采用poll()调用方式来支持多个中断。
2)另外,当中断发生时IIO模块向用户驱动程序发送SIGIO信号,用户驱动程序通过SIGIO信号处理来实现。一般说来,信号机制会有比较大的中断响应延迟。
IIO模块驱动需要用最简洁的中断处理函数来初始化iio_device结构体的handler()函数指针;中断一旦发生,这个最简洁的中断处理函数就会调用;一旦这个·处理函数返回IRQ_HANDLER,用户空间就能感知到。基于iio_device结构体中的event_write()函数指针,用户空间驱动程序可以对事件设备进行写操作,这样一来,用户驱动程序可以控制内核对中断的响应,以及对中断的屏蔽。
我们注意到kevent子系统相关代码已经向内核提交了。一旦kevent纳入内核主流代码中,IIO事件处理代码需要基于kevent机制重写。
Thomas还给了一个驱动的案例(参见本文最后),来描述如何使用IIO编写用户驱动程序。很多人心中真正的问题是:ATI和nVidia是否愿意使用IIO的用户态驱动模型。除非这两家厂商自己表态,没人知道他们是否真的愿意。
/**
* $Date: 2006-08-24 15:00:46 +0200 (Do, 24 Aug 2006) $
* $Author: baueh $
* $Rev: 370 $
*
* Sercos kernel stub driver
*
* Copyright (C) 2006 Berger Lahr GmbH
* Author: Thomas Bauer
*
* This code is GPL V2.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "berger.h"
static struct pci_device_id sercos_table[] __devinitdata = {
{ MY_VENDOR_ID, MY_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sercos_table);
/*
* Use the generic file operations
*/
static struct file_operations generic_fops = {
.open = iio_open,
.release = iio_release,
.read = iio_read,
.write = iio_write,
.llseek = iio_lseek,
.mmap = iio_mmap,
};
/*
* The chip specific portion of the interrupt handler. The framework code
* takes care of userspace notification when we return IRQ_HANDLED
*/
static irqreturn_t sercos_handler (int irq, void *dev_id, struct pt_regs *reg)
{
struct iio_device *idev = (struct iio_device *) dev_id;
/* Check, if this interrupt is originated from the SERCOS chip */
if (!(*((u16 *)(idev->virtaddr + SERCOS_INTERRUPT_STATUS)) & SERCOS_INTERRUPT_MASK))
return IRQ_NONE;
/* Acknowledge the chip interrupts */
*((u16 *)(idev->virtaddr + SERCOS_INTERRUPT_ACK1_OFFSET)) = SERCOS_INTERRUPT_ACK1;
*((u16 *)(idev->virtaddr + SERCOS_INTERRUPT_ACK2_OFFSET)) = SERCOS_INTERRUPT_ACK2;
return IRQ_HANDLED;
}
static struct iio_device sercos_idev = {
.name = "SERCON816",
.version = "0x0010",
.fops = &generic_fops,
.handler = sercos_handler,
};
static struct sercos_idevs
{
int sercos;
} sercos_idevs;
static int sercos_register (struct pci_dev *pdev, const struct pci_device_id *ent)
{
if (pci_enable_device(pdev) != 0) {
printk(KERN_ERR "sercos: Cannot enable PCI device\n");
return -ENODEV;
}
/* FIXME: Read this from PCI ! */
sercos_idev.size = SERCOS_SIZE;
sercos_idev.virtaddr = ioremap(pci_resource_start (pdev, 0), sercos_idev.size);
sercos_idev.physaddr = pci_resource_start(pdev, 0);
sercos_idev.irq = SERCOS_IRQ;
sercos_idevs.sercos = iio_register_device(&pdev->dev, &sercos_idev);
printk(KERN_INFO "SERCOS = %d\n",sercos_idevs.sercos);
printk(KERN_INFO "SERCOS registered at Phys. address 0x%lx \n", sercos_idev.physaddr);
printk(KERN_INFO "SERCOS registered at Virt. address 0x%x \n", (unsigned int)
sercos_idev.virtaddr);
printk(KERN_INFO "SERCOS SIZE 0x%lx \n", sercos_idev.size);
pdev->dev.driver_data = &sercos_idevs;
return 0;
}
static void __devexit sercos_unregister (struct pci_dev *pdev)
{
if (sercos_idevs.sercos)
iio_unregister_device(sercos_idevs.sercos);
release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
}
static struct pci_driver sercos_driver = {
.name = "sercos",
.id_table = sercos_table,
.probe = sercos_register,
.remove = __devexit_p(sercos_unregister),
};
static int __init sercos_init (void)
{
return pci_register_driver (&sercos_driver);
}
static void __exit sercos_exit (void)
{
pci_unregister_driver (&sercos_driver);
}
module_init(sercos_init);
module_exit(sercos_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BergerLahr");
MODULE_DESCRIPTION("sercos driver");
/*
* Userspace portion of the SERCOS driver - simplified
*
* iionum - the iio device number to access:
* /dev/iioX
* /dev/iioX_event
* /sys/class/iio/iioX
*/
int sercos_driver(int iionum)
{
int fd;
char fnam[64];
size_t maplen;
uint16_t *sercos;
sprintf(fnam, "/dev/iio%d", iionum);
fd = open(fnam, O_RDWR);
if (fd < 0)
return -EIO;
/* Get map length from /sys/class/iioX/size */
if (iiolib_getmaplen(iionum, &maplen)) {
close(fd);
return -ENODEV;
}
/* Map the chip space */
sercos = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (sercos == MAP_FAILED) {
close(fd);
return -ENODEV;
}
close(fd);
/* Initialize the chip */
sercos_init_chip(sercos);
/*
* Open the event channel. We are the only user, but this could also
* be done by requesting notification by a signal.
*/
sprintf(fnam, "/dev/iio%d_event", iionum);
fd = open(fnam, O_RDONLY);
if (fd < 0) {
sercos_reset_chip(sercos);
munmap(sercos, maplen);
return -EIO;
}
/* interrupt event loop */
while (!stop_sercos) {
char c;
/* Block on the interrupt event */
read(fd, &c, 1);
sercos_handle_interrupt(sercos);
}
sercos_reset_chip(sercos);
munmap(sercos, maplen);
return 0;
}