和i2c驱动类似,usb驱动架构中也有usb-core这样的框架,为上层设备驱动程序提供封装后的方便使用的api函数,为底层与硬件相关的代码封装提供总线接入功能。
撇开复杂的usb协议,我们先认知linux驱动中的usb框架,至于细节的实现,需要漫长的代码。在我们还不知道它长什么样的时候就去讨论内部的实现机制就毫无意义的。
图1
准确的说,图1是不完整的usb驱动架构,但是有助于对架构的理解。不光是i2c,linux均是以这样的架构呈现。
对于我们初学者来说,先接触一下usb协议的基本概念,然后在分析一个代表性的usb设备驱动,这样是一个有效的学习usb驱动的途径。
之后,在深入usb协议再分析usb-core。
linux内核源码中的/drivers/usb/usb-skeleton.c 文件为我们提供了一个最基础的usb驱动程序,即usb骨架程序,可以看做这是一个最简单的usb设备驱动实例,尽管具体的usb设备驱动千差万别,但其骨架则万变不离其宗。
我们的usb驱动开发也从他开始。
linux USB驱动程序需要做的第一件事件就是在linux usb子系统里注册,这个子系统就是usb-core,同事提供一些相关信息,例如这个驱动程序支持哪种设备,当被支持的设备从系统插入或者拔出时,会有哪些动作,所有这些信息都传送到usb子系统中,在usb-skeleton.c 中是这样表示的。
static struct usb_driver skel_driver = { .name = "skeleton", .probe = skel_probe, .disconnect = skel_disconnect, .id_table = skel_table, }; static int __init usb_skel_init(void) { int result; /* register this driver with the USB subsystem */ result = usb_register(&skel_driver); if (result) err("usb_register failed. Error number %d", result); return result; } static void __exit usb_skel_exit(void) { /* deregister this driver with the USB subsystem */ usb_deregister(&skel_driver); } module_init (usb_skel_init); module_exit (usb_skel_exit);
其实在2.4的内核中struct usb_driver skel_driver中还有file_operations的一个成员(现在被移到了usb_class_driver中),大多数usb驱动程序需要钩住另外一个驱动系统,usb驱动往往不是单独存在的,例如scsi、网络(usb网卡)、或者tty(usb转串口)子系统,这些驱动程序在其他驱动系统中注册,同事任何用户空间的交互操作(open、read、write、ioctl等)通过那些借口提供,比如我们把scsi设备驱动作为我们usb驱动钩住的另外一个驱动系统,那么我们此sub设备的read、write等操作,就相应按scsi设备的read、write函数进行访问。但如果没有一个匹配的驱动系统可以使用时,那我们就要自己处理与用户空间的read、write等交互函数,在usb_class_driver中提供注册file_operations的函数指针,这样就可以与用户空间实现方便的交互。
当usb设备插入时,为了使hotplug系统自动装载驱动程序,还需要创建一个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 */ };USB_SKEL_VENDOR_ID和USB_SKEL_PRODUCT_ID为厂商的ID和产品ID,为我么提供了一个设备的唯一标识,当系统插入一个id匹配的usb设备到usb总线时,驱动会在usb-core中注册,驱动程序的skel_probe函数也会被调用。
static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)驱动程序需要确认插入的设备是否可以被接受,如果不接受,或者再初始化的过程中发生任何错误,skel_probe会返回一个错误,否则将使用usb-core的usb_register_dev注册设备。
static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id) { ... /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); ... /* we can register the device now, as it is ready */ retval = usb_register_dev(interface, &skel_class); if (retval) { goto error; } ... error: if (dev) kref_put(&dev->kref, skel_delete); return retval; }
static struct file_operations skel_fops = { .owner = THIS_MODULE, .read = skel_read, .write = skel_write, .open = skel_open, .release = skel_release, }; static struct usb_class_driver skel_class = { .name = "skel%d", .fops = &skel_fops, .minor_base = USB_SKEL_MINOR_BASE, };由于usb-skeleton没有钩住任何驱动子系统,因此这里实现自己的用户交互接口,在字符驱动中所数字的file_operations。
现在,usb-skeleton驱动就已经和设备绑定上了,任何用户态程序要操作此设备都可以通过file_operations结构所定义的函数进行了,
在skel_write中,有一个usb_fill_bulk_urb函数,实现urb系统callbak和我们自己的skel_write_bulk_callback回调函数。然后调用usb_submit_urb函数想目标端口提交写入的数据。
read函数与write函数稍有不同在于,read并没有用urb将数据从设备传送到驱动程序,而是用usb_bulk_msg函数代替,所以read能够不需要创建urbs和操作urb函数的情况下来读取设备数据到驱动程序,或者发送数据给设备。当对usb设备进行一次读或者写是,usb_bulk_msg函数是非常有用的,然后,当你需要连续的对设备进行读写是,最好自己建立自己的urbs。下面是代码展示。
static ssize_t skel_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { ... retval = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), dev->bulk_in_buffer, min(dev->bulk_in_size, count), &bytes_read, 10000); ... copy_to_user(buffer, dev->bulk_in_buffer, bytes_read); ... return retval; }
static ssize_t skel_write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos) { ... usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), buf, writesize, skel_write_bulk_callback, dev); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* send the data out the bulk port */ retval = usb_submit_urb(urb, GFP_KERNEL); ... return retval; }写成功后的回调函数,用于释放空间
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs) { ... /* free up our allocated buffer */ usb_buffer_free(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); up(&dev->limit_sem); }usb骨架驱动程序,提供足够的例子来帮助初始人员在最短的时间里开发一个驱动程序。也是学习usb驱动的最佳开始。
有了上面的理解,usb驱动架构可以如图2所示