本文目录
- 什么是阻塞操作
- 如何编写网卡驱动程序
- usb的驱动应该怎么写
- arm linux驱动程序和应用程序之间的一些问题
- 瑞昱RTL 8187L无线网卡linux驱动
- USB驱动的组成有哪些
- 如何创建用于WindowsNT PCI 设备驱动程序
- 如何编写Linux的驱动程序
- 网络设备初始化主要流程是
- 怎么能将linux下自动分配的usb0改为usb1
什么是阻塞操作
阻塞操作
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。非阻塞操作的进程在不能进行设备操作时,并不挂起。被挂起的进程进入sleep状态,被从调度器的运行队列移走,直到等待的条件被满足。
在Linux驱动程序中,我们可以使用等待队列(wait queue)来实现阻塞操作。wait queue很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。等待队列可以用来同步对系统资源的访问。
下面定义设备“globalvar“,它可以被多个进程打开,但是每次只有当一个进程写入了一个数据之后本进程或其它进程才可以读取该数据,否则一直阻塞。
#include 《linux/module.h》 #include 《linux/init.h》 #include 《linux/fs.h》 #include 《asm/uaccess.h》 #include 《linux/wait.h》 #include 《asm/semaphore.h》 MODULE_LICENSE(“GPL“);#define MAJOR_NUM 254static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);struct file_operations globalvar_fops ={ read: globalvar_read, write: globalvar_write,};static int global_var = 0;static struct semaphore sem;static wait_queue_head_t outq;static int flag = 0;static int __init globalvar_init(void){ int ret; ret = register_chrdev(MAJOR_NUM, “globalvar“, &globalvar_fops); if (ret) { printk(“globalvar register failure“); } else { printk(“globalvar register success“); init_MUTEX(&sem); init_waitqueue_head(&outq); } return ret;}static void __exit globalvar_exit(void){ int ret; ret = unregister_chrdev(MAJOR_NUM, “globalvar“); if (ret) { printk(“globalvar unregister failure“); } else { printk(“globalvar unregister success“); }}static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ //等待数据可获得 if (wait_event_interruptible(outq, flag != 0)) { return - ERESTARTSYS; } if (down_interruptible(&sem)) { return - ERESTARTSYS; } flag = 0; if (copy_to_user(buf, &global_var, sizeof(int))) { up(&sem); return - EFAULT; } up(&sem); return sizeof(int);} static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off) { if (down_interruptible(&sem)) { return - ERESTARTSYS; } if (copy_from_user(&global_var, buf, sizeof(int))) { up(&sem); return - EFAULT; } up(&sem); flag = 1; //通知数据可获得 wake_up_interruptible(&outq); return sizeof(int);} module_init(globalvar_init);module_exit(globalvar_exit);
编写两个用户态的程序来测试,第一个用于阻塞地读/dev/globalvar,另一个用于写/dev/globalvar。只有当后一个对/dev/globalvar进行了输入之后,前者的read才能返回。
读的程序为:
#include 《sys/types.h》#include 《sys/stat.h》#include 《stdio.h》#include 《fcntl.h》main(){ int fd, num; fd = open(“/dev/globalvar“, O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) {read(fd, #, sizeof(int));//程序将阻塞在此语句,除非有针对globalvar的输入 printf(“The globalvar is %d\n“, num); //如果输入是0,则退出 if (num == 0) { close(fd); break; } } } else { printf(“device open failure\n“); }}
写的程序为:
#include 《sys/types.h》#include 《sys/stat.h》#include 《stdio.h》#include 《fcntl.h》main(){ int fd, num; fd = open(“/dev/globalvar“, O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { printf(“Please input the globalvar:\n“); scanf(“%d“, #); write(fd, #, sizeof(int)); //如果输入0,退出 if (num == 0) { close(fd); break; } } } else { printf(“device open failure\n“); } }
打开两个终端,分别运行上述两个应用程序,发现当在第二个终端中没有输入数据时,第一个终端没有输出(阻塞),每当我们在第二个终端中给globalvar输入一个值,第一个终端就会输出这个值。
关于上述例程,我们补充说一点,如果将驱动程序中的read函数改为:
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off){ //获取信号量:可能阻塞 if (down_interruptible(&sem)) { return - ERESTARTSYS; } //等待数据可获得:可能阻塞 if (wait_event_interruptible(outq, flag != 0)) { return - ERESTARTSYS; } flag = 0; //临界资源访问 if (copy_to_user(buf, &global_var, sizeof(int))) { up(&sem); return - EFAULT; } //释放信号量 up(&sem); return sizeof(int);}
即交换wait_event_interruptible(outq, flag != 0)和down_interruptible(&sem)的顺序,这个驱动程序将变得不可运行。实际上,当两个可能要阻塞的事件同时出现时,即两个wait_event或down摆在一起的时候,将变得非常危险,死锁的可能性很大,这个时候我们要特别留意它们的出现顺序。当然,我们应该尽可能地避免这种情况的发生!
还有一个与设备阻塞与非阻塞访问息息相关的论题,即select和poll, select和 poll的本质一样,前者在BSD Unix中引入,后者在System V中引入。poll和select用于查询设备的状态,以便用户程序获知是否能对设备进行非阻塞的访问,它们都需要设备驱动程序中的poll函数支持。
驱动程序中poll函数中最主要用到的一个API是poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait函数所做的工作是把当前进程添加到wait参数指定的等待列表(poll_table)中。下面我们给globalvar的驱动添加一个poll函数:
static unsigned int globalvar_poll(struct file *filp, poll_table *wait){ unsigned int mask = 0; poll_wait(filp, &outq, wait); //数据是否可获得? if (flag != 0) { mask |= POLLIN | POLLRDNORM; //标示数据可获得 } return mask;}
需要说明的是,poll_wait函数并不阻塞,程序中poll_wait (filp, &outq, wait)这句话的意思并不是说一直等待outq信号量可获得,真正的阻塞动作是上层的select/poll函数中完成的。select/poll会在一个循环中对每个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪,则内核进行调度(调用schedule)让出cpu进入阻塞状态,schedule返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪, select/poll都立即返回。
我们编写一个用户态应用程序来测试改写后的驱动。程序中要用到BSD Unix中引入的select函数,其原型为:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
其中readfds、writefds、exceptfds分别是被select ()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1。timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval数据结构为:
struct timeval { int tv_sec; int tv_usec; };
除此之外,我们还将使用下列API:
FD_ZERO(fd_set *set)――清除一个文件描述符集;
FD_SET(int fd,fd_set *set)――将一个文件描述符加入文件描述符集中FD_CLR(int fd,fd_set *set)――将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)――判断文件描述符是否被置位。
下面的用户态测试程序等待/dev/globalvar可读,但是设置了5秒的等待超时,若超过5秒仍然没有数据可读,则输出“No data within 5 seconds“:
#include 《sys/types.h》 #include 《sys/stat.h》 #include 《stdio.h》 #include 《fcntl.h》 #include 《sys/time.h》 #include 《sys/types.h》#include 《unistd.h》main(){ int fd, num; fd_set rfds; struct timeval tv; fd = open(“/dev/globalvar“, O_RDWR, S_IRUSR | S_IWUSR); if (fd != - 1) { while (1) { //查看globalvar是否有输入 FD_ZERO(&rfds); FD_SET(fd, &rfds); //设置超时时间为5s tv.tv_sec = 5; tv.tv_usec = 0; select(fd + 1, &rfds, NULL, NULL, &tv); //数据是否可获得? if (FD_ISSET(fd, &rfds)) { read(fd, #, sizeof(int)); printf(“The globalvar is %d\n“, num); //输入为0,退出 if (num == 0) { close(fd); break; } } else printf(“No data within 5 seconds.\n“); } } else { printf(“device open failure\n“); } }
开两个终端,分别运行程序:一个对globalvar进行写,一个用上述程序对 globalvar进行读。当我们在写终端给globalvar输入一个值后,读终端立即就能输出该值,当我们连续5秒没有输入时,“No data within 5 seconds“在读终端被输出。
如何编写网卡驱动程序
实现方法和步骤如下:
1、如果网络设备(包括wireless)是PCI规范的,则先是向内核注册该PCI设备(pci_register_driver),然后由pci_driver数据结构中的probe函数指针所指向的侦测函数来初始化该PCI设备,并且同时注册和初始化该网络设备。-module_device_table
申明为PCI设备:
static struct pci_driver tg3_driver = {
.name = DRV_MODULE_NAME,
.id_table = tg3_pci_tbl, //此驱动所支持的网卡系列,vendor_id, device_id
.probe = tg3_init_one, //初始化网络设备的回调函数
.remove = __devexit_p(tg3_remove_one), //注销网络设备的回调函数
.suspend = tg3_suspend, //设备挂起函数
.resume = tg3_resume //设备恢复函数
};
PCI设备探测函数probe,初始化网络设备:-module_device_table
static int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
{
//初始化设备,使I/O,memory可用,唤醒设备
pci_enable_device(pdev);
//申请内存空间,配置网卡的I/O,memory资源
pci_request_regions(pdev, DRV_MODULE_NAME);
pci_set_master(pdev);
//设置DMA属性
pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);
//网卡 I/O,memory资源的启始地址
tg3reg_base = pci_resource_start(pdev, 0);
//网卡I/O,memory资源的大小
tg3reg_len = pci_resource_len(pdev, 0);
//分配并设置网络设备
dev = alloc_etherdev(sizeof(*tp));
//申明为内核设备模块
SET_MODULE_OWNER(dev);
//初始化私有结构中的各成员值
tp = dev-》priv;
tp-》pdev = pdev;
tp-》dev = dev;
……
//锁的初始化
spin_lock_init(&tp-》lock);
//映射I/O,memory地址到私有域中的寄存器结构
tp-》regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);
dev-》irq = pdev-》irq;
//网络设备回调函数赋值
dev-》open = tg3_open;
dev-》stop = tg3_close;
dev-》get_stats = tg3_get_stats;
dev-》set_multicast_list = tg3_set_rx_mode;
dev-》set_mac_address = tg3_set_mac_addr;
dev-》do_ioctl = tg3_ioctl;
dev-》tx_timeout = tg3_tx_timeout;
dev-》hard_start_xmit= tg3_start_xmit;
//网卡的MAC地址赋值dev-》addr
tg3_get_device_address(tp);
//注册网络设备
register_netdev(dev);
//把网络设备指针地址放入PCI设备中的设备指针中
pci_set_drvdata(pdev, dev);
}
打开网络设备:-module_device_table
/* int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要申请的硬件中断号。在Intel平台,范围0--15。
handler是向系统登记的中断处理函数。
这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。
dev_id就是下面的request_irq时传递给系统的参数dev_id。
irqflags是中断处理的一些属性。比较重要的有 SA_INTERRUPT,
标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。
快速 处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。
还有一个SA_SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。
一般设置为这个设备的device结构本身或者NULL。
中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用rq2dev_map找到 中断对应的设备。
*/
static int tg3_open(struct net_device *dev)
{
//分配一个中断
request_irq(dev-》irq, tg3_interrupt, SA_SHIRQ, dev-》name, dev);
//初始化硬件
tg3_init_hw(tp);
//初始化收包和发包的缓冲区
tg3_init_rings(tp);
//初始化定时器
init_timer(&tp-》timer);
tp-》timer.expires = jiffies + tp-》timer_offset;
tp-》timer.data = (unsigned long) tp;
tp-》timer.function = tg3_timer; //超时回调函数
add_timer(&tp-》timer);
//允许网卡开始传输包
netif_start_queue(dev);
}
-module_device_table
usb的驱动应该怎么写
linux下usb驱动编写(内核2.4)——2.6与此接口有区别2006-09-15 14:57我们知道了在Linux下如何去使用一些最常见的USB设备。但对于做系统设计的程序员来说,这是远远不够的,我们还需要具有驱动程序的阅读、修改和开发能力。在此下篇中,就是要通过简单的USB驱动的例子,随您一起进入USB驱动开发的世界。
USB骨架程序(usb-skeleton),是USB驱动程序的基础,通过对它源码的学习和理解,可以使我们迅速地了解USB驱动架构,迅速地开发我们自己的USB硬件的驱动。
USB驱动开发
在掌握了USB设备的配置后,对于程序员,我们就可以尝试进行一些简单的USB驱动的修改和开发了。这一段落,我们会讲解一个最基础USB框架的基础上,做两个小的USB驱动的例子。
USB骨架
在Linux kernel源码目录中driver/usb/usb-skeleton.c为我们提供了一个最基础的USB驱动程序。我们称为USB骨架。通过它我们仅需要修改极少的部分,就可以完成一个USB设备的驱动。我们的USB驱动开发也是从她开始的。
那些linux下不支持的USB设备几乎都是生产厂商特定的产品。如果生产厂商在他们的产品中使用自己定义的协议,他们就需要为此设备创建特定的驱动程序。当然我们知道,有些生产厂商公开他们的USB协议,并帮助Linux驱动程序的开发,然而有些生产厂商却根本不公开他们的USB协议。因为每一个不同的协议都会产生一个新的驱动程序,所以就有了这个通用的USB驱动骨架程序, 它是以pci 骨架为模板的。
如果你准备写一个linux驱动程序,首先要熟悉USB协议规范。USB主页上有它的帮助。一些比较典型的驱动可以在上面发现,同时还介绍了USB urbs的概念,而这个是usb驱动程序中最基本的。
Linux USB 驱动程序需要做的第一件事情就是在Linux USB 子系统里注册,并提供一些相关信息,例如这个驱动程序支持那种设备,当被支持的设备从系统插入或拔出时,会有哪些动作。所有这些信息都传送到USB 子系统中,在usb骨架驱动程序中是这样来表示的:
static struct usb_driver skel_driver = {
name: “skeleton“,
probe: skel_probe,
disconnect: skel_disconnect,
fops: &skel_fops,
minor: USB_SKEL_MINOR_BASE,
id_table: skel_table,
};
变量name是一个字符串,它对驱动程序进行描述。probe 和disconnect 是函数指针,当设备与在id_table 中变量信息匹配时,此函数被调用。
fops和minor变量是可选的。大多usb驱动程序钩住另外一个驱动系统,例如SCSI,网络或者tty子系统。这些驱动程序在其他驱动系统中注册,同时任何用户空间的交互操作通过那些接口提供,比如我们把SCSI设备驱动作为我们USB驱动所钩住的另外一个驱动系统,那么我们此USB设备的read、write等操作,就相应按SCSI设备的read、write函数进行访问。但是对于扫描仪等驱动程序来说,并没有一个匹配的驱动系统可以使用,那我们就要自己处理与用户空间的read、write等交互函数。Usb子系统提供一种方法去注册一个次设备号和file_operations函数指针,这样就可以与用户空间实现方便地交互。
USB骨架程序的关键几点如下:
USB驱动的注册和注销
Usb驱动程序在注册时会发送一个命令给usb_register,通常在驱动程序的初始化函数里。
当要从系统卸载驱动程序时,需要注销usb子系统。即需要usb_unregister 函数处理:
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
module_exit(usb_skel_exit);
当usb设备插入时,为了使linux-hotplug(Linux中PCI、USB等设备热插拔支持)系统自动装载驱动程序,你需要创建一个MODULE_DEVICE_TABLE。代码如下(这个模块仅支持某一特定设备):
/* table of devices that work with this driver */
static struct usb_device_id skel_table = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);
USB_DEVICE宏利用厂商ID和产品ID为我们提供了一个设备的唯一标识。当系统插入一个ID匹配的USB设备到USB总线时,驱动会在USB core中注册。驱动程序中probe 函数也就会被调用。usb_device 结构指针、接口号和接口ID都会被传递到函数中。
static void * skel_probe(struct usb_device *dev,
unsigned int ifnum, const struct usb_device_id *id)
驱动程序需要确认插入的设备是否可以被接受,如果不接受,或者在初始化的过程中发生任何错误,probe函数返回一个NULL值。否则返回一个含有设备驱动程序状态的指针。通过这个指针,就可以访问所有结构中的回调函数。
在骨架驱动程序里,最后一点是我们要注册devfs。我们创建一个缓冲用来保存那些被发送给usb设备的数据和那些从设备上接受的数据,同时USB urb 被初始化,并且我们在devfs子系统中注册设备,允许devfs用户访问我们的设备。注册过程如下:
/* initialize the devfs node for this device
and register it */
sprintf(name, “skel%d“, skel-》minor);
skel-》devfs = devfs_register
(usb_devfs_handle, name,
DEVFS_FL_DEFAULT, USB_MAJOR,
USB_SKEL_MINOR_BASE + skel-》minor,
S_IFCHR | S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP | S_IROTH,
&skel_fops, NULL);
如果devfs_register函数失败,不用担心,devfs子系统会将此情况报告给用户。
当然最后,如果设备从usb总线拔掉,设备指针会调用disconnect 函数。驱动程序就需要清除那些被分配了的所有私有数据、关闭urbs,并且从devfs上注销调自己。
/* remove our devfs node */
devfs_unregister(skel-》devfs);
现在,skeleton驱动就已经和设备绑定上了,任何用户态程序要操作此设备都可以通过file_operations结构所定义的函数进行了。首先,我们要open此设备。在open函数中MODULE_INC_USE_COUNT 宏是一个关键,它的作用是起到一个计数的作用,有一个用户态程序打开一个设备,计数器就加一,例如,我们以模块方式加入一个驱动,若计数器不为零,就说明仍然有用户程序在使用此驱动,这时候,你就不能通过rmmod命令卸载驱动模块了。
/* increment our usage count for the module */
MOD_INC_USE_COUNT;
++skel-》open_count;
/* save our object in the file’s private structure */
file-》private_data = skel;
当open完设备后,read、write函数就可以收、发数据了。
skel的write、和read函数
他们是完成驱动对读写等操作的响应。
在skel_write中,一个FILL_BULK_URB函数,就完成了urb 系统callbak和我们自己的skel_write_bulk_callback之间的联系。注意skel_write_bulk_callback是中断方式,所以要注意时间不能太久,本程序中它就只是报告一些urb的状态等。
read 函数与write 函数稍有不同在于:程序并没有用urb 将数据从设备传送到驱动程序,而是我们用usb_bulk_msg 函数代替,这个函数能够不需要创建urbs 和操作urb函数的情况下,来发送数据给设备,或者从设备来接收数据。我们调用usb_bulk_msg函数并传提一个存储空间,用来缓冲和放置驱动收到的数据,若没有收到数据,就失败并返回一个错误信息。
usb_bulk_msg函数
当对usb设备进行一次读或者写时,usb_bulk_msg 函数是非常有用的; 然而, 当你需要连续地对设备进行读/写时,建议你建立一个自己的urbs,同时将urbs 提交给usb子系统。
skel_disconnect函数
当我们释放设备文件句柄时,这个函数会被调用。MOD_DEC_USE_COUNT宏会被用到(和MOD_INC_USE_COUNT刚好对应,它减少一个计数器),首先确认当前是否有其它的程序正在访问这个设备,如果是最后一个用户在使用,我们可以关闭任何正在发生的写,操作如下:
/* decrement our usage count for the device */
--skel-》open_count;
if (skel-》open_count 《= 0) {
/* shutdown any bulk writes that might be
going on */
usb_unlink_urb (skel-》write_urb);
skel-》open_count = 0;
}
/* decrement our usage count for the module */
MOD_DEC_USE_COUNT;
最困难的是,usb 设备可以在任何时间点从系统中取走,即使程序目前正在访问它。usb驱动程序必须要能够很好地处理解决此问题,它需要能够切断任何当前的读写,同时通知用户空间程序:usb设备已经被取走。
如果程序有一个打开的设备句柄,在当前结构里,我们只要把它赋值为空,就像它已经消失了。对于每一次设备读写等其它函数操作,我们都要检查usb_device结构是否存在。如果不存在,就表明设备已经消失,并返回一个-ENODEV错误给用户程序。当最终我们调用release 函数时,在没有文件打开这个设备时,无论usb_device结构是否存在、它都会清空skel_disconnect函数所作工作。
Usb 骨架驱动程序,提供足够的例子来帮助初始人员在最短的时间里开发一个驱动程序。更多信息你可以到linux usb开发新闻组去寻找。
U盘、USB读卡器、MP3、数码相机驱动
对于一款windows下用的很爽的U盘、USB读卡器、MP3或数码相机,可能Linux下却不能支持。怎么办?其实不用伤心,也许经过一点点的工作,你就可以很方便地使用它了。通常是此U盘、USB读卡器、MP3或数码相机在WindowsXP中不需要厂商专门的驱动就可以识别为移动存储设备,这样的设备才能保证成功,其他的就看你的运气了。
USB存储设备,他们的read、write等操作都是通过上章节中提到的钩子,把自己的操作钩到SCSI设备上去的。我们就不需要对其进行具体的数据读写处理了。
第一步:我们通过cat /proc/bus/usb/devices得到当前系统探测到的USB总线上的设备信息。它包括Vendor、ProdID、Product等。下面是我买的一款杂牌CF卡读卡器插入后的信息片断:
T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=02 Dev#= 5 Spd=12 MxCh= 0
D: Ver= 1.10 Cls=00(》ifc ) Sub=00 Prot=00 MxPS=8 #Cfgs= 1
P: Vendor=07c4 ProdID=a400 Rev= 1.13
S: Manufacturer=USB
S: Product=Mass Storage
C:* #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=70mA
I: If#= 0 Alt= 0 #EPs= 2 Cls=08(vend.) Sub=06 Prot=50 Driver=usb-storage
E: Ad=81(I) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl= 0ms
其中,我们最关心的是Vendor=07c4 ProdID=a400和Manufacturer=USB(果然是杂牌,厂商名都看不到)Product= Mass Storage。
对于这些移动存储设备,我们知道Linux下都是通过usb-storage.o驱动模拟成scsi设备去支持的,之所以不支持,通常是usb-storage驱动未包括此厂商识别和产品识别信息(在类似skel_probe的USB最初探测时被屏蔽了)。对于USB存储设备的硬件访问部分,通常是一致的。所以我们要支持它,仅需要修改usb-storage中关于厂商识别和产品识别列表部分。
第二部,打开drivers/usb/storage/unusual_devs.h文件,我们可以看到所有已知的产品登记表,都是以UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, vendor_name, product_name, use_protocol, use_transport, init_function, Flags)方式登记的。其中相应的涵义,你就可以根据命名来判断了。所以只要我们如下填入我们自己的注册,就可以让usb-storage驱动去认识和发现它。
UNUSUAL_DEV(07c4, a400, 0x0000, 0xffff,
“ USB “, “ Mass Storage “,
US_SC_SCSI, US_PR_BULK, NULL,
US_FL_FIX_INQUIRY | US_FL_START_STOP |US_FL_MODE_XLATE )
注意:添加以上几句的位置,一定要正确。比较发现,usb-storage驱动对所有注册都是按idVendor, idProduct数值从小到大排列的。我们也要放在相应位置。
最后,填入以上信息,我们就可以重新编译生成内核或usb-storage.o模块。这时候插入我们的设备就可以跟其他U盘一样作为SCSI设备去访问了。
目前很多键盘都有飞梭和手写板,下面我们就尝试为一款键盘飞梭加入一个驱动。在通常情况,当我们插入USB接口键盘时,在/proc/bus/usb/devices会看到多个USB设备。比如:你的USB键盘上的飞梭会是一个,你的手写板会是一个,若是你的USB键盘有USB扩展连接埠,也会看到。
下面是具体看到的信息
T: Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 1 Spd=12 MxCh= 2
B: Alloc= 11/900 us ( 1%), #Int= 1, #Iso= 0
D: Ver= 1.00 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=0000 ProdID=0000 Rev= 0.00
S: Product=USB UHCI Root Hub
S: SerialNumber=d800
C:* #Ifs= 1 Cfg#= 1 Atr=40 MxPwr= 0mA
I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 8 Ivl=255ms
T: Bus=02 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 3 Spd=12 MxCh= 3
D: Ver= 1.10 Cls=09(hub ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=07e4 ProdID=9473 Rev= 0.02
S: Manufacturer=ALCOR
S: Product=Movado USB Keyboard
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=100mA
I: If#= 0 Alt= 0 #EPs= 1 Cls=09(hub ) Sub=00 Prot=00 Driver=hub
E: Ad=81(I) Atr=03(Int.) MxPS= 1 Ivl=255ms
找到相应的信息后就可开始工作了。实际上,飞梭的定义和键盘键码通常是一样的,所以我们参照drivers/usb/usbkbd..c代码进行一些改动就可以了。因为没能拿到相应的硬件USB协议,我无从知道飞梭在按下时通讯协议众到底发什么,我只能把它的信息打出来进行分析。幸好,它比较简单,在下面代码的usb_kbd_irq函数中if(kbd-》new == (char)0x01)和if(((kbd-》new》》4)&0x0f)!=0x7)就是判断飞梭左旋。usb_kbd_irq函数就是键盘中断响应函数。他的挂接,就是在usb_kbd_probe函数中
FILL_INT_URB(&kbd-》irq, dev, pipe, kbd-》new, maxp 》 8 ? 8 : maxp,
usb_kbd_irq, kbd, endpoint-》bInterval);
一句中实现。
从usb骨架中我们知道,usb_kbd_probe函数就是在USB设备被系统发现是运行的。其他部分就都不是关键了。你可以根据具体的探测值(Vendor=07e4 ProdID=9473等)进行一些修改就可以了。值得一提的是,在键盘中断中,我们的做法是收到USB飞梭消息后,把它模拟成左方向键和右方向键,在这里,就看你想怎么去响应它了。当然你也可以响应模拟成F14、F15等扩展键码。
在了解了此基本的驱动后,对于一个你已经拿到通讯协议的键盘所带手写板,你就应该能进行相应驱动的开发了吧。
附一个键盘飞梭的源码
使用此驱动要注意的问题:在加载此驱动时你必须先把hid设备卸载,加载完usbhkey.o模块后再加载hid.o。因为若hid存在,它的probe会屏蔽系统去利用我们的驱动发现我们的设备。其实,飞梭本来就是一个hid设备,正确的方法,或许你应该修改hid的probe函数,然后把我们的驱动融入其中。
键盘飞梭驱动
#include 《linux/kernel.h》
#include 《linux/slab.h》
#include 《linux/module.h》
#include 《linux/input.h》
#include 《linux/init.h》
#include 《linux/usb.h》
#include 《linux/kbd_ll.h》
/*
* Version Information
*/
#define DRIVER_VERSION ““
#define DRIVER_AUTHOR “TGE HOTKEY “
#define DRIVER_DESC “USB HID Tge hotkey driver“
#define USB_HOTKEY_VENDOR_ID 0x07e4
#define USB_HOTKEY_PRODUCT_ID 0x9473
//厂商和产品ID信息就是/proc/bus/usb/devices中看到的值
MODULE_AUTHOR( DRIVER_AUTHOR );
MODULE_DESCRIPTION( DRIVER_DESC );
struct usb_kbd {
struct input_dev dev;
struct usb_device *usbdev;
unsigned char new;
unsigned char old;
struct urb irq, led;
// devrequest dr;
//这一行和下一行的区别在于kernel2.4.20版本对usb_kbd键盘结构定义发生了变化
struct usb_ctrlrequest dr;
unsigned char leds, newleds;
char name;
int open;
};
//此结构来自内核中drivers/usb/usbkbd..c
static void usb_kbd_irq(struct urb *urb)
{
struct usb_kbd *kbd = urb-》context;
int *new;
new = (int *) kbd-》new;
if(kbd-》new == (char)0x01)
{
if(((kbd-》new》》4)&0x0f)!=0x7)
{
handle_scancode(0xe0,1);
handle_scancode(0x4b,1);
handle_scancode(0xe0,0);
handle_scancode(0x4b,0);
}
else
{
handle_scancode(0xe0,1);
handle_scancode(0x4d
-module_device_table
arm linux驱动程序和应用程序之间的一些问题
个人的见解:
1、驱动算是底层的东西,它加载后在用户层也就是文件系统中会生成一个设备文件,一般在/dev目录下。
2、应用程序就是看名字就是应用层的,它就是利用上面所说的设备文件跟底层联系的。所以应用层开发只需对设备文件进行操作就行了。
3、QT的话,其实是属于应用层的,具体就baidu一下QT移植与开发。
另外,团IDC网上有许多产品团购,便宜有口碑
-module_device_table
瑞昱RTL 8187L无线网卡linux驱动
应该是想在LINUX下使用8187无线网卡。那么只要达到目的就行。
拿8187的WINDOWS下的驱动,使用ndiswrapper这个桥梁程序,把驱动借到linux下用,换句话说就是内核先调用ndiswrapper模块,ndiswrapper模块再调用无线网卡在WIN下的驱动。
详细步骤如下,我在我的本本上成功的驱上了我的无线网卡,我把过程记录下来了,现在贴给你,希望对你有帮助。
--------------------------
《Linksys WPC54Gv2的PCMCIA无线网卡在centos5.3下能用了》
问题:CENTOS_5.3安装好后在设备管理器里能看见无线网卡,但无法激活
使用lspci可以看到这个无线网卡
[root@ljj ~]# lspci
...... ......
03:00.0 Network controller: Broadcom Corporation BCM4306 802.11b/g Wireless LAN Controller (rev 02)
参考lex在
8、把它变成模块
[root@ljj ~]# ndiswrapper -m
adding “alias wlan0 ndiswrapper“ to /etc/modprobe.d/ndiswrapper ...
9、lsmod发现系统本身有bcm43xx,这也就是为什么在设备管理器中能看到无线网卡的原因,但它不能用,所以要删掉这个模块改用ndiswrapper
[root@ljj ~]# modprobe -r bcm43xx
10、再加载ndiswrapper模块让它负责用windows下的驱动程序驱动无线网卡
[root@ljj ~]# modprobe ndiswrapper
再用lsmod看看,仅有ndiswrapper模块了
[root@ljj ~]# lsmod
Module Size Used by
ndiswrapper 170256 0
...... ......
11、指定无线信号源,使用
[root@ljj ~]# iwconfig eth1 essid ’ljj’
12、指定密码
[root@ljj ~]# iwconfig eth1 key 6666-6666-66 open
如果要开机就用可以直接在rc.local文件里设定:
echo ’iwconfig eth1 key 6666-6666-66 open’ 》》 /etc/rc.d/rc.local
13、指定ip地址
[root@ljj ~]# ifconfig eht1 192.168.1.10 netmask 255.255.255.0
[root@ljj ~]# ifconfig eth1 up
15、指定网关、缺省路由
[root@ljj ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 * 255.255.255.0 U 0 0 0 eth1
169.254.0.0 * 255.255.0.0 U 0 0 0 eth1
[root@ljj ~]# route add default gw 192.168.1.1 eth1
[root@ljj ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 * 255.255.255.0 U 0 0 0 eth1
169.254.0.0 * 255.255.0.0 U 0 0 0 eth1
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth1
16、就可以用了,你还可以扫描一下周围的其他网络信号
[root@ljj ~]# iwlist eth1 scanning
eth1 Scan completed :
Cell 01 - Address: 00:1D:0F:93:7A:46
ESSID:“ljj“
Protocol:IEEE 802.11b
Mode:Managed
Frequency:2.462 GHz (Channel 11)
Quality:100/100 Signal level:-31 dBm Noise level:-96 dBm
Encryption keyff
Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
12 Mb/s; 24 Mb/s; 36 Mb/s; 9 Mb/s; 18 Mb/s
48 Mb/s; 54 Mb/s
Extra:bcn_int=100
Extra:atim=0
Cell 02 - Address: 00:25:86:4D:B2:BA
ESSID:“TP-LINK_4DB2BA“
Protocol:IEEE 802.11b
Mode:Managed
Frequency:2.437 GHz (Channel 6)
Quality:37/100 Signal level:-72 dBm Noise level:-96 dBm
Encryption keyn
Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
12 Mb/s; 24 Mb/s; 36 Mb/s; 9 Mb/s; 18 Mb/s
48 Mb/s; 54 Mb/s
Extra:bcn_int=100
Extra:atim=0
Cell 03 - Address: 00:1D:0F:92:7F:AC
ESSID:“TP-LINK“
Protocol:IEEE 802.11b
Mode:Managed
Frequency:2.437 GHz (Channel 6)
Quality:23/100 Signal level:-81 dBm Noise level:-96 dBm
Encryption keyn
Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
12 Mb/s; 24 Mb/s; 36 Mb/s; 9 Mb/s; 18 Mb/s
48 Mb/s; 54 Mb/s
Extra:bcn_int=100
Extra:atim=0
-----------------------------------
以下是配置过程:
[ljj@ljj ~]$ su - root
口令:
[root@ljj ~]# lsmod
Module Size Used by
savage 32705 2
drm 65365 3 savage
deflate 7873 0
zlib_deflate 21977 1 deflate
ccm 13505 0
serpent 29249 0
blowfish 12609 0
twofish 46017 0
ecb 7617 0
xcbc 9921 0
crypto_hash 6336 1 xcbc
cbc 8257 0
md5 8257 0
sha256 15297 0
sha512 13121 0
des 20417 0
aes_generic 31745 0
testmgr_cipher 6849 0
testmgr 44593 1 testmgr_cipher
crypto_blkcipher 17601 4 ccm,ecb,cbc,testmgr
aes_i586 37057 0
ipcomp6 11849 0
ipcomp 11465 0
ah6 10433 0
ah4 10305 0
esp6 12225 0
xfrm6_esp 9537 1 esp6
esp4 12352 0
xfrm4_esp 9793 1 esp4
aead 11841 4 ccm,testmgr,esp6,esp4
crypto_algapi 22849 7 ccm,ecb,xcbc,cbc,testmgr,crypto_blkcipher,aead
xfrm4_tunnel 6593 0
tunnel4 7365 1 xfrm4_tunnel
xfrm4_mode_tunnel 6849 0
xfrm4_mode_transport 6209 0
xfrm6_mode_transport 6337 0
xfrm6_mode_tunnel 6721 0
xfrm6_tunnel 11233 1 ipcomp6
tunnel6 7365 1 xfrm6_tunnel
af_key 40785 0
autofs4 24261 2
hidp 23105 2
l2cap 29505 5 hidp
bluetooth 53797 2 hidp,l2cap
sunrpc 144765 1
ip_conntrack_ftp 11569 0
ip_conntrack_netbios_ns 6977 0
iptable_nat 11205 0
ip_nat 20845 1 iptable_nat
iptable_mangle 6849 0
ipt_REJECT 9537 1
xt_state 6209 11
ip_conntrack 52897 5 ip_conntrack_ftp,ip_conntrack_netbios_ns,iptable_nat,ip_nat,xt_state
nfnetlink 10713 2 ip_nat,ip_conntrack
xt_tcpudp 7105 13
iptable_filter 7105 1
ip_tables 17029 3 iptable_nat,iptable_mangle,iptable_filter
x_tables 17349 5 iptable_nat,ipt_REJECT,xt_state,xt_tcpudp,ip_tables
vfat 15937 1
fat 51165 1 vfat
dm_mirror 23877 0
dm_multipath 24013 0
scsi_dh 11713 1 dm_multipath
video 21193 0
thinkpad_acpi 56929 0
hwmon 7365 1 thinkpad_acpi
backlight 10049 2 video,thinkpad_acpi
sbs 18533 0
i2c_ec 9025 1 sbs
button 10705 0
battery 13637 0
asus_acpi 19289 0
ac 9157 0
ipv6 261473 21 ipcomp6,ah6,esp6,xfrm6_esp,xfrm6_mode_transport,xfrm6_tunnel,tunnel6
xfrm_nalgo 13381 7 ah6,ah4,esp6,xfrm6_esp,esp4,xfrm4_esp,ipv6
crypto_api 12608 10 ccm,testmgr,crypto_blkcipher,ah6,ah4,esp6,esp4,aead,crypto_algapi,xfrm_nalgo
lp 15849 0
snd_intel8x0 35421 1
snd_ac97_codec 93025 1 snd_intel8x0
ac97_bus 6337 1 snd_ac97_codec
snd_seq_dummy 7877 0
snd_seq_oss 32577 0
snd_seq_midi_event 11073 1 snd_seq_oss
snd_seq 49585 5 snd_seq_dummy,snd_seq_oss,snd_seq_midi_event
snd_seq_device 11725 3 snd_seq_dummy,snd_seq_oss,snd_seq
snd_pcm_oss 42817 0
bcm43xx 415584 0
ieee80211softmac 33729 1 bcm43xx
ieee80211 34313 2 bcm43xx,ieee80211softmac
snd_mixer_oss 19009 1 snd_pcm_oss
ieee80211_crypt 10049 1 ieee80211
snd_pcm 72133 3 snd_intel8x0,snd_ac97_codec,snd_pcm_oss
serio_raw 10693 0
snd_timer 24517 2 snd_seq,snd_pcm
e100 36809 0
ide_cd 40161 0
snd 55237 11 snd_intel8x0,snd_ac97_codec,snd_seq_oss,snd_seq,snd_seq_device,snd_pcm_oss,snd_mixer_oss,snd_pcm,snd_timer
mii 9409 1 e100
soundcore 11553 1 snd
i2c_i801 11725 0
parport_pc 29157 1
pcspkr 7105 0
cdrom 36577 1 ide_cd
snd_page_alloc 14281 2 snd_intel8x0,snd_pcm
i2c_core 23745 2 i2c_ec,i2c_i801
parport 37513 2 lp,parport_pc
dm_raid45 66509 0
dm_message 6977 1 dm_raid45
dm_region_hash 15681 1 dm_raid45
dm_log 14529 3 dm_mirror,dm_raid45,dm_region_hash
dm_mod 62201 4 dm_mirror,dm_multipath,dm_raid45,dm_log
dm_mem_cache 9537 1 dm_raid45
ata_piix 23621 0
libata 156677 1 ata_piix
sd_mod 25153 0
scsi_mod 141589 3 scsi_dh,libata,sd_mod
ext3 124361 1
jbd 56937 1 ext3
uhci_hcd 25421 0
ohci_hcd 24553 0
ehci_hcd 33357 0
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]# modprobe -r bcm43xx
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]#
[root@ljj ~]# lsmod
Module Size Used by
savage 32705 2
drm 65365 3 savage
deflate 7873 0
zlib_deflate 21977 1 deflate
ccm 13505 0
serpent 29249 0
blowfish 12609 0
twofish 46017 0
ecb 7617 0
xcbc 9921 0
crypto_hash 6336 1 xcbc
cbc 8257 0
md5 8257 0
sha256 15297 0
sha512 13121 0
des 20417 0
aes_generic 31745 0
testmgr_cipher 6849 0
testmgr 44593 1 testmgr_cipher
crypto_blkcipher 17601 4 ccm,ecb,cbc,testmgr
aes_i586 37057 0
ipcomp6 11849 0
ipcomp 11465 0
ah6 10433 0
ah4 10305 0
esp6 12225 0
xfrm6_esp 9537 1 esp6
esp4 12352 0
xfrm4_esp 9793 1 esp4
aead 11841 4 ccm,testmgr,esp6,esp4
crypto_algapi 22849 7 ccm,ecb,xcbc,cbc,testmgr,crypto_blkcipher,aead
xfrm4_tunnel 6593 0
tunnel4 7365 1 xfrm4_tunnel
xfrm4_mode_tunnel 6849 0
xfrm4_mode_transport 6209 0
xfrm6_mode_transport 6337 0
xfrm6_mode_tunnel 6721 0
xfrm6_tunnel 11233 1 ipcomp6
tunnel6 7365 1 xfrm6_tunnel
af_key 40785 0
autofs4 24261 2
hidp 23105 2
l2cap 29505 5 hidp
bluetooth 53797 2 hidp,l2cap
sunrpc 144765 1
ip_conntrack_ftp 11569 0
ip_conntrack_netbios_ns 6977 0
iptable_nat 11205 0
ip_nat 20845 1 iptable_nat
iptable_mangle 6849 0
ipt_REJECT 9537 1
xt_state 6209 11
ip_conntrack 52897 5 ip_conntrack_ftp,ip_conntrack_netbios_ns,iptable_nat,ip_nat,xt_state
nfnetlink 10713 2 ip_nat,ip_conntrack
xt_tcpudp 7105 13
iptable_filter 7105 1
ip_tables 17029 3 iptable_nat,iptable_mangle,iptable_filter
x_tables 17349 5 iptable_nat,ipt_REJECT,xt_state,xt_tcpudp,ip_tables
vfat 15937 1
fat 51165 1 vfat
dm_mirror 23877 0
dm_multipath 24013 0
scsi_dh 11713 1 dm_multipath
video 21193 0
thinkpad_acpi 56929 0
hwmon 7365 1 thinkpad_acpi
backlight 10049 2 video,thinkpad_acpi
sbs 18533 0
i2c_ec 9025 1 sbs
button 10705 0
battery 13637 0
asus_acpi 19289 0
ac 9157 0
ipv6 261473 21 ipcomp6,ah6,esp6,xfrm6_esp,xfrm6_mode_transport,xfrm6_tunnel,tunnel6
xfrm_nalgo 13381 7 ah6,ah4,esp6,xfrm6_esp,esp4,xfrm4_esp,ipv6
crypto_api 12608 10 ccm,testmgr,crypto_blkcipher,ah6,ah4,esp6,esp4,aead,crypto_algapi,xfrm_nalgo
lp 15849 0
snd_intel8x0 35421 1
snd_ac97_codec 93025 1 snd_intel8x0
ac97_bus 6337 1 snd_ac97_codec
snd_seq_dummy 7877 0
snd_seq_oss 32577 0
snd_seq_midi_event 11073 1 snd_seq_oss
snd_seq 49585 5 snd_seq_dummy,snd_seq_oss,snd_seq_midi_event
snd_seq_device 11725 3 snd_seq_dummy,snd_seq_oss,snd_seq
snd_pcm_oss 42817 0
snd_mixer_oss 19009 1 snd_pcm_oss
snd_pcm 72133 3 snd_intel8x0,snd_ac97_codec,snd_pcm_oss
serio_raw 10693 0
snd_timer 24517 2 snd_seq,snd_pcm
e100 36809 0
ide_cd 40161 0
snd 55237 11 snd_intel8x0,snd_ac97_codec,snd_seq_oss,snd_seq,snd_seq_device,snd_pcm_oss,snd_mixer_oss,snd_pcm,snd_timer
mii 9409 1 e100
soundcore 11553 1 snd
i2c_i801 11725 0
parport_pc 29157 1
pcspkr 7105 0
cdrom 36577 1 ide_cd
snd_page_alloc 14281 2 snd_intel8x0,snd_pcm
i2c_core 23745 2 i2c_ec,i2c_i801
parport 37513 2 lp,parport_pc
dm_raid45 66509 0
dm_message 6977 1 dm_raid45
dm_region_hash 15681 1 dm_raid45
dm_log 14529 3 dm_mirror,dm_raid45,dm_region_hash
dm_mod 62201 4 dm_mirror,dm_multipath,dm_raid45,dm_log
dm_mem_cache 9537 1 dm_raid45
ata_piix 23621 0
libata 156677 1 ata_piix
sd_mod 25153 0
scsi_mod 141589 3 scsi_dh,libata,sd_mod
ext3 124361 1
jbd 56937 1 ext3
uhci_hcd 25421 0
ohci_hcd 24553 0
ehci_hcd 33357 0
[root@ljj ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
[root@ljj ~]# modprobe ndiswrapper
[root@ljj ~]# ndiswrapper -l
lsbcmnds : driver installed
device (14E4:4320) present (alternate driver: bcm43xx)
[root@ljj ~]#
[root@ljj ~]# iwconfig eth1 essid ’ljj’
[root@ljj ~]# iwconfig eth1 key 6666-6666-66 open
[root@ljj ~]# iwconfig
lo no wireless extensions.
eth0 no wireless extensions.
sit0 no wireless extensions.
eth1 IEEE 802.11g ESSID:“ljj“
Mode:Managed Frequency:2.462 GHz Access Point: 00:1D:0F:93:7A:46
Bit Rate:54 Mb/s Tx-Power:14 dBm
RTS thr:2347 B Fragment thr:2346 B
Encryption key:6666-6666-66 Security modepen
Power Managementff
Link Quality:100/100 Signal level:-31 dBm Noise level:-96 dBm
Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0
Tx excessive retries:0 Invalid misc:0 Missed beacon:0
[root@ljj ~]# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=4.77 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.68 ms
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 1.687/3.229/4.771/1.542 ms
[root@ljj ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 * 255.255.255.0 U 0 0 0 eth1
169.254.0.0 * 255.255.0.0 U 0 0 0 eth1
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth1
[root@ljj ~]# ping www.baidu.com
PING www.a.shifen.com (202.108.22.5) 56(84) bytes of data.
64 bytes from xd-22-5-a8.bta.net.cn (202.108.22.5): icmp_seq=1 ttl=54 time=15.4 ms
64 bytes from xd-22-5-a8.bta.net.cn (202.108.22.5): icmp_seq=2 ttl=54 time=14.7 ms
64 bytes from xd-22-5-a8.bta.net.cn (202.108.22.5): icmp_seq=3 ttl=54 time=14.9 ms
64 bytes from xd-22-5-a8.bta.net.cn (202.108.22.5): icmp_seq=4 ttl=54 time=14.7 ms
--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 14.708/14.952/15.402/0.296 ms
-module_device_table
USB驱动的组成有哪些
在USB规范中把USB分为五个部分:控制器、控制器驱动程序、USB芯片驱动程序、USB设备和针对不同USB设备的客户驱动程序。第一、 USB芯片驱动程序:这部分组件主要是可以提供对USB的支持。第二、 USB控制器驱动程序:这部分主要是在控制器和USB设备之间建立通信信道。第三、 USB设备:这部分组件包括和PC相连的USB外围设备,主要分为:设备本身可以再接上其它的USB外围设备,另外设备本身不能再连接其它外围设备。即集线器和设备。也可以说集线器是带有连接其它外围设备的USB端口,设备是连接在计算机上的用语完成特定功能而且符合USB规范的设备单元。第四、 USB控制器:这部分主要负责执行由USB控制器驱动程序发出的命令。第五、 USB设备驱动程序:这部分组件主要是用来驱动USB设备的程序,一般来说这是由操作系统或者是USB设备制造商提供
-module_device_table
如何创建用于WindowsNT PCI 设备驱动程序
Linux下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 int devfn;
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int class;
u8 hdr_type;
u8 rom_base_reg;
struct pci_driver *driver;
void *driver_data;
u64 dma_mask;
u32 current_state;
unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
unsigned int irq;
struct resource resource[DEVICE_COUNT_RESOURCE];
struct resource dma_resource[DEVICE_COUNT_DMA];
struct resource irq_resource[DEVICE_COUNT_IRQ];
char name;
char slot_name;
int active;
int ro;
unsigned short regs;
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);
}
小结
PCI总线不仅是目前应用广泛的计算机总线标准,而且是一种兼容性最强、功能最全的计算机总线。而Linux作为一种新的操作系统,其发展前景是无法估量的,同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放,因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本文介绍如何编译Linux下的PCI驱动程序,针对的内核版本是2.4。
-module_device_table
如何编写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(“blog.ednchina.com/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; 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; }
-module_device_table
网络设备初始化主要流程是
网卡驱动初始化
网络设备驱动加载时,内核会调用一个驱动程序注册的初始化函数。 内核提供了一个宏 module_init 来执行注册操作。
igb 模块的初始化函数长这样(请叫我程序拷贝员)。
/**
* igb_init_module - Driver Registration Routine
*
* igb_init_module is the first routine called when the driver is
* loaded. All it does is register with the PCI subsystem.
**/
static int __init igb_init_module(void)
{
int ret;
pr_info(“%s - version %s\n“,
igb_driver_string, igb_driver_version);
pr_info(“%s\n“, igb_copyright);
#ifdef CONFIG_IGB_DCA
dca_register_notify(&dca_notifier);
#endif
ret = pci_register_driver(&igb_driver);
return ret;
}
module_init(igb_init_module);-module_device_table
可以看到主要是有个 pci_register_driver 的函数,下面看看它干了啥。
PCI初始化
网卡一般都是PCI设备,可以用命令lspci查看。 PCI设备通过配置空间中的一系列寄存器来识别自己。 驱动会使用内核提供的宏 MODULE_DEVICE_TABLE 来导出驱动支持的PCI设备(使用设备ID标识)。 内核会用这个表决定加载特定的驱动从而控制PCI设备。-module_device_table
static const struct pci_device_id igb_pci_tbl = {
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_1GBPS) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_SGMII) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_2_5GBPS) },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_SGMII), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_COPPER_DUAL), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SGMII), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SERDES), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_BACKPLANE), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SFP), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_NS), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_NS_SERDES), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_FIBER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_SERDES), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_SERDES_QUAD), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_QUAD_COPPER_ET2), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_QUAD_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_FIBER_SERDES), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82575GB_QUAD_COPPER), board_82575 },
/* required last entry */
{0, }
};
MODULE_DEVICE_TABLE(pci, igb_pci_tbl);-module_device_table
pci_register_driver 使用 igb_driver 作为参数。igb_driver 则使用 igb_pci_tbl 赋值给域 id_table。
static struct pci_driver igb_driver = {
.name = igb_driver_name,
.id_table = igb_pci_tbl,
.probe = igb_probe,
.remove = igb_remove,
#ifdef CONFIG_PM
.driver.pm = &igb_pm_ops,
#endif
.shutdown = igb_shutdown,
.sriov_configure = igb_pci_sriov_configure,
.err_handler = &igb_err_handler
};-module_device_table
重头戏 igb_probe
igb_driver 有个很重要的域 igb_probe。内核识别到PCI设备驱动后,就会调用pci_driver 中 probe 指向的函数。对 igb 来说,就是 igb_probe 函数。
igb_probe 会执行以下操作:
启用PCI设备。
设置DMA掩码。
请求内存区域和IO端口。
注册ethtool函数。
分配 net_device,这个结构代表一个抽象的网络设备。
注册 net_device_ops 到 net_device 的 netdev_ops 域。
设置 net_device 的features。
还有一些杂七杂八的工作都在这里完成,watchdog, 缓冲区分配等等。
- static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {
- ...
- netdev-》netdev_ops = &igb_netdev_ops;
- ...
- }
- static const struct net_device_ops igb_netdev_ops = {
- .ndo_open = igb_open,
- .ndo_stop = igb_close,
- .ndo_start_xmit = igb_xmit_frame,
- .ndo_get_stats64 = igb_get_stats64,
- .ndo_set_rx_mode = igb_set_rx_mode,
- .ndo_set_mac_address = igb_set_mac,
- .ndo_change_mtu = igb_change_mtu,
- .ndo_do_ioctl = igb_ioctl,
- .ndo_tx_timeout = igb_tx_timeout,
- .ndo_validate_addr = eth_validate_addr,
- .ndo_vlan_rx_add_vid = igb_vlan_rx_add_vid,
- .ndo_vlan_rx_kill_vid = igb_vlan_rx_kill_vid,
- .ndo_set_vf_mac = igb_ndo_set_vf_mac,
- .ndo_set_vf_vlan = igb_ndo_set_vf_vlan,
- .ndo_set_vf_rate = igb_ndo_set_vf_bw,
- .ndo_set_vf_spoofchk = igb_ndo_set_vf_spoofchk,
- .ndo_get_vf_config = igb_ndo_get_vf_config,
- #ifdef CONFIG_NET_POLL_CONTROLLER
- .ndo_poll_controller = igb_netpoll,
- #endif
- .ndo_fix_features = igb_fix_features,
- .ndo_set_features = igb_set_features,
- .ndo_fdb_add = igb_ndo_fdb_add,
- .ndo_features_check = igb_features_check,
- }
struct net_device_ops 包含网络子系统操作设备的诸多函数指针。
怎么能将linux下自动分配的usb0改为usb1
你好,方法如下:
写一个USB的驱动程序最 基本的要做四件事:
驱动程序要支持的设备、注册USB驱动程序、探测和断开、提交和控制urb(USB请求块)
驱动程序支持的设备:有一个结构体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,
};
探测和断开:当 一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用,探测函数检查传递给它的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因 为某种原因不应该控制设备时,断开函数被调用,它可以做一些清理工作。探测回调函数中,USB驱动程序初始化任何可能用于控制USB设备的局部结构体,它 还把所需的任何设备相关信息保存到一个局部结构体中,
提交和控制urb:当驱动程序有数据要发送到USB设备时(大多数情况是在驱动程序的写函数中),要分配一个urb来把数据传输给设备:
/* 创建一个urb,并且给它分配一个缓存*/
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}
当urb被成功分配后,还要创建一个DMA缓冲区来以高效的方式发送数据到设备,传递给驱动程序的数据要复制到这块缓冲中去:
buf = usb_buffer_alloc(dev-》udev, count, GFP_KERNEL, &urb-》transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, count)) {
retval = -EFAULT;
goto error;
}
当数据从用户空间正确复制到局部缓冲区后,urb必须在可以被提交给USB核心之前被正确初始化:
/* 初始化urb */
usb_fill_bulk_urb(urb, dev-》udev,
usb_sndbulkpipe(dev-》udev, dev-》bulk_out_endpointAddr),
buf, count, skel_write_bulk_callback, dev);
urb-》transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
然后urb就可以被提交给USB核心以传输到设备了:
/* 把数据从批量OUT端口发出 */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
err(“%s - failed submitting write urb, error %d“, __FUNCTION__, retval);
goto error;
}
当urb被成功传输到USB设备之后,urb回调函数将被USB核心调用,在我们的例子中,我们初始化urb,使它指向skel_write_bulk_callback函数,以下就是该函数:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
struct usb_skel *dev;
dev = (struct usb_skel *)urb-》context;
if (urb-》status &&
!(urb-》status == -ENOENT ||
urb-》status == -ECONNRESET ||
urb-》status == -ESHUTDOWN)) {
dbg(“%s - nonzero write bulk status received: %d“,
__FUNCTION__, urb-》status);
}
/* 释放已分配的缓冲区 */
usb_buffer_free(urb-》dev, urb-》transfer_buffer_length,
urb-》transfer_buffer, urb-》transfer_dma);
}
有时候USB驱动程序只是要发送或者接收一些简单的数据,驱动程序也可以不用urb来进行数据的传输,这是里涉及到两个简单的接口函数:usb_bulk_msg和usb_control_msg ,在这个USB框架程序里读操作就是这样的一个应用:
/* 进行阻塞的批量读以从设备获取数据 */
retval = usb_bulk_msg(dev-》udev,
usb_rcvbulkpipe(dev-》udev, dev-》bulk_in_endpointAddr),
dev-》bulk_in_buffer,
min(dev-》bulk_in_size, count),
&count, HZ*10);
/*如果读成功,复制到用户空间 */
if (!retval) {
if (copy_to_user(buffer, dev-》bulk_in_buffer, count))
retval = -EFAULT;
else
retval = count;
}
usb_bulk_msg接口函数的定义如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
其参数为:
struct usb_device *usb_dev:指向批量消息所发送的目标USB设备指针。
unsigned int pipe:批量消息所发送目标USB设备的特定端点,此值是调用usb_sndbulkpipe或者usb_rcvbulkpipe来创建的。
void *data:如果是一个OUT端点,它是指向即将发送到设备的数据的指针。如果是IN端点,它是指向从设备读取的数据应该存放的位置的指针。
int len:data参数所指缓冲区的大小。
int *actual_length:指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。
int timeout:以Jiffies为单位的等待的超时时间,如果该值为0,该函数一直等待消息的结束。
如果该接口函数调用成功,返回值为0,否则返回一个负的错误值。
usb_control_msg接口函数定义如下:
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8 request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)
除了允许驱动程序发送和接收USB控制消息之外,usb_control_msg函数的运作和usb_bulk_msg函数类似,其参数和usb_bulk_msg的参数有几个重要区别:
struct usb_device *dev:指向控制消息所发送的目标USB设备的指针。
unsigned int pipe:控制消息所发送的目标USB设备的特定端点,该值是调用usb_sndctrlpipe或usb_rcvctrlpipe来创建的。
__u8 request:控制消息的USB请求值。
__u8 requesttype:控制消息的USB请求类型值。
__u16 value:控制消息的USB消息值。
__u16 index:控制消息的USB消息索引值。
void *data:如果是一个OUT端点,它是指身即将发送到设备的数据的指针。如果是一个IN端点,它是指向从设备读取的数据应该存放的位置的指针。
__u16 size:data参数所指缓冲区的大小。
int timeout:以Jiffies为单位的应该等待的超时时间,如果为0,该函数将一直等待消息结束。
如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。
这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它函数取消,使用时要谨慎。
我们要给未知的USB设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的ID号,把0xfff0这两个值改为未知USB的ID号。
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
还 有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULK)IN和OUT端点,可 以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK)让其探测其它的端点类型,驱动程序会对USB设备的每一个接口进行一次探测, 当探测成功后,驱动程序就被绑定到这个接口上。再有就是urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用 了,这里我们简单介绍三个初始化urb的辅助函数:
usb_fill_int_urb :它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);
这个函数用来正确的初始化即将被发送到USB设备的中断端点的urb。
usb_fill_bulk_urb :它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)
这个函数是用来正确的初始化批量urb端点的。
usb_fill_control_urb :它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);
这个函数是用来正确初始化控制urb端点的。
还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的konicawc.c文件。
-module_device_table