完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、引言
USB(Universal Serial Bus)即通用串行总线,是一种全新的双向同步传输的支持热插拔的数据传输总线,其目的是为了提供一种兼容不同速度的、可扩充的并且使用方便的外围设备接口,同时也是为了解决计算机接口的太多的弊端而设计的。一个USB系统主要有三部分组成:USB互连、USB主机、USB设备三部分组成的,其结构如图1所示。在编写USB设备驱动程序设计时,可以分为三部分编写:主机端设备驱动程序、主机控制器驱动程序设计和设备端驱动程序三部分,在本文中重点介绍主机端驱动程序的设计。 二、USB设备驱动程序的设计 USB设备驱动程序的设计包括主机端设备驱动程序设计、主机控制器驱动程序设计和设备端驱动程序设计三部分组成。主机端设备驱动程序就是通常说的设备驱动程序,它是主机环境中为用户应用程序提供一个访问USB外设的接口。Linux为这部分驱动程序提供编程接口,驱动程序设计者只要按照需求编写驱动程序框架,通过调用操作系统提供的API接口函数可以完成对USB外设的特定访问。 主机控制驱动主要是对USB主机控制器的驱动,在大多数PC环境下,主机控制器都是由操作系统提供。嵌入式设备一般都没有USB主机控制器,只是工作在Slave模式下。如果要使USB具有主机功能,那么设备中需要选用一个带主机控制器的USB接口控制芯片, 同时自己还要有实现该主机控制器的驱动程序。目前Linux内核中只提供USB主机控制器的开放主机控制器和通用主机控制器接口两种规格,而这两种规格主要用在PC架构中。USB主机端驱动程序与主机控制器的结构如图2所示。其中USB核是Linux的一个子模块,集中定义了一组USB相关的数据结构、宏以及API函数。 USB设备驱动程序是常说的设备固件程序的一部分,提供设备信息与主机的通信接口。设备端USB驱动程序设计由以下几部分处理程序组成。初始化例程:完成描述符指针、端点、配置改变等操作。数据传输例程:完成控制传输、批量传输、中断传输及同步传输等传输方式下的数据收发工作。标准设备处理请求:处理标准设备请求。厂商请求处理:处理生产商指定请求。其他操作:处理主机发出的端口复位、配置改变等操作。 1.USB设备驱动程序框架 USB驱动程序首先要向Linux内核注册自己,并告诉系统它所支持的设备类型以及它所支持的操作。这些信息通过一个u***_driver结构来传递。u***_driver结构如下: static struct u***_driver skel_driver = { name: “skeleton”;/*驱动程序的名称*/ probe: skel_probe; /*设备列举时被调用*/ disconnect: skel_disconnect; /*设备被卸载时被调用*/ fops: &skel_fops; /*指向一个file_operation结构,内核通过它来访问驱动程序的文件操作函数,与用户程序的read、write等操作进行交互*/ minor USB_SKEL_MINOR_BASE; /*指向设备的次设备号,用于系统识别主设备号相同的设备(即一个驱动程序可以同时支持多个USB设备*/ id_table: skel_table; /*保存设备的厂商ID和产品ID,作为该设备的唯一标识,驱动程序向系统注册后,当下次插入时,系统根据这个标识查找正确的驱动程序,实现设备的即插即用*/ }; static struct file_operation skel_fops={ { owner:THIS_module, read:skel_read, write:skel_write, ioctl:skel_ioctl, open:skel_open, release:skel_release, }; (1)注册和注销 USB驱动程序注册,就是把在初始化函数中填好的use_driver结构作为参数传递给 use_register()函数即可,函数的调用方法为: result=u***_register(&skel_driver); 当要从系统卸载驱动程序时,也是将use_driver结构作为参数传递给u***_deregister 函数处理。 函数的调用格式为: static void __exit u***_skel_exit(void) { /* deregister this driver with the USB subsystem */ u***_deregister(&skel_driver); } module_exit(u***_skel_exit); 当USB设备插入时,为了使linux-hotplug(Linux中PCI、USB等设备热插拔支持)系统自动装载驱动程序,需要创建一个MODULE_device_TABLE。核心代码如下(这个模块仅支持某一特定设备): /* table of Devices that work with this driver */ static struct u***_device_id skel_table [] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_Product_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (u***, skel_table); USB_DEVICE宏利用厂商ID和产品ID提供了一个设备的唯一标识。当系统插入一个ID匹配的USB设备到USB总线时,驱动会在USB core中注册,驱动程序中probe 函数也就会被调用。u***_device 结构指针、接口号和接口ID都会被传递到函数中。 (2)probe()函数 probe()函数的编写格式为:static void * skel_probe(struct u***_device *dev, unsigned int ifnum, const struct u***_device_id *id);驱动程序需要确认插入的设备是否可以被接受,如果不接受,或者在初始化的过程中发生任何错误,probe()函数返回一个NULL值。否则返回一个含有设备驱动程序状态的指针,通过这个指针,就可以访问所有结构中的回调函数。 在驱动程序里,最后一点是要注册devfs(设备文件系统)。首先创建一个缓冲用来保存那些被发送给USB设备的数据和那些从设备上接受的数据,并为设备传输创建一个USB请求块(URB)以向设备写入数据,同时USB urb 被初始化,然后在devfs子系统中注册设备,允许devfs用户访问USB的设备。注册过程如下: /* initialize the devfs node for this device and register it */ sprintf(name, “skel%d”, skel-》minor); skel-》devfs = devfs_register (u***_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结构所定义的函数进行了。 (3)open()、write()和read()函数 首先,要打开此设备。在open()函数中MODULE_INC_USE_COUNT 宏是一个关键,它起到一个计数的作用,有一个用户态程序打开一个设备,计数器就加1。例如,以模块方式加入一个驱动,若计数器不为零,就说明仍然有用户程序在使用此驱动,这时候,就不能通过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()函数就可以收、发数据了。 read()函数首先从open()函数中保存的fi。 Write()函数和read()函数是完成驱动对读写等操作的响应。在skel_write中,一个FILL_BULK_URB函数,就完成了urb 系统callbak和的skel_write_bulk_callback之间的联系。注意skel_write_bulkcallback是中断方式,所以要注意时间不能太久,本程序中它就只是报告一些urb的状态等。 read 函数与write 函数稍有不同在于:程序并没有用urb 将数据从设备传送到驱动程序,而是用u***_bulk_msg 函数代替,这个函数能够不需要创建urbs 和操作urb函数的情况下,来发送数据给设备,或者从设备来接收数据。调用u***_bulk_msg函数并传到一个存储空间,用来缓冲和放置驱动收到的数据,若没有收到数据表示失败并返回一个错误信息。 u***_bulk_msg函数:当对u***设备进行一次读或者写时,u***_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 /* shutdown any bulk writes that might be going on */ u***_unlink_urb (skel-》write_urb); skel-》open_count = 0; } /* decrement our usage count for the module */ MOD_DEC_USE_COUNT; USB设备可以在任何时间点从系统中取走,即使程序目前正在访问它。USB驱动程序必须要能够很好地处理解决此问题,它需要能够切断任何当前的读写,同时通知用户空间程序:USB设备已经被取走。 2.设计实例 下面通过介绍键盘飞梭驱动程序的实例来让读者更好的理解USB驱动程序的工作原理,实现代码如下: /*需要的头文件*/ #include #include #include #include #include #include #include /* 驱动程序版本信息*/ #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/u***/devices中看到的值,通过cat/proc/bus/u***/devices得到当前系统探测到的USB总线上的设备信息。它包括Vendor、ProdID、Product等*/ MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); /*此结构来自内核中drivers/u***/u***kbd.c*/ struct u***_kbd { struct input_dev dev; struct u***_device *u***dev; unsigned char new[8]; unsigned char old[8]; struct urb irq, led; struct u***_ctrlrequest dr; unsigned char leds, newleds; char name[128]; int open; }; static void u***_kbd_irq(struct urb *urb) /*urb为USB请求块*/ { struct u***_kbd *kbd = urb-》context; int *new; new = (int *) kbd-》new; if(kbd-》new[0] == (char)0x01) { if(((kbd-》new[1]》》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,1); handle_scancode(0xe0,0); handle_scancode(0x4d,0); } } printk(“new=%x %x %x %x %x %x %x %x”, kbd-》new[0],kbd-》new[1],kbd-》new[2],kbd-》new[3], kbd-》new[4],kbd-》new[5],kbd-》new[6],kbd-》new[7]); } static void *u***_kbd_probe(struct u***_device *dev, unsigned int ifnum, const struct u***_device_id *id) { struct u***_interface *iface; struct u***_interface_descriptor *interface; struct u***_endpoint_descriptor *endpoint; struct u***_kbd *kbd; int pipe, maxp; iface = &dev-》actconfig-》interface[ifnum]; interface = &iface-》altsetting[iface-》act_altsetting]; if ((dev-》descriptor.idVendor != USB_HOTKEY_VENDOR_ID) || (dev-》descriptor.idProduct != USB_HOTKEY_PRODUCT_ID) || (ifnum != 1)) { return NULL; } if (dev-》actconfig-》bNumInterfaces != 2) { return NULL; } if (interface-》bNumEndpoints != 1) return NULL; endpoint = interface-》endpoint + 0; pipe = u***_rcvintpipe(dev, endpoint-》bEndpointAddress); maxp = u***_maxpacket(dev, pipe, u***_pipeout(pipe)); u***_set_protocol(dev, interface-》bInterfaceNumber, 0); u***_set_idle(dev, interface-》bInterfaceNumber, 0, 0); printk(KERN_INFO “GUO: Vid = %.4x, Pid = %.4x, Device = %.2x, ifnum = %.2x, bufCount = %.8x//n”, dev-》descriptor.idVendor,dev-》descriptor.idProduct,dev-》descriptor.bcdDevice, ifnum, maxp); if (!(kbd = kmalloc(sizeof(struct u***_kbd), GFP_KERNEL))) return NULL; memset(kbd, 0, sizeof(struct u***_kbd)); kbd-》u***dev = dev; FILL_INT_URB(&kbd-》irq, dev, pipe, kbd-》new, maxp 》 8 ? 8 : maxp, u***_kbd_irq,kbd, endpoint-》bInterval); kbd-》irq.dev = kbd-》u***dev; if (dev-》descriptor.iManufacturer) u***_string(dev, dev-》descriptor.iManufacturer, kbd-》name, 63); if (u***_submit_urb(&kbd-》irq)) { kfree(kbd); return NULL; } printk(KERN_INFO “input%d: %s on u***%d:%d.%d//n”, kbd-》dev.number, kbd-》name, dev-》bus-》busnum, dev-》devnum, ifnum); return kbd; } static void u***_kbd_disconnect(struct u***_device *dev, void *ptr) { struct u***_kbd *kbd = ptr; u***_unlink_urb(&kbd-》irq); kfree(kbd); } static struct u***_device_id u***_kbd_id_table [] = { { USB_DEVICE(USB_HOTKEY_VENDOR_ID, USB_HOTKEY_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (u***, u***_kbd_id_table); static struct u***_driver u***_kbd_driver = { name: “Hotkey”, probe: u***_kbd_probe, disconnect: u***_kbd_disconnect, id_table: u***_kbd_id_table, NULL, }; static int __init u***_kbd_init(void) { u***_register(&u***_kbd_driver); info(DRIVER_VERSION “:” DRIVER_DESC); return 0; } static void __exit u***_kbd_exit(void) { u***_deregister(&u***_kbd_driver); } module_init(u***_kbd_init); module_exit(u***_kbd_exit); 三、结语 USB规范是一门比较新的技术,接口使用方便,但是驱动程序的设计较复杂。上面介绍了USB设备驱动程序的设计,主要分析了主机端驱动程序的设计,并且给出了一个编写USB驱动程序的实例。 |
|
|
|
只有小组成员才能发言,加入小组>>
12131 浏览 2 评论
4466 浏览 3 评论
3696 浏览 5 评论
9524 浏览 47 评论
4499 浏览 9 评论
706浏览 0评论
507浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-21 22:51 , Processed in 0.792471 second(s), Total 84, Slave 65 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号