用户空间驱动的支持2006

原文网址: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;
}

你可能感兴趣的:(linux)