linux PCIE驱动开发

2018-01-26 10:31:24来源:oschina作者:悲催的古灵武士人点击

分享

linux下PCI驱动源码实例1,该源码缺少pci_fops的初始化 #include
#include
#include
#include
#include
#include
#undef debug// ATTENTION copied from /uboot_for_mpc/arch/powerpc/include/asm/signal.h
// Maybe it don't work with that
//____________________________________________________________
#define SA_INTERRUPT0x20000000 /* dummy -- ignored */
#define SA_SHIRQ 0x04000000
//____________________________________________________________
#define pci_module_init pci_register_driver // function is obsoleted
// Hardware specific part
#define MY_VENDOR_ID 0x5333
#define MY_DEVICE_ID 0x8e40
#define MAJOR_NR 240
#define DRIVER_NAME"PCI-Driver"
static unsigned long ioport=0L, iolen=0L, memstart=0L, memlen=0L,flag0,flag1,flag2,temp=0L;
// private_data
struct _instance_data {
int counter; // just as a example (5-27)
// other instance specific data
};
// Interrupt Service Routine
static irqreturn_t pci_isr( int irq, void *dev_id, struct pt_regs *regs )
{
return IRQ_HANDLED;
}// Check if this driver is for the new device
static int device_init(struct pci_dev *dev,
const struct pci_device_id *id)
{
int err=0;// temp variable
#ifdef debug
flag0=pci_resource_flags(dev, 0 );
flag1=pci_resource_flags(dev, 1 );
flag2=pci_resource_flags(dev, 2 );
printk("DEBUG: FLAGS0 = %u/n",flag0);
printk("DEBUG: FLAGS1 = %u/n",flag1);
printk("DEBUG: FLAGS2 = %u/n",flag2);
/*
* The following sequence checks if the resource is in the
* IO / Storage / Interrupt / DMA address space
* and prints the result in the dmesg log
*/
if(pci_resource_flags(dev,0) & IORESOURCE_IO)
{
// Ressource is in the IO address space
printk("DEBUG: IORESOURCE_IO/n");
}
else if (pci_resource_flags(dev,0) & IORESOURCE_MEM)
{
// Resource is in the Storage address space
printk("DEBUG: IORESOURCE_MEM/n");
}
else if (pci_resource_flags(dev,0) & IORESOURCE_IRQ)
{
// Resource is in the IRQ address space
printk("DEBUG: IORESOURCE_IRQ/n");
}
else if (pci_resource_flags(dev,0) & IORESOURCE_DMA)
{
// Resource is in the DMA address space
printk("DEBUG: IORESOURCE_DMA/n");
}
else
{
printk("DEBUG: NOTHING/n");
}
#endif /* debug */
// allocate memory_region
memstart = pci_resource_start( dev, 0 );
memlen = pci_resource_len( dev, 0 );
if( request_mem_region( memstart, memlen, dev->dev.kobj.name )==NULL ) {
printk(KERN_ERR "Memory address conflict for device /"%s/"/n",
dev->dev.kobj.name);
return -EIO;
}
// allocate a interrupt
if(request_irq(dev->irq,pci_isr,SA_INTERRUPT|SA_SHIRQ,
"pci_drv",dev)) {
printk( KERN_ERR "pci_drv: IRQ %d not free./n", dev->irq );
}
else
{
err=pci_enable_device( dev );
if(err==0)// enable device successful
{
return 0;
}
else // enable device not successful
{
return err;
}
}
// cleanup_mem
release_mem_region( memstart, memlen );
return -EIO;
}
// Function for deinitialization of the device
static void device_deinit( struct pci_dev *pdev )
{
free_irq( pdev->irq, pdev );
if( memstart )
release_mem_region( memstart, memlen );
}
static struct file_operations pci_fops;
static struct pci_device_id pci_drv_tbl[] __devinitdata = {
{MY_VENDOR_ID,// manufacturer identifier
MY_DEVICE_ID,// device identifier
PCI_ANY_ID,// subsystem manufacturer identifier
PCI_ANY_ID,// subsystem device identifier
0,// device class
0,// mask for device class
0 },// driver specific data
{ 0, }
};
static int driver_open( struct inode *geraetedatei, struct file *instance )
{
struct _instance_data *iptr;
iptr = (struct _instance_data *)kmalloc(sizeof(struct _instance_data),
GFP_KERNEL);
if( iptr==0 ) {
printk("not enough kernel mem/n");
return -ENOMEM;
}
/* replace the following line with your instructions*/
iptr->counter= strlen("Hello World/n")+1;// just as a example (5-27)
instance->private_data = (void *)iptr;
return 0;
}
static void driver_close( struct file *instance )
{
if( instance->private_data )
kfree( instance->private_data );
}static struct pci_driver pci_drv = {
.name= "pci_drv",
.id_table= pci_drv_tbl,
.probe= device_init,
.remove= device_deinit,
};
static int __init pci_drv_init(void)
{// register the driver by the OS
if(register_chrdev(MAJOR_NR, DRIVER_NAME, &pci_fops)==0) {
if(pci_module_init(&pci_drv) == 0 ) // register by the subsystem
return 0;
unregister_chrdev(MAJOR_NR,DRIVER_NAME); // unregister if no subsystem support
}
return -EIO;
}
static void __exit pci_drv_exit(void)
{
pci_unregister_driver( &pci_drv );
unregister_chrdev(MAJOR_NR,DRIVER_NAME);
}
module_init(pci_drv_init);
module_exit(pci_drv_exit);
MODULE_LICENSE("GPL"); PCIE同PCI驱动的差异

From a software standpoint, PCI and PCI Express devices are essentially the same. PCIe devices had the same configuration space, BARs, and (usually) support the same PCI INTx interrupts.一般情况下,两者基本保持一致


Example #1: Windows XP has no special knowledge of PCIe, but runs fine on PCIe systems.


Example #2: My company offers both PCI and PCIe versions of a peripheral board, and they use the same Windows/Linux driver package. The driver does not "know" the difference between the two boards.


However: PCIe devices frequently take advantage of "advanced" features, likeMSI, Hotplugging, extended configuration space, etc. Many of these feature existed on legacy PCI, but were unused. If this is a device you are designing, it is up to you whether or not you implement these advanced features.但是pcie在一些高级特性上有优势,比如MSI(Message Signaled Interrupts)、Hotplugging(热插拔)、配置空间扩展等。

linux设备驱动程序框架

Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。


1. 字符设备和块设备


Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。


在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。


所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:



[root@gary root]# mknod/dev/lp0c60


将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。


2. 设备驱动程序接口


Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的:



struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
};


当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。


3. 设备驱动程序模块


Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。


从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。


在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module( )和cleanup_module( ),而且至少要包含两个头文件。在用gcc编译内核模块时,需要加上-DMODULE -D__KERNEL__ -DLINUX这几个参数,编译生成的模块(一般为.o文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module( )。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。


4. 设备驱动程序结构


了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。

驱动程序的注册与注销

向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。


设备的打开与释放

打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工作:



检查设备相关错误,如设备尚未准备好等。
如果是第一次打开,则初始化硬件设备。
识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。
分配和填写要放在file->private_data里的数据结构。
使用计数增1。

释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:



使用计数减1。
释放在file->private_data中分配的内存。
如果使用计算为0,则关闭设备。设备的读写操作

字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。


设备的控制操作

除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。


设备的中断和轮询处理

对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

三、PCI驱动程序实现


1. 关键数据结构


PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。


Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。


在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:

pci_driver

这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( ):


struct pci_driver {
struct list_head node;
char *name;
const struct pci_device_id *id_table;
int(*probe)(struct pci_dev *dev, const struct pci_device_id *id);
void (*remove) (struct pci_dev *dev);
int(*save_state) (struct pci_dev *dev, u32 state);
int(*suspend)(struct pci_dev *dev, u32 state);
int(*resume) (struct pci_dev *dev);
int(*enable_wake) (struct pci_dev *dev, u32 state, int enable);
};
pci_dev

这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:


struct pci_dev {
struct list_head global_list;
struct list_head bus_list;
struct pci_bus*bus;
struct pci_bus*subordinate;
void *sysdata;
struct proc_dir_entry *procent;
unsigned intdevfn;
unsigned shortvendor;
unsigned shortdevice;
unsigned shortsubsystem_vendor;
unsigned shortsubsystem_device;
unsigned intclass;
u8hdr_type;
u8rom_base_reg;
struct pci_driver *driver;
void *driver_data;
u64 dma_mask;
u32current_state;
unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
unsigned intirq;
struct resource resource[DEVICE_COUNT_RESOURCE];
struct resource dma_resource[DEVICE_COUNT_DMA];
struct resource irq_resource[DEVICE_COUNT_IRQ];
char name[80];
char slot_name[8];
int active;
int ro;
unsigned shortregs;
int (*prepare)(struct pci_dev *dev);
int (*activate)(struct pci_dev *dev);
int (*deactivate)(struct pci_dev *dev);
};

2. 基本框架


在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。



/* 指明该驱动程序适用于哪一些PCI设备 */
static struct pci_device_id demo_pci_tbl [] __initdata = {
{PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},
{0,}
};
/* 对特定PCI设备进行描述的数据结构 */
struct demo_card {
unsigned int magic;
/* 使用链表保存所有同类的PCI设备 */
struct demo_card *next;/* ... */
}
/* 中断处理模块 */
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* ... */
}
/* 设备文件操作接口 */
static struct file_operations demo_fops = {
owner:THIS_MODULE, /* demo_fops所属的设备模块 */
read:demo_read,/* 读设备操作*/
write:demo_write,/* 写设备操作*/
ioctl:demo_ioctl,/* 控制设备操作*/
mmap:demo_mmap,/* 内存重映射操作*/
open:demo_open,/* 打开设备操作*/
release:demo_release/* 释放设备操作*/
/* ... */
};
/* 设备模块信息 */
static struct pci_driver demo_pci_driver = {
name:demo_MODULE_NAME,/* 设备模块名称 */
id_table: demo_pci_tbl,/* 能够驱动的设备列表 */
probe:demo_probe,/* 查找并初始化设备 */
remove: demo_remove/* 卸载设备模块 */
/* ... */
};
static int __init demo_init_module (void)
{
/* ... */
}
static void __exit demo_cleanup_module (void)
{
pci_unregister_driver(&demo_pci_driver);
}
/* 加载驱动程序模块入口 */
module_init(demo_init_module);
/* 卸载驱动程序模块入口 */
module_exit(demo_cleanup_module);


上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。


3. 初始化设备模块


在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:

检查PCI总线是否被Linux内核支持;
检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。
读出配置头中的信息提供给驱动程序使用。

当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:



static int __init demo_init_module (void)
{
/* 检查系统是否支持PCI总线 */
if (!pci_present())
return -ENODEV;
/* 注册硬件驱动程序 */
if (!pci_register_driver(&demo_pci_driver)) {
pci_unregister_driver(&demo_pci_driver);
return -ENODEV;
}
/* ... */

return 0;
}


驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了。在2.4以前的内核中,需要手工调用pci_find_device( )函数来查找PCI设备,但在2.4以后更好的办法是调用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工作。



static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
struct demo_card *card;
/* 启动PCI设备 */
if (pci_enable_device(pci_dev))
return -EIO;
/* 设备DMA标识 */
if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {
return -ENODEV;
}
/* 在内核空间中动态申请内存 */
if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {
printk(KERN_ERR "pci_demo: out of memory/n");
return -ENOMEM;
}
memset(card, 0, sizeof(*card));
/* 读取PCI配置信息 */
card->iobase = pci_resource_start (pci_dev, 1);
card->pci_dev = pci_dev;
card->pci_id = pci_id->device;
card->irq = pci_dev->irq;
card->next = devs;
card->magic = DEMO_CARD_MAGIC;
/* 设置成总线主DMA模式 */
pci_set_master(pci_dev);
/* 申请I/O资源 */
request_region(card->iobase, 64, card_names[pci_id->driver_data]);
return 0;
}


4. 打开设备模块


在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。



static int demo_open(struct inode *inode, struct file *file)
{
/* 申请中断,注册中断处理程序 */
request_irq(card->irq, &demo_interrupt, SA_SHIRQ,
card_names[pci_id->driver_data], card)) {
/* 检查读写模式 */
if(file->f_mode & FMODE_READ) {
/* ... */
}
if(file->f_mode & FMODE_WRITE) {
/* ... */
}/* 申请对设备的控制权 */
down(&card->open_sem);
while(card->open_mode & file->f_mode) {
if (file->f_flags & O_NONBLOCK) {
/* NONBLOCK模式,返回-EBUSY */
up(&card->open_sem);
return -EBUSY;
} else {
/* 等待调度,获得控制权 */
card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);
up(&card->open_sem);
/* 设备打开计数增1 */
MOD_INC_USE_COUNT;
/* ... */
}
}
}


5. 数据读写和控制信息模块


PCI设备驱动程序可以通过demo_fops 结构中的函数demo_ioctl( ),向应用程序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个数据,并传送到用户空间里:



static int demo_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
/* ... */switch(cmd) {
case DEMO_RDATA:
/* 从I/O端口读取4字节的数据 */
val = inl(card->iobae + 0x10);

/* 将读取的数据传输到用户空间 */
return 0;
}/* ... */
}


事实上,在demo_fops里还可以实现诸如demo_read( )、demo_mmap( )等操作,Linux内核源码中的driver目录里提供了许多设备驱动程序的源代码,找那里可以找到类似的例子。在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过DMA传送到系统内存中。


6. 中断处理模块


PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。



static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct demo_card *card = (struct demo_card *)dev_id;
u32 status;
spin_lock(&card->lock);
/* 识别中断 */
status = inl(card->iobase + GLOB_STA);
if(!(status & INT_MASK))
{
spin_unlock(&card->lock);
return;/* not for us */
}
/* 告诉设备已经收到中断 */
outl(status & INT_MASK, card->iobase + GLOB_STA);
spin_unlock(&card->lock);/* 其它进一步的处理,如更新DMA缓冲区指针等 */
}


7. 释放设备模块


释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:



static int demo_release(struct inode *inode, struct file *file)
{
/* ... *//* 释放对设备的控制权 */
card->open_mode &= (FMODE_READ | FMODE_WRITE);/* 唤醒其它等待获取控制权的进程 */
wake_up(&card->open_wait);
up(&card->open_sem);/* 释放中断 */
free_irq(card->irq, card);/* 设备打开计数增1 */
MOD_DEC_USE_COUNT;/* ... */
}


8. 卸载设备模块


卸载设备模块与初始化设备模块是相对应的,实现起来相对比较简单,主要是调用函数pci_unregister_driver( )从Linux内核中注销设备驱动程序:



static void __exit demo_cleanup_module (void)
{
pci_unregister_driver(&demo_pci_driver);
}


如何将驱动程序编译后加载进内核

(1)编写Makefile文件


makefile文件实例


ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
#generate the path
CURRENT_PATH:=$(shell pwd)
#the absolute path
LINUX_KERNEL_PATH:=/lib/modules/$(shell uname -r)/build
#complie object
default:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
endif

obj-m 表示该文件要作为模块编译 obj-y则表示该文件要编译进内核


正常情况下只需修改hello.o即可


(2)执行make命令生成 *.ko 文件


(3)sudo insmod *.ko加载驱动模块


(4)sudo rmmod *.ko卸载驱动模块


(5)使用dmesg | tail -10来查看内核输出的最后十条信息


(6)使用modinfo *.ko来查看模块信息

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台