USB骨架程序(usb-skeleton),是USB驱动程序的基础,通过对它源码的学习和理解,可以使我们迅速地了解USB驱动架构,迅速地开发我们自己的USB硬件的驱动。
前言
在上篇《Linux下的硬件驱动--USB设备(上)(驱动配制部分)》中,我们知道了在Linux下如何去使用一些最常见的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骨架程序的关键几点如下: 1. 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函数就可以收、发数据了。 2. 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函数并传提一个存储空间,用来缓冲和放置驱动收到的数据,若没有收到数据,就失败并返回一个错误信息。 3. usb_bulk_msg函数 当对usb设备进行一次读或者写时,usb_bulk_msg 函数是非常有用的; 然而, 当你需要连续地对设备进行读/写时,建议你建立一个自己的urbs,同时将urbs 提交给usb子系统。 4. 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[0] == (char)0x01)和if(((kbd->;new[1]>;>;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等扩展键码。 在了解了此基本的驱动后,对于一个你已经拿到通讯协议的键盘所带手写板,你就应该能进行相应驱动的开发了吧。 程序见附录1:键盘飞梭驱动。 使用此驱动要注意的问题:在加载此驱动时你必须先把hid设备卸载,加载完usbhkey.o模块后再加载hid.o。因为若hid存在,它的probe会屏蔽系统去利用我们的驱动发现我们的设备。其实,飞梭本来就是一个hid设备,正确的方法,或许你应该修改hid的probe函数,然后把我们的驱动融入其中。 参考资料 1. 《LINUX设备驱动程序》 ALESSANDRO RUBINI著 LISOLEG 译 2. 《Linux系统分析与高级编程技术》 周巍松 编著 3. Linux Kernel-2.4.20源码和文档说明 附录1:键盘飞梭驱动
#include ; #include ; #include ; #include ; #include ; #include ; #include ; /* * 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[8]; unsigned char old[8]; struct urb irq, led; // devrequest dr; //这一行和下一行的区别在于kernel2.4.20版本对usb_kbd键盘结构定义发生了变化 struct usb_ctrlrequest dr; unsigned char leds, newleds; char name[128]; 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[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 *usb_kbd_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id) { struct usb_interface *iface; struct usb_interface_descriptor *interface; struct usb_endpoint_descriptor *endpoint; struct usb_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 = usb_rcvintpipe(dev, endpoint->;bEndpointAddress); maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); usb_set_protocol(dev, interface->;bInterfaceNumber, 0); usb_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 usb_kbd), GFP_KERNEL))) return NULL; memset(kbd, 0, sizeof(struct usb_kbd)); FILL_INT_URB(&kbd->;irq, dev, pipe, kbd->;new, maxp >; 8 ? 8 : maxp, usb_kbd_irq, kbd, endpoint->;bInterval); kbd->;irq.dev = kbd->;usbdev; if (dev->;descriptor.iManufacturer) usb_string(dev, dev->;descriptor.iManufacturer, kbd->;name, 63); if (usb_submit_urb(&kbd->;irq)) { kfree(kbd); return NULL; } printk(KERN_INFO "input%d: %s on usb%d:%d.%d\\n", kbd->;dev.number, kbd->;name, dev->;bus->;busnum, dev->;devnum, ifnum); static void usb_kbd_disconnect(struct usb_device *dev, void *ptr) { struct usb_kbd *kbd = ptr; usb_unlink_urb(&kbd->;irq); kfree(kbd); static struct usb_device_id usb_kbd_id_table [] = { { USB_DEVICE(USB_HOTKEY_VENDOR_ID, USB_HOTKEY_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, usb_kbd_id_table); static struct usb_driver usb_kbd_driver = { name: "Hotkey", probe: usb_kbd_probe, disconnect: usb_kbd_disconnect, id_table: usb_kbd_id_table, NULL, }; static int __init usb_kbd_init(void) { usb_register(&usb_kbd_driver); info(DRIVER_VERSION ":" DRIVER_DESC); return 0; } static void __exit usb_kbd_exit(void) { usb_deregister(&usb_kbd_driver); } module_init(usb_kbd_init); module_exit(usb_kbd_exit); |
在另外一台机上发现一个小问题。之前做的修改,其中的LOGO_REPEAT_RIGHT功能是把logo右边4像素宽的区域截取出来作为一个临时Image,然后把这个Image用类似tile blit的方式连续绘制到屏幕上作为logo栏的背景。正常情况下,logo显示时会覆盖在这个背景上,然而在那台机上logo却是以XOR的方式显示的,结果就显示得乱糟糟。而且试着用ROP_COPY来fillarea,看到的效果也是ROP_XOR的效果。于是只好在显示logo前先用fb_copyarea对要显示logo的区域进行操作,由于是XOR方式,所以把那个区域复制到自身的结果就是那段区域被清空了,接下来显示logo就正常了。修正后的代码diff如下: --- /gs2e/source/linux26-2edev/drivers/video/fbmem.c2006-08-29 14:07:15.000000000 +0800
+++ drivers/video/fbmem.c2006-10-13 10:18:46.000000000 +0800
@@ -375,9 +375,10 @@
int fb_show_logo(struct fb_info *info)
{
u32 *palette = NULL, *saved_pseudo_palette = NULL;
-unsigned char *logo_new = NULL;
-struct fb_image image;
-int x;
+unsigned char *logo_new = NULL, *border = NULL;
+struct fb_image image, imageborder;
+int x, xoffset;
+struct fb_copyarea region;
/* Return if the frame buffer is not mapped or suspended */
if (fb_logo.logo == NULL || info->state != FBINFO_STATE_RUNNING)
@@ -421,12 +422,57 @@
image.height = fb_logo.logo->height;
image.dy = 0;
+
+printk("Screen resolution:%d x %d\n", info->var.xres,info->var.yres);
+#ifdef CONFIG_LOGO_REPEAT_RIGHT
+//#if 0
+border = kmalloc(4 * fb_logo.logo->height, GFP_KERNEL);
+if (border != NULL){
+ for (x = 0; x < fb_logo.logo->height; x++){ /* use x as y */
+*((u32 *)border + x) = /* will repeat 4 pixels of the right side*/
+*(u32 *)(image.data + (x+1)*fb_logo.logo->width - 4);
+ }
+ imageborder.depth = 8;
+ imageborder.data = border;
+ imageborder.width = 4;
+ imageborder.height = image.height;
+ imageborder.dy = 0;
+ for (x = 0; x < info->var.xres; x+=4){
+imageborder.dx = x;
+info->fbops->fb_imageblit(info, &imageborder);
+ }
+ kfree(border);
+}
+#endif
+region.width = image.width;
+region.height = image.height;
+region.dy = 0;
+region.sy = 0;
+#ifndef CONFIG_CENTER_LOGO
+//#if 0
+xoffset = 0;
for (x = 0; x < num_online_cpus() * (fb_logo.logo->width + 8) &&
x <= info->var.xres-fb_logo.logo->width; x += (fb_logo.logo->width + 8)) {
+ region.dx = region.sx = x;
+ info->fbops->fb_copyarea(info, ®ion);
+/* XOR to blank. fix for cards that only support XOR mode - by Returner*/
image.dx = x;
info->fbops->fb_imageblit(info, &image);
}
-
+#else
+xoffset = (info->var.xres - num_online_cpus() * (fb_logo.logo->width + 8))>>1;
+//xoffset = (info->var.xres - 2 * (fb_logo.logo->width + 8))>>1;
+if (xoffset<0)xoffset = 0;
+ for (x = xoffset; x < num_online_cpus() * (fb_logo.logo->width + 8) + xoffset &&
+ //for (x = xoffset; x < 2 * (fb_logo.logo->width + 8) + xoffset &&
+ x <= info->var.xres-fb_logo.logo->width+xoffset; x += (fb_logo.logo->width + 8)) {
+region.dx = region.sx = x;
+info->fbops->fb_copyarea(info, ®ion);
+/* XOR to blank. fix for cards that only support XOR mode - by Returner*/
+image.dx = x;
+info->fbops->fb_imageblit(info, &image);
+}
+#endif
kfree(palette);
if (saved_pseudo_palette != NULL)
info->pseudo_palette = saved_pseudo_palette;
sqlite3嵌入式数据库的ARM-Linux移置详解
1.1 sqlite
简介(跳过
) 1.2 下载代码(跳过
) 1.3 交叉编译sqlite库准备工作:
1.
取得一个 arm-linux-gcc 的编译器。
2. sqlite
的源码 sqlite-3.0.8.tar.gz (本文以)好了,我们可以开始了。
这里设 arm-linux-gcc 在
/usr/local/arm-linux/bin/ 目录下解压sqlite-3.0.8.tar.gz 到/usr/local/arm-linux/sqlite
# tar zxvf sqlite-3.0.8.tar.gz -C /usr/local/arm-linux/sqlite # cd /usr/local/arm-linux/sqlite 将目录下的 configure 文件的
19206
行 { (exit 1); exit 1; }; }改为
{ (echo 1); echo 1; }; } 20107行 { (exit 1); exit 1; }; }改为
{ (echo 1); echo 1; }; } 20514行 { (exit 1); exit 1; }; }改为
{ (echo 1); echo 1; }; } 20540行 { (exit 1); exit 1; }; }改为
{ (echo 1); echo 1; }; } # mkdir /usr/local/arm-linux/sqlite-arm-linux # cd /usr/local/arm-linux/sqlite-arm-linux 设置交叉编译环境# export PATH=/usr/local/arm-linux/bin:$PATH
配置:
# ../sqlite/configure --host=arm-linux --prefix=/usr/local/arm-linux/sqlite-arm-linux 注意:这里不用写成了 "../sqlite/configure --host=arm-linux --prefix=/usr/local/arm-linux/sqlite-arm-linux/"这最后一个斜杠"/"不要带上了。如果一切顺利的话,会在/usr/local/arm-linux/sqlite-arm-linux目录下生成一些相关文件
: config.log config.status libtool Makefile sqlite3.pc 改/usr/local/arm-linux/sqlite-arm-linux/Makefile文件的 23行
BCC = arm-linux-gcc -g -O2
为
BCC = gcc -g -O2 编译安装
: # make # make install
注意: "# make install" 这一步将会在 /usr/local/arm-linux/sqlite-arm-linux/lib 生成库文件
# cd lib # file libsqlite3.so.0.8.6 libsqlite3.so.0.8.6: ELF 32-bit LSB shared object, ARM, version 1 (ARM), not stripped
此时生成的sqlite文件是还未strip过的,你可以使用命令“file sqlite”查看文件信息。用strip处理过后,将去掉其中的调试信息,执行文件大小也将小很多。
命令如下:
# arm-linux-strip libsqlite3.so.0.8.6
这样我们已经编译出了在ARM板上运行sqlite将/usr/local/arm-linux/sqlite-arm-linux/bin/目录下的 sqlite3 文件下载到你的arm板上,方法很多,你需要根据自己的情况来选择。如ftp,nfs,串口等。好,开始运行
chmod +wx sqlite [root@51Board var]# ./sqlite3 zieckey.db ./sqlite3: error while loading shared libraries: libsqlite3.so.0: cannot open shared object file: No such fileor directory这里是因为刚刚编译时编译的是动态连接库形式的,所有我们还的将库文件下载到ARM板上。
将 /usr/local/arm-linux/sqlite-arm-linux/lib 目录下所有文件下到ARM板上。再次运行,
[root@51Board var]# ./sqlite3 zieckey.db ./sqlite3: error while loading shared libraries: libsqlite3.so.0: cannot open shared object file: No such fileor directory
还是出错,哦,我们没有设置环境变量,假设我们下在库文件在ARM板上的 /usr/qpe/lib/ 目录下,
这里设置环境就像下面:
[root@51Board var]# export LD_LIBRARY_PATH=/usr/qpe/lib:$LD_LIBRARY_PATH好了这样就可以运行了:
[root@51Board var]# ./sqlite3 aa.db SQLite version 3.0.8Enter ".help" for instructions sqlite>
看见sqlite>提示符号没有?成功了。哈哈!!
sqlite>.help好了。
现在
sqlite
已经在
arm-linux
下跑了起来
想自己弄块2410 的板子,为此花了3周时间研究公版原理图、画原理图,可是现在2410的6层核心板自己做花不来呀,自己做一块6层的核心板最少要1500,再加上底板的话就要2000了,1500大洋就可以买块不错的板子,自己做的话还不如买块2410的开发板。唉,郁闷!!!
Linux内核的配置系统由三个部分组成,分别是: Makefile:分布在 Linux 内核源代码中的 Makefile,定义 Linux 内核的编译规则; 配置文件(config.in):给用户提供配置选择的功能; 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和 make xconfig)。 这些配置工具都是使用脚本语言,如 Tcl/TK、Perl 编写的(也包含一些用 C 编写的代码)。本文并不是对配置系统本身进行分析,而是介绍如何使用配置系统。所以,除非是配置系统的维护者,一般的内核开发者无须了解它们的原理,只需要知道如何编写 Makefile 和配置文件就可以。所以,在本文中,我们只对 Makefile 和配置文件进行讨论。另外,凡是涉及到与具体 CPU 体系结构相关的内容,我们都以 ARM 为例,这样不仅可以将讨论的问题明确化,而且对内容本身不产生影响。 2. Makefile 2.1 Makefile 概述 Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 Linux 内核二进制文件。 由于 Linux 内核源代码是按照树形结构组织的,所以 Makefile 也被分布在目录树中。Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有:
Makefile:顶层 Makefile,是整个内核配置、编译的总体控制文件。 .config:内核配置文件,包含由用户选择的配置选项,用来存放内核配置后的结果(如 make config)。 arch/*/Makefile:位于各种 CPU 体系目录下的 Makefile,如 arch/arm/Makefile,是针对特定平台的 Makefile。 各个子目录下的 Makefile:比如 drivers/Makefile,负责所在子目录下源代码的管理。 Rules.make:规则文件,被所有的 Makefile 使用。 用户通过 make config 配置后,产生了 .config。顶层 Makefile 读入 .config 中的配置选择。顶层 Makefile 有两个主要的任务:产生 vmlinux 文件和内核模块(module)。为了达到此目的,顶层 Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile。至于到底进入哪些子目录,取决于内核的配置。在顶层 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 体系结构下的 Makefile,这个 Makefile 中包含了平台相关的信息。 位于各个子目录下的 Makefile 同样也根据 .config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有 include $(TOPDIR)/Rules.make。 Rules.make 文件起着非常重要的作用,它定义了所有 Makefile 共用的编译规则。比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在 Makefile 中有以下的编译规则: %.s: %.c $(CC) $(CFLAGS) -S $< -o $@
有很多子目录下都有同样的要求,就需要在各自的 Makefile 中包含此编译规则,这会比较麻烦。而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中,并在各自的 Makefile 中包含进了 Rules.make(include Rules.make),这样就避免了在多个 Makefile 中重复同样的规则。对于上面的例子,在 Rules.make 中对应的规则为: %.s: %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $< -o $@
2.2 Makefile 中的变量 顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。 常用的变量有以下几类: 1) 版本信息 版本信息有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。版本信息定义了当前内核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk7 2) CPU 体系结构:ARCH 在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。 3) 路径信息:TOPDIR, SUBDIRS TOPDIR 定义了 Linux 内核源代码所在的根目录。例如,各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。 SUBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值,参见4)中的例子。 4) 内核组成信息:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS Linux 内核文件 vmlinux 是由以下规则产生的: vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o --start-group $(CORE_FILES) $(DRIVERS) $(NETWORKS) $(LIBS) --end-group -o vmlinux 可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中,HEAD在arch/*/Makefile 中定义,用来确定被最先链接进 vmlinux 的文件列表。比如,对于 ARM 系列的 CPU,HEAD 定义为: HEAD := arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.o 表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。PROCESSOR 为 armv 或 armo,取决于目标 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。 CORE_FILES 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充: # arch/arm/Makefile # If we have a machine-specific directory, then include it in the build. MACHDIR := arch/arm/mach-$(MACHINE) ifeq ($(MACHDIR),$(wildcard $(MACHDIR))) SUBDIRS += $(MACHDIR) CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES) endif HEAD := arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.o SUBDIRS += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe CORE_FILES := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES) LIBS := arch/arm/lib/lib.a $(LIBS)
5) 编译信息:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS 在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了 CROSS_COMPILE。比如: CROSS_COMPILE = arm-linux- CC = $(CROSS_COMPILE)gcc LD = $(CROSS_COMPILE)ld ...... CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux- 开头的,所以在各个交叉编译器工具之前,都加入了 $(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如 arm-linux-gcc。 CFLAGS 定义了传递给 C 编译器的参数。 LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义,比如: # arch/arm/Makefile LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds
6) 配置变量CONFIG_* .config 文件中有许多的配置变量等式,用来说明用户配置的结果。例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。 .config 被顶层 Makefile 包含后,就形成许多的配置变量,每个配置变量具有确定的值:y 表示本编译选项对应的内核代码被静态编译进 Linux 内核;m 表示本编译选项对应的内核代码被编译成模块;n 表示不选择此编译选项;如果根本就没有选择,那么配置变量的值为空。 2.3 Rules.make 变量 前面讲过,Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。 O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。 M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。 O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用 $(LD) -r 把它们链接成一个 O_TARGET 或 L_TARGET。O_TARGET 以 .o 结尾,而 L_TARGET 以 .a 结尾。
ARM系统的JTAG接口的设计不当往往使硬件系统无法调试,所以在设计ARM系统前要先熟悉ARM系统的JTAG接口的定义和常见问题。
|
|
1.ARM系统的JTAG接口是如何定义的? 每个PIN又是如何连接的? |
下图是JTAG接口的信号排列示意:
|
|
接口是一个20脚的IDC插座。下表给出了具体的信号说明:
|
表 1 JTAG引脚说明
|
序号
|
信号名
|
方向
|
说 明
|
1
|
Vref
|
Input
|
接口电平参考电压,通常可直接接电源
|
2
|
Vsupply
|
Input
|
电源
|
3
|
nTRST
|
Output
|
(可选项) JTAG复位。在目标端应加适当的上拉电阻以防止误触发。
|
4
|
GND
|
--
|
接地
|
5
|
TDI
|
Output
|
Test Data In from Dragon-ICE to target.
|
6
|
GND
|
--
|
接地
|
7
|
TMS
|
Output
|
Test Mode Select
|
8
|
GND
|
--
|
接地
|
9
|
TCK
|
Output
|
Test Clock output from Dragon-ICE to the target
|
10
|
GND
|
--
|
接地
|
11
|
RTCK
|
Input
|
(可选项) Return Test Clock。由目标端反馈给Dragon-ICE的时钟信号,用来同步TCK信号的产生。不使用时可以直接接地。
|
12
|
GND
|
--
|
接地
|
13
|
TDO
|
Input
|
Test Data Out from target to Dragon-ICE.
|
14
|
GND
|
--
|
接地
|
15
|
nSRST
|
Input/Output
|
(可选项) System Reset,与目标板上的系统复位信号相连。可以直接对目标系统复位,同时可以检测目标系统的复位情况。为了防止误触发,应在目标端加上适当的上拉电阻。
|
16
|
GND
|
--
|
接地
|
17
|
NC
|
|
保留
|
18
|
GND
|
--
|
接地
|
19
|
NC
|
--
|
保留
|
20
|
GND
|
--
|
接地
|
|
|
2.目标系统如何设计? |
目标板使用与Dragon-ICE一样的20脚针座,信号排列见表1。RTCK和 nTRST这两个信号根据目标ASIC有否提供对应的引脚来选用。nSRST则根据目标系统的设计考虑来选择使用。下面是一个典型的连接关系图:
|
复位电路中可以根据不同的需要包含上电复位、手动复位等等功能。如果用户希望系统复位信号nSRST能同时触发JTAG口的复位信号nTRST,则可以使用一些简单的组合逻辑电路来达到要求。后面给出了一种电路方案的效果图。
图 3 一个复位电路结构的例子
在目标系统的PCB设计中,最好把JTAG接口放置得离目标ASIC近一些,如果这两者之间的连线过长,会影响JTAG口的通信速率。 另外电源的连线也需要加以额外考虑,因为Dragon-ICE要从目标板上吸取超过100mA的大电流。最好能有专门的敷铜层来供电,假如只能使用连线供电的话,最小线宽不应小于10mil (0.254mm)。
|
|
3. 14脚JTAG如何与20JTAG连接? |
Dragon-ICE使用工业标准的20脚JTAG插头,但是有些老的系统采用一种14脚的插座
|
。这两类接口的信号排列如下:
|
|
这两类接口之间的信号电气特性都是一样的,因此可以把对应的信号直接连起来进
|
行转接。Dragon-ICE配备这种转接卡,随机配备。
|
我们在阅读linux源代码时都有这样的体会:核心的组织相对松散, 在看一个文件时往往要牵涉到其他的头文件、源代码文件。如此来回 跳转寻找变量、常量、函数的定义十分不方便,这样折腾几次,便使 读代码的心情降到了低点。 lxr(linux cross reference)就是一个解决这个问题的工具:他对 你指定的源代码文件建立索引数据库,利用perl脚本CGI动态生成包含 源码的web页面,你可以用任何一种浏览器查阅。在此web页中,所有 的变量、常量、函数都以超连接的形式给出,十分方便查阅。比如你 在阅读/usr/src/linux/net/socket.c的源代码,发现函数 get_empty_inode不知道是如何以及在哪里定义的,这时候你只要点击 get_empty_inode,lxr将返回此函数的定义、实现以及各次引用是在什 么文件的哪一行,注意,这些信息也是超连接,点击将直接跳转到相应 的文件相应的行。另外lxr还提供标识符搜索、文件搜索,结合程序 glimpse还可以提供对所有的源码文件进行全文检索,甚至包括注释! 下面将结合实例介绍一下lxr和glimpse的基本安装和使用,由于 glimpse比较简单,就从它开始: 首先访问站点: http://glimpse.cs.arizona.edu/ 得到glimpse 的源码,比如我得到的是glimpse-4.12.5.tar.gz . 用root登录,在 任一目录下用tar zxvf glimpse-4.12.5.tar.gz解开压缩包,在当前 目录下出现新目录glimpse-4.12.5 .进入该目录,执行make即可。进 入bin目录,将文件glimpse和glimpseindex拷贝到/bin或/usr/bin下 即可。如果单独使用glimpse,那么只要简单的执行glimpseindex foo 即可,其中foo是你想要索引的目录,比如说是/usr/src/linux .glimpseindex 的执行结果是在你的起始目录下产生若干.glimpse*的索引文件。然后 你只要执行glimpse yourstring即可查找/usr/src/linux下所有包含 字符串yourstring的文件。 对于lxr,你可以访问lxr.linux.no得到它的源代码解包后,遵循如下步骤: /*下面的文字来源于lxr的帮助文档以及本人的安装体会*/ 1)修改Makefile中的变量PERLBIN和INSTALLPREFIX,使它们分别为 perl程序的位置和你想lxr安装的位置.在我的机器上,PERLBIN的值为 /usr/bin/perl .至于INSTALLPREFIX,有如下原则,lxr的安装路径 必须是web服务器能有权限访问。因此它的值简单一点可取 /home/httpd/html/lxr (对于Apache web server)。 2)执行 make install 3)修改$INSTALLPREFIX/http/lxr.conf : baseurl : http://yourIP/lxr/http/ htmlhead: /home/httpd/html/lxr/http/template-head htmltail: /home/httpd/html/lxr/http/template-tail htmldir: /home/httpd/html/lxr/http/template-dir sourceroot : /usr/src/linux # 假如对linux核心代码索引 dbdir : /home/httpd/html/lxr/dbdir/ #dbdirk可任意起名,且位置任意 glimpsebin: /usr/bin/glimpse #可执行程序glimpse的位置 4)在$INSTALLPREFIX/http/下增加一个文件.htaccess 内容: <Files ~ (source|search|ident|diff|find)$> *** SetHandler cgi-script </Files> 上面这个文件保证Apache server将几个perl文件作为cgi-script. 5)按照lxr.conf中的设置建立dbdir ,按照上例,建立目录 /home/httpd/html/lxr/dbdir 进入这个目录执行$INSTALLPREFIX/bin/genxref yourdir 其中yourdir是源码目录,比如/usr/src/linux 如果要结合glimpse,则执行glimpseindex -H . yourdir 6)修改 /etc/httpd/conf/access.conf ,加入 <Directory /home/httpd/html/lxr/http> Options All AllowOverride All order allow,deny allow from all </Directory> 7)进入/etc/rc.d/init.d/ 执行 killall httpd ./httpd start 进入X ,用浏览器 http://yourIP/lxr/http/blurb.html 大功告成 ,这下你可以舒心的读源码了。 注意:以上只是lxr和glimpse的基本用法,进一步的说明可以参考连机文档。
引言
通用串行总线(USB)是一种快速而灵活地连接配件与计算机工作站的接口,其应用非常广泛。Linux中除了包含对USB主机控制器的驱动,还含有USB设备控制器,尤其是集成在StrongARM SA1110处理器上的控制器的驱动。这些控制器驱动通过使用USB可使基于Linux的嵌入式系统与主机 (运行的可以是Linux,或不是)进行通信。这里提供三种方法给运行Linux操作系统的嵌入式系统增加USB支持,可采用其中一种与USB主机展开通信。 第一种,最复杂的设备采用专门编写的内核模块解析标准USB总线上通行的错综复杂的高层协议;相应的USB主机定制驱动和应用程序来完成连接。第二种,有些基于Linux的设备把总线当作一种简单的运行在主机上的点对点串行连接使用;主机应用程序采用主机操作系统提供的USB编程界面,而其外在表现则仿佛是在通过一种典型的串行端口进行通信。第三种,另有一些设备把USB看作一种以太网络,它们用主机作网关,把USB设备与办公LAN或 Internet相连接。通常的做法是使用专门的主机驱动实现它。 最佳方案的选择取决于研发所需时间,以及针对具体嵌入式应用,要把USB接口作成什么样。以下对这三种方法如何在基于Linux的USB设备上的应用逐一进行描述。本文是关于如何在基于Linux的照相机和PDA之类的USB设备上使用Linux的论述,在此,USB是指由方形连接器而非扁平矩形连接器构成的USB设备。 内核模块
把USB加到基于Linux的设备上的第一种方法是编写一个定制的Linux内核模块。这种方法通常要求相应开发主机操作系统(Windows、Linux以及其它OS)的驱动。 借助定制内核模块在设备中的安装,可以进行文件系统仿真等,使嵌入式应用将其USB主机当作远程存储设备对待。这一方法的另一潜在用途是构成一种存储转发字符设备,从嵌入式应用程序中缓冲数据流,直到USB主机连接完成建立为止。 对于基于StrongARM的Linux设备,其USB应用内核模块调用sa1100_usb_open(),对管理芯片的板上USB设备控制器外设的内核代码进行初始化。然后该模块调用sa1100_usb_get_descriptor_ptr()和 sa1100_usb_set_string_descriptor(),通过枚举过程对USB主机的给定USB描述符进行设置。这些描述符包括设备供货商及产品的数字标识符、正文字符串等主机可用来对设备进行识别的信息。甚至有一个序列号域,以便主机唯一地识别设备或对USB上相同设备的多个实例加以区分。 内核模块必须在开始USB通信前完成USB描述符的建立,这是因为枚举过程由USB设备控制器驱动,一旦USB主机连上后会自动执行。一切准备就绪后,USB设备模块便调用sa1100_usb_start(),告诉内核接受来自主机的USB连接请求。如果模块在USB主机连上前调用 sa1100_set_configured_ callback(),那么内核将会在枚举过程结束时调用所提供的回调函数。回调函数能很好地对设备完成连接状态进行可视化指示。
如果USB通信不再需要,那么设备的内核模块便调用sa1100_usb_stop(),然后是 sa1100_usb_close(),关闭SA1100的USB控制器。
StrongARM USB控制器支持数据传输作业的bulk-in 和bulk-out。在从USB主机接收数据包时,内核模块调用sa1100_usb_recv(),把数据缓冲区和回调函数地址传递给它。然后内核的底层USB设备控制代码对来自主机的bulk-out包进行检索,把内容放于缓冲区中,并调用回调函数。
回调函数必须从接收缓冲区提取数据并保存于其它位置或者把缓冲区空间加到一个队列中,为下一个数据包的接收分配新的缓冲区。而后回调函数二次调用sa1100_usb_recv(),在需要时进行下一个数据包的接收。过程与对USB主机的数据传输相类似。在聚集起一帧的数据量后,内核模块将数据的地址、长度和回调地址传递给 sa1100_usb_send()。传输完成时,内核调用回调函数。
主机
主机端USB驱动的几个例子在主流的Linux版本以及 Linux内核档案组织分配的原始内核源中都有提供。用于Handspring Visor(drivers/usb/serial/visor.c)的模块是编写较为简洁易懂的模块之一,作为USB主机端模块的模板 (drivers/usb/usb-skeleton.c)使用。
高速串行
对于大多数实际应用来说, 可以把USB总线当作一种高速串行端口考虑。如此在某些类型的嵌入式设备和应用中对它进行原型模拟是有意义的。StrongARM处理器的Linux内核提供现成的USB设备驱动专工于此,称作usb-char。
在希望与USB主机通信时,Linux USB设备应用程序只是打开对其usb-char设备节点(字符型,最大10,最小240)的连接,然后开始读写数据即可。read()和 write()操作将一直返回错误值直到USB主机连上为止。一旦连接建立和枚举完成,便开始通信,就像USB是一种点对点串行端口一样。
由于这种USB数据传递方法十分直接且实用,因此usb-char设备得到高效使用。它还为其它USB通信方法的实现提供了重要的参照基准。
usb-char的实际动作从usbc_open()功能开始,部分内容示于列表1中。
列表1:打开USB上的串行连接
static int usbc_open(struct inode *pInode, struct file *pFile)
{
int retval = 0;
/* start usb core */
sa1100_usb_open(_sb-char?;
/* allocate memory for in-transit USB packets */
tx_buf = (char*) kmalloc(TX_PACKET_SIZE, GFP_KERNEL | GFP_DMA);
packet_buffer = (char*) kmalloc(RX_PACKET_SIZE, GFP_KERNEL | GFP_DMA);
/* allocate memory for the receive buffer; the contents of this
buffer are provided during read() */
rx_ring.buf = (char*) kmalloc(RBUF_SIZE, GFP_KERNEL);
/* set up USB descriptors */
twiddle_descriptors();
/* enable USB i/o */
sa1100_usb_start();
/* set up to receive a packet */
kick_start_rx();
return 0; twiddle_descriptors ()功能建立起设备的USB描述符。在描述符全部建起后,准备从USB主机枚举并接收一个数据帧。kick_start_rx()所需的代码大多数情况下只是一种对sa1100_usb_recv() 的调用以建立回调而已。当USB主机发送数据包时,设备的内核通过回调调用rx_done_callback_packet_buffer()函数,把数据包的内容移入usb-char 设备点上由read()返回的FIFO队列。
主机
对于运行Linux的USB主机,usb-char相应的USB主机模块称为usbserial模块。大多数Linux版本都包括Usbserial模块,尽管通常不是自动装入。在USB与设备的连接建立之前,usbserial 由modprobe 或 insmod载入。
一旦USB设备开始枚举,主机上的应用程序便用usbserial设备点(字符型,最大188,最小0以上)之一与设备进行通信。这些节点通常命名为/dev/ttyUSBn。Usbserial模块在内核报文日志记录中报告它把哪个节点指定给USB设备使用:
usbserial.c: 通用转换器删除
usbserial.c: 通用转换器当前连到ttyUSB0上。连接建立后,USB主机上的应用程序便通过读写指定的节点与USB设备进行通信。
Linux 主机上usbserial模块的一种替代选择是一种称作libusb(libusb.sourceforge.net)的库。这种库使用低层内核系统调用进行USB数据传输,而不是通过usbserial模块,在某种程度上跨Linux内核版本建立和使用时更方便。Libusb库还提供大量有用的调试功能,这一点在对运行在USB链路上的复杂通信协议进行除错时有帮助。用libusb与采用usb-char的USB设备进行通信时,Linux主机应用程序使用usb_open()函数建立与该设备的连接。然后应用程序使用usb_bulk_read()和usb_bulk_write()与设备交换数据。
USB上的以太网
另一种选择是把USB作为一种以太网络来对待。Linux具有在主机和设备端均可实现这种功能的模块。由于iPAQ硬件既没有可接入的串行端口也没有一种专用的网络接口,因此,iPAQ 的Linux内核专门采用这种通信策略,在StrongARM的Linux内核中,usb-eth模块(arch/arm/mach- sa1100/usb-eth.c)对用USB作物理媒介的虚构以太网设备进行仿真。一旦创建后,这一网络界面便被指定一个IP地址,否则作为通常的以太网硬件对待。一旦USB主机连上后,usb-eth模块便能使USB设备“看到” Internet(如果存在Internet的话),ping测其它IP地址,甚至“谈论”DHCP, HTTP, NFS, telnet, 和e-mail。简言之,任何在实际的以太网界面上运行的应用将不折不扣地在usb-eth接口上得到实现,因为它们不能分辨出其正在使用的不是实在的以太网硬件。
主机
在Linux主机上,相应的Ethernet-over-USB内核模块称为usbnet。当usbnet模块得到安装且设备的USB连接建立完成时,usbnet模块便针对主机端内核及用户应用创建一个与实际硬件酷似的虚构以太网界面,主机端应用程序通过运行设备IP地址 ping测,可以检查USB设备的存在。如果ping测成功,设备便加上了。
摘要: 1.1
硬件系统介绍
1.1.1
... 阅读全文
|