
Linux是Unix *** 作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁, *** 作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的 *** 作要自己来编写,而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿与Linux fans共享
一、Linux device driver 的概念系统调用是 *** 作系统内核和应用程序之间的接口,设备驱动程序是 *** 作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象 *** 作普通文件一样对硬件设备进行 *** 作。设备驱动程序是内核的一部分,它完成以下的功能:
1对设备初始化和释放。
2把数据从内核传送到硬件和从硬件读取数据。
3读取应用程序传送给设备文件的数据和回送应用程序请求的数据。
4检测和处理设备出现的错误。
二、实例剖析我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。
}; //IO功能选项,硬件上拉输出 static unsigned int gpio_cfg_table[] = { S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP, }; //编写一个ioctl函数,这个函数提供给用户端使用(也就是用户态使用) static int my_ioctl(struct inode inode,struct file file,unsigned int cmd, unsigned long arg) { if (arg > 4) { return -EINVAL; } if (cmd == 1) //led ON { s3c2410_gpio_setpin(gpio_table[arg],0); return 0; } if (cmd == 0) //led OFF { s3c2410_gpio_setpin(gpio_table[arg],1); return 0; } else { return -EINVAL; } } //一个和文件设备相关的结构体。 static struct file_operations dev_fops = { owner = THIS_MODULE, ioctl = my_ioctl, //read = my_read, //这个暂时屏蔽,一会我们再加入一个读 *** 作的函数 }; //linux中设备的注册结构体 static struct miscdevice misc =
{ minor = MISC_DYNAMIC_MINOR, name = DEVICE_NAME, fops = &dev_fops, }; //设备初始化(包括注册)函数 static int __init dev_init(void) { int ret; int i; for (i=0;i<4;i++) { s3c2410_gpio_cfgpin(gpio_table[i],gpio_cfg_table[i]); s3c2410_gpio_setpin(gpio_table[i],0); mdelay(500); s3c2410_gpio_setpin(gpio_table[i],1); } ret = misc_register(&misc); printk(DEVICE_NAME"MY_LED_DRIVER init ok\n"); return ret; } //设备注销函数 static void __exit dev_exit(void) { misc_deregister(&misc); } //与模块相关的函数 module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("blogednchinacom/itspy");
MODULE_DESCRIPTION("MY LED DRIVER"); 到此,上面就完成了一个简单的驱动(别急,下面我们再会稍微增加点复杂的东西),以上代码的可以简单概括为:像自己写51单片机或者ARM的裸奔程序一样 *** 作IO函数,然后再linux系统中进行相关必须的函数关联和注册。 为什么要关联呢,为什么注册呢? 因为这是必须的,从以下这些结构体就知道了。 stuct file_operations{ struct module owner; loff_t (llseek) (struct file , loff_t, int); ssize_t (read) (struct file , char __user , size_t, loff_t ); ssize_t (write) (struct file , const char __user , size_t, loff_t ); ssize_t (aio_read) (struct kiocb , const struct iovec , unsigned long, loff_t); ssize_t (aio_write) (struct kiocb , const struct iovec , unsigned long, 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); long (unlocked_ioctl) (struct file , unsigned int, unsigned long); … } file_operations 结构体中包括了很多与设备相关的函数指针,指向了驱动所提供的函数。 struct inode{ struct hlist_node i_hash; struct list_head i_list; struct list_head i_sb_list; struct list_head i_dentry; unsigned long i_ino; atomic_t i_count; unsigned int i_nlink; uid_t i_uid; gid_t i_gid; dev_t i_rdev; u64 i_version; loff_t i_size; … } inode 是 UNIX *** 作系统中的一种数据结构,它包含了与文件系统中各个文件相关的一些重要信息。在 UNIX 中创建文件系统时,同时将会创建大量的 inode 。通常,文件系统磁盘空间中大约百分之一空间分配给了 inode 表。 大略了解以上信息之后,我们只需把我们所要实现的功能和结构体关联起来。上例中已经完成IO写 *** 作的函数,现在我们再添加一个读的函数。基于这种原理,我们想实现各种功能的驱动也就很简单了。 //添加读函数示意, 用户层可以通过 read函数来 *** 作。 static int my_read(struct file fp, char __user dat,size_t cnt) { size_t i; printk("now read the hardware\n"); for(i=0;i<cnt;i++) dat[i] = 'A'; dat[i] = '\0'; return cnt; } 这样,完成驱动编写。编译之后,本驱动可以通过直接嵌入内核中,也可以以模块的嵌入的形式加载到linux内核中去。 完成了驱动,写个应用程序了验证一下吧: int main(int argc,char argv) {
int on; int led_no; int fd; char str[10]; int cnt =0; fd = open("/dev/MY_LED_DRIVER",0); if (fd < 0) { printf("can't open dev\n"); exit(1); } printf("read process\n"); cnt = read(fd,str,10); printf("get data from driver:\n%s\ncount = %d\n",str,cnt); printf("read process end \n"); cnt = 0; printf("running\n"); while(cnt++<1000) { ioctl(fd,0,0); //led off ioctl(fd,0,1); ioctl(fd,0,2); ioctl(fd,0,3); sleep(1); //printf("sdfdsfdsfdsfds\n"); ioctl(fd,1,0); //led on ioctl(fd,1,1); ioctl(fd,1,2); ioctl(fd,1,3); sleep(1); printf("%d\b",cnt); } close(fd); return 0; }
如果只是简单的做驱动,你就先学一下USB方面的协议,然后直接学习WinDriver,这样,你只需要写一次驱动就能得到Nt/2000/9X/linux的驱动,如果要深入的搞,那学完USB方面的协议,还要学DDK的用法,9X下面要学VXD,2000下面要学WDM,linux下面还要重新学一些东西
USB驱动程序基础
在动手写USB驱动程序这前,让我们先看看写的USB驱动程序在内核中的结构,如下图:
USB驱动程序存在于不同的内核子系统和USB硬件控制器之间,USB核心为USB驱动程序提供了一个用于访问和控制USB硬件的接口,而不必考虑系统当前存在的各种不同类型的USB硬件控制器。USB是一个非常复杂的设备,linux内核为我们提供了一个称为USB的核心的子系统来处理大部分的复杂性,USB设备包括配置(configuration)、接口(interface)和端点(endpoint),USB设备绑定到接口上,而不是整个USB设备。如下图所示:
USB通信最基本的形式是通过端点(USB端点分中断、批量、等时、控制四种,每种用途不同),USB端点只能往一个方向传送数据,从主机到设备或者从设备到主机,端点可以看作是单向的管道(pipe)。所以我们可以这样认为:设备通常具有一个或者更多的配置,配置经常具有一个或者更多的接口,接口通常具有一个或者更多的设置,接口没有或具有一个以上的端点。驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否已经安装了硬件。USB核心使用一个列表(是一个包含制造商ID和设备号ID的一个结构体)来判断对于一个设备该使用哪一个驱动程序,热插拨脚本使用它来确定当一个特定的设备插入到系统时该自动装载哪一个驱动程序。
上面我们简要说明了驱动程序的基本理论,在写一个设备驱动程序之前,我们还要了解以下两个概念:模块和设备文件。
模块:是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:直接加载到内核代码中,启动内核时就会驱动此硬件设备。另一种就是以模块方式,编译生成一个ko文件(在24以下内核中是用o作模块文件,我们以26的内核为准,以下同)。当应用程序需要时再加载到内核空间运行。所以我们所说的一个硬件的驱动程序,通常指的就是一个驱动模块。
设备文件:对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设备,一般对应着确定的驱动程序;次设备号一般是区分不同属性,例如不同的使用方法,不同的位置,不同的 *** 作。这个设备号是从/proc/devices文件中获得的,所以一般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一个设备文件时, *** 作系统就已经知道这个设备所对应的驱动程序。对于一个硬件,Linux是这样来进行驱动的:首先,我们必须提供一个ko的驱动模块文件。我们要使用这个驱动程序,首先要加载它,我们可以用insmod
xxxko,这样驱动就会根据自己的类型(字符设备类型或块设备类型,例如鼠标就是字符设备而硬盘就是块设备)向系统注册,注册成功系统会反馈一个主设备号,这个主设备号就是系统对它的唯一标识。驱动就是根据此主设备号来创建一个一般放置在/dev目录下的设备文件。在我们要访问此硬件时,就可以对设备文件通过open、read、write、close等命令进行。而驱动就会接收到相应的read、write *** 作而根据自己的模块中的相应函数进行 *** 作了。
USB驱动程序实践
了解了上述理论后,我们就可以动手写驱动程序,如果你基本功好,而且写过linux下的硬件驱动,USB的硬件驱动和pci_driver很类似,那么写USB的驱动就比较简单了,如果你只是大体了解了linux的硬件驱动,那也不要紧,因为在linux的内核源码中有一个框架程序可以拿来借用一下,这个框架程序在/usr/src/~(你的内核版本,以下同)/drivers/usb下,文件名为usb-skeletonc。写一个USB的驱动程序最基本的要做四件事:驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)(当然也可以不用urb来传输数据,下文我们会说到)。
驱动程序支持的设备:有一个结构体struct
usb_device_id,这个结构体提供了一列不同类型的该驱动程序支持的USB设备,对于一个只控制一个特定的USB设备的驱动程序来说,struct
usb_device_id表被定义为:
/ 驱动程序支持的设备列表 /
static struct usb_device_id
skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID)
},
{ } / 终止入口 /
};
MODULE_DEVICE_TABLE (usb,
skel_table);
对于PC驱动程序,MODULE_DEVICE_TABLE是必需的,而且usb必需为该宏的第一个值,而USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID就是这个特殊设备的制造商和产品的ID了,我们在程序中把定义的值改为我们这款USB的,如:
/
定义制造商和产品的ID号 /
#define USB_SKEL_VENDOR_ID 0x1234
#define
USB_SKEL_PRODUCT_ID
0x2345
这两个值可以通过命令lsusb,当然你得先把USB设备先插到主机上了。或者查看厂商的USB设备的手册也能得到,在我机器上运行lsusb是这样的结果:
Bus
004 Device 001: ID 0000:0000
Bus 003 Device 002: ID 1234:2345 Abc Corp
Bus 002 Device 001: ID 0000:0000
Bus 001 Device 001: ID
0000:0000
得到这两个值后把它定义到程序里就可以了。
注册USB驱动程序:所有的USB驱动程序都必须创建的结构体是struct
usb_driver。这个结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述USB驱动程序。创建一个有效的struct
usb_driver结构体,只须要初始化五个字段就可以了,在框架程序中是这样的:
static struct usb_driver skel_driver
= {
owner = THIS_MODULE,
name = "skeleton",
probe = skel_probe,
disconnect = skel_disconnect,
id_table = skel_table,
};
以上就是关于如何编写Linux 驱动程序全部的内容,包括:如何编写Linux 驱动程序、如何编写Linux的驱动程序、如何学习编写USB驱动程序等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)