初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了。好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发。但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催货。当然,还会顺带提一下其他的驱动程序写法。

  事实上,Linux的设备驱动都遵循一个惯例——表征驱动程序(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程序所需要的所有资源。用术语来说,就是这个驱动器对象所拥有的属性及成员。由于Linux的内核用c来编写,所以我们也按照这种结构化的思想来分析代码,但我还是希望从OO的角度来阐述这些细节。这个结构体的名字有驱动开发人员决定,比如说,鼠标可能有一个叫做mouse_dev的struct,键盘可能由一个keyboard_dev的struct(dev for device,我们做的只是设备驱动)。而这次我们来分析一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),自然,他定义的设备结构体就叫做usb-skel:

  struct usb_skel {

  struct usb_device * udev; /* the usb device for this device */

  struct usb_interface * interface; /* the interface for this device */

  struct semaphore limit_sem; /* limiting the number of writes in progress */

  unsigned char * bulk_in_buffer; /* the buffer to receive data */

  size_t bulk_in_size; /* the size of the receive buffer */

  __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */

  __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */

  struct kref kref;

  };

  这里我们得补充说明一下一些USB的协议规范细节。USB能够自动监测设备,并调用相应得驱动程序处理设备,所以其规范实际上是相当复杂的,幸好,我们不必理会大部分细节问题,因为Linux已经提供相应的解决方案。就我现在的理解来说,USB的驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已经做好了,我们可以不管,但我们至少要了解他的功能。形象得说,USB的bus驱动相当于铺出一条路来,让所有的信息都可以通过这条USB通道到达该到的地方,这部分工作由usb_core来完成。当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或者是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备。里面复杂细节我们不用管,我们要做的是另一块工作——usb的设备驱动。也就是说,我们就等着usb_core告诉我们要工作了,我们才工作。

  从开发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又可以有多个接口(interface),每个接口又有多个设置(setting图中没有给出),而接口本身可能没有端点或者多个端点(end point)。USB的数据交换通过端点来进行,主机与各个端点之间建立起单向的管道来传输数据。而这些接口可以分为四类:

  控制(control)

  用于配置设备、获取设备信息、发送命令或者获取设备的状态报告

  中断(interrupt)

  当USB宿主要求设备传输数据时,中断端点会以一个固定的速率传送少量数据,还用于发送数据到USB设备以控制设备,一般不用于传送大量数据。

  批量(bulk)

  用于大量数据的可靠传输,如果总线上的空间不足以发送整个批量包,它会被分割成多个包传输。

  等时(isochronous)

  大量数据的不可靠传输,不保证数据的到达,但保证恒定的数据流,多用于数据采集。

  Linux中用struct usb_host_endpoint来描述USB端点,每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息以及设备自定义的各种信息,这些信息包括:

  bEndpointAddress(b for byte)

  8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定。

  bmAttributes

  端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK可以确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。

  wMaxPacketSize

  端点一次处理的最大字节数。发送的BULK包可以大于这个数值,但会被分割传送。

  bInterval

  如果端点是中断类型,该值是端点的间隔设置,以毫秒为单位。

  在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程序来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,以下是该结构体中比较重要的字段:

  struct usb_host_interface *altsetting(注意不是usb_interface)

  其实据我理解,他应该是每个接口的设置,虽然名字上有点奇怪。该字段是一个设置的数组(一个接口可以有多个设置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。

  unsigned num_altstting

  可选设置的数量,即altsetting所指数组的元素个数。

  struct usb_host_interface *cur_altsetting

  当前活动的设置,指向altsetting数组中的一个。

  int minor

  当捆绑到该接口的USB驱动程序使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。

  除了它可以用struct usb_host_config来描述之外,到现在为止,我对配置的了解不多。而整个USB设备则可以用struct usb_device来描述,但基本上只会用它来初始化函数的接口,真正用到的应该是我们之前所提到的自定义的一个结构体。

  好,了解过USB一些规范细节之后,我们现在来看看Linux的驱动框架。事实上,Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。要写一个Linux的模块并不复杂,以一个helloworld为例:

  #include

  #include

  MODULE_LICENSE(“GPL”);

  static int hello_init(void)

  {

  printk(KERN_ALERT “Hello World!\n”);

  return 0;

  }

  static int hello_exit(void)

  {

  printk(KERN_ALERT “GOODBYE!\n”);

  }

  module_init(hello_init);

  module_exit(hello_exit);

  这个简单的程序告诉大家应该怎么写一个模块,MODULE_LICENSE告诉内核该模块的版权信息,很多情况下,用GPL或者BSD,或者两个,因为一个私有模块一般很难得到社区的帮助。module_init和module_exit用于向内核注册模块的初始化函数和模块推出函数。如程序所示,初始化函数是hello_init,而退出函数是hello_exit。

  另外,要编译一个模块通常还需要用到内核源码树中的makefile,所以模块的Makefile可以写成:

  ifneq ($(KERNELRELEASE),)

  obj-m:= hello.o#usb-dongle.o

  else

  KDIR:= /usr/src/linux-headers-$(shell uname -r)

  BDIR:= $(shell pwd)

  default:

  $(MAKE) -C $(KDIR) M=$(PWD) modules

  .PHONY: clean

  clean:

  make -C $(KDIR) M=$(BDIR) clean

  endif

可以用insmod跟rmmod来验证模块的挂在跟卸载,但必须用root的身份登陆命令行,用普通用户加su或者sudo在Ubuntu上的测试是不行的。

  下面分析一下usb-skeleton的源码。这个范例程序可以在linux-2.6.17/drivers/usb下找到,其他版本的内核程序源码可能有所不同,但相差不大。大家可以先找到源码看一看,先有个整体印象。

  之前已经提到,模块先要向内核注册初始化跟销毁函数:

  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);

  MODULE_LICENSE("GPL");

  从代码开来,这个init跟exit函数的作用只是用来注册驱动程序,这个描述驱动程序的结构体是系统定义的标准结构struct usb_driver,注册和注销的方法很简单,usb_register(struct *usb_driver), usb_deregister(struct *usb_driver)。那这个结构体需要做些什么呢?他要向系统提供几个函数入口,跟驱动的名字:

  static struct usb_driver skel_driver = {

  .name = "skeleton",

  .probe = skel_probe,

  .disconnect = skel_disconnect,

  .id_table = skel_table,

  };

  从代码看来,usb_driver需要初始化四个东西:模块的名字skeleton,probe函数skel_probe,disconnect函数skel_disconnect,以及id_table。

  在解释skel_driver各个成员之前,我们先来看看另外一个结构体。这个结构体的名字有开发人员自定义,它描述的是该驱动拥有的所有资源及状态:

  struct usb_skel {

  struct usb_device * udev; /* the usb device for this device */

  struct usb_interface * interface; /* the interface for this device */

  struct semaphore limit_sem; /* limiting the number of writes in progress */

  unsigned char * bulk_in_buffer; /* the buffer to receive data */

  size_t bulk_in_size; /* the size of the receive buffer */

  __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */

  __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */

  struct kref kref;

  };

  我们先来对这个usb_skel作个简单分析,他拥有一个描述usb设备的结构体udev,一个接口interface,用于并发访问控制的semaphore(信号量) limit_sem,用于接收数据的缓冲bulk_in_buffer及其尺寸bulk_in_size,然后是批量输入输出端口地址bulk_in_endpointAddr、bulk_out_endpointAddr,最后是一个内核使用的引用计数器。他们的作用我们将在后面的代码中看到。

  我们再回过头来看看skel_driver。

  name用来告诉内核模块的名字是什么,这个注册之后有系统来使用,跟我们关系不大。

  id_table用来告诉内核该模块支持的设备。usb子系统通过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序作处理。我们可以看看这个id_table到底是什么东西:

  /* Define these values to match your devices */

  #define USB_SKEL_VENDOR_ID 0xfff0

  #define USB_SKEL_PRODUCT_ID 0xfff0

  /* 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);

  MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备,那自然是usb(如果是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备。这涉及PCI设备的驱动了,在此先不深究)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,如果它们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。

  probe是usb子系统自动调用的一个函数,有USB设备接到硬件集线器时,usb子系统会根据production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备调用相应驱动程序的probe(探测)函数,对于skeleton来说,就是skel_probe。系统会传递给探测函数一个usb_interface *跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。probe函数比较长,我们分段来分析这个函数:

  dev->udev = usb_get_dev(interface_to_usbdev(interface));

  dev->interface = interface;

  在初始化了一些资源之后,可以看到第一个关键的函数调用——interface_to_usbdev。他同uo一个usb_interface来得到该接口所在设备的设备描述结构。本来,要得到一个usb_device只要用interface_to_usbdev就够了,但因为要增加对该usb_device的引用计数,我们应该在做一个usb_get_dev的操作,来增加引用计数,并在释放设备时用usb_put_dev来减少引用计数。这里要解释的是,该引用计数值是对该usb_device的计数,并不是对本模块的计数,本模块的计数要由kref来维护。所以,probe一开始就有初始化kref。事实上,kref_init操作不单只初始化kref,还将其置设成1。所以在出错处理代码中有kref_put,它把kref的计数减1,如果kref计数已经为0,那么kref会被释放。kref_put的第二个参数是一个函数指针,指向一个清理函数。注意,该指针不能为空,或者kfree。该函数会在最后一个对kref的引用释放时被调用(如果我的理解不准确,请指正)。下面是内核源码中的一段注释及代码:

  /**

  * kref_put - decrement refcount for object.

  * @kref: object.

  * @release: pointer to the function that will clean up the object when the

  * last reference to the object is released.

  * This pointer is required, and it is not acceptable to pass kfree

  * in as this function.

  *

  * Decrement the refcount, and if 0, call release().

  * Return 1 if the object was removed, otherwise return 0. Beware, if this

  * function returns 0, you still can not count on the kref from remaining in

  * memory. Only use the return value if you want to see if the kref is now

  * gone, not present.

  */

  int kref_put(struct kref *kref, void (*release)(struct kref *kref))

  {

  WARN_ON(release == NULL);

  WARN_ON(release == (void (*)(struct kref *))kfree);

  /*

  * if current count is one, we are the last user and can release object

  * right now, avoiding an atomic operation on 'refcount'

  */

  if ((atomic_read(&kref->refcount) == 1) ||

  (atomic_dec_and_test(&kref->refcount))) {

  release(kref);

  return 1;

  }

  return 0;

  }

  当我们执行打开操作时,我们要增加kref的计数,我们可以用kref_get,来完成。所有对struct kref的操作都有内核代码确保其原子性。

  得到了该usb_device之后,我们要对我们自定义的usb_skel各个状态跟资源作初始化。这部分工作的任务主要是向usb_skel注册该usb设备的端点。这里可能要补充以下一些关于usb_interface_descriptor的知识,但因为内核源码对该结构体的注释不多,所以只能靠个人猜测。在一个usb_host_interface结构里面有一个usb_interface_descriptor叫做desc的成员,他应该是用于描述该interface的一些属性,其中bNumEndpoints是一个8位(b for byte)的数字,他代表了该接口的端点数。probe然后遍历所有的端点,检查他们的类型跟方向,注册到usb_skel中。

  /* set up the endpoint information */

  /* use only the first bulk-in and bulk-out endpoints */

  iface_desc = interface->cur_altsetting;

  for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {

  endpoint = &iface_desc->endpoint.desc;

  if ( !dev->bulk_in_endpointAddr &&

  ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&

  ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {

  /* we found a bulk in endpoint */

  buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);

  dev->bulk_in_size = buffer_size;

  dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

  dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

  if (!dev->bulk_in_buffer) {

  err("Could not allocate bulk_in_buffer");

  goto error;

  }

  }

  if (!dev->bulk_out_endpointAddr &&

  ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&

  ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {

  /* we found a bulk out endpoint */

  dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;

  }

  }

  if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {

  err("Could not find both bulk-in and bulk-out endpoints");

  goto error;

  }

接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到。

  usb_set_intfdata(interface, dev);

  retval = usb_register_dev(interface, &skel_class);

  然后我们向这个interface注册一个skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:

  static struct usb_class_driver skel_class = {

  .name = "skel%d",

  .fops = &skel_fops,

  .minor_base = USB_SKEL_MINOR_BASE,

  };

  它其实是一个系统定义的结构,里面包含了一名字、一个文件操作结构体还有一个次设备号的基准值。事实上它才是定义 真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。这里补充一些我个人的估计:因为usb设备可以有多个interface,每个interface所定义的IO操作可能不一样,所以向系统注册的usb_class_driver要求注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。通常情况下,linux系统用主设备号来识别某类设备的驱动程序,用次设备号管理识别具体的设备,驱动程序可以依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。

  static struct file_operations skel_fops = {

  .owner = THIS_MODULE,

  .read = skel_read,

  .write = skel_write,

  .open = skel_open,

  .release = skel_release,

  };

  这个文件操作结构中定义了对设备的读写、打开、释放(USB设备通常使用这个术语release)。他们都是函数指针,分别指向skel_read、skel_write、skel_open、skel_release这四个函数,这四个函数应该有开发人员自己实现。

  当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interface的data:

  dev = usb_get_intfdata(interface);

  usb_set_intfdata(interface, NULL);

  /* give back our minor */

  usb_deregister_dev(interface, &skel_class);

  然后他会用kref_put(&dev->kref, skel_delete)进行清理,kref_put的细节参见前文。

  到目前为止,我们已经分析完usb子系统要求的各个主要操作,下一部分我们在讨论一下对USB设备的IO操作。

  说到usb子系统的IO操作,不得不说usb request block,简称urb。事实上,可以打一个这样的比喻,usb总线就像一条高速公路,货物、人流之类的可以看成是系统与设备交互的数据,而urb就可以看成是汽车。在一开始对USB规范细节的介绍,我们就说过USB的endpoint有4种不同类型,也就是说能在这条高速公路上流动的数据就有四种。但是这对汽车是没有要求的,所以urb可以运载四种数据,不过你要先告诉司机你要运什么,目的地是什么。我们现在就看看struct urb的具体内容。它的内容很多,为了不让我的理解误导各位,大家最好还是看一看内核源码的注释,具体内容参见源码树下include/linux/usb.h。

  在这里我们重点介绍程序中出现的几个关键字段:

  struct usb_device *dev

  urb所发送的目标设备。

  unsigned int pipe

  一个管道号码,该管道记录了目标设备的端点以及管道的类型。每个管道只有一种类型和一个方向,它与他的目标设备的端点相对应,我们可以通过以下几个函数来获得管道号并设置管道类型:

  unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个控制OUT端点。

  unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个控制IN端点。

  unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个批量OUT端点。

  unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个批量OUT端点。

  unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个中断OUT端点。

  unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个中断OUT端点。

  unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个等时OUT端点。

  unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)

  把指定USB设备的指定端点设置为一个等时OUT端点。

  unsigned int transfer_flags

  当不使用DMA时,应该transfer_flags |= URB_NO_TRANSFER_DMA_MAP(按照代码的理解,希望没有错)。

  int status

  当一个urb把数据送到设备时,这个urb会由系统返回给驱动程序,并调用驱动程序的urb完成回调函数处理。这时,status记录了这次数据传输的有关状态,例如传送成功与否。成功的话会是0。

  要能够运货当然首先要有车,所以第一步当然要创建urb:

  struct urb *usb_alloc_urb(int isoc_packets, int mem_flags);

  第一个参数是等时包的数量,如果不是乘载等时包,应该为0,第二个参数与kmalloc的标志相同。

  要释放一个urb可以用:

  void usb_free_urb(struct urb *urb);

  要承载数据,还要告诉司机目的地信息跟要运的货物,对于不同的数据,系统提供了不同的函数,对于中断urb,我们用

  void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe,

  void *transfer_buffer, int buffer_length,

  usb_complete_t complete, void *context, int interval);

  这里要解释一下,transfer_buffer是一个要送/收的数据的缓冲,buffer_length是它的长度,complete是urb完成回调函数的入口,context由用户定义,可能会在回调函数中使用的数据,interval就是urb被调度的间隔。

  对于批量urb和控制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,

  void *context);

  void usb_fill_bulk_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);

  控制包有一个特殊参数setup_packet,它指向即将被发送到端点的设置数据报的数据。

  对于等时urb,系统没有专门的fill函数,只能对各urb字段显示赋值。

  有了汽车,有了司机,下一步就是要开始运货了,我们可以用下面的函数来提交urb

  int usb_submit_urb(struct urb *urb, int mem_flags);

  mem_flags有几种:GFP_ATOMIC、GFP_NOIO、GFP_KERNEL,通常在中断上下文环境我们会用GFP_ATOMIC。

  当我们的卡车运货之后,系统会把它调回来,并调用urb完成回调函数,并把这辆车作为函数传递给驱动程序。我们应该在回调函数里面检查status字段,以确定数据的成功传输与否。下面是用urb来传送数据的细节。

  /* initialize the urb properly */

  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);

  这里skel_write_bulk_callback就是一个完成回调函数,而他做的主要事情就是检查数据传输状态和释放urb:

  dev = (struct usb_skel *)urb->context;

  /* sync/async unlink faults aren't errors */

  if (urb->status && !(urb->status = = -ENOENT || urb->status == -ECONNRESET || urb->status = = -ESHUTDOWN)) {

  dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);

  }

  /* free up our allocated buffer */

  usb_buffer_free(urb->dev, urb->transfer_buffer_length,

  urb->transfer_buffer, urb->transfer_dma);

  事实上,如果数据的量不大,那么可以不一定用卡车来运货,系统还提供了一种不用urb的传输方式,而usb-skeleton的读操作正是采用这种方式实现:

  /* do a blocking bulk read to get data from the device */

  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);

  /* if the read was successful, copy the data to userspace */

  if (!retval) {

  if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))

  retval = -EFAULT;

  else

  retval = bytes_read;

  }

  程序使用了usb_bulk_msg来传送数据,它的原型如下:

  int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data,

  int len, int *actual length, int timeout)

  这个函数会阻塞等待数据传输完成或者等到超时,data是输入/输出缓冲,len是它的大小,actual length是实际传送的数据大小,timeout是阻塞超时。

  对于控制数据,系统提供了另外一个函数,他的原型是:

  Int usb_contrl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,

  __u8 requesttype, __u16 value, __u16 index, void *data,

  __u16 size, int timeout);

  request是控制消息的USB请求值、requesttype是控制消息的USB请求类型,value是控制消息的USB消息值,index是控制消息的USB消息索引。具体是什么,暂时不是很清楚,希望大家提供说明。

  至此,Linux下的USB驱动框架分析基本完成了。

转载于:https://www.cnblogs.com/sankye/articles/2288981.html

OS相关驱动 Linux USB驱动框架分析相关推荐

  1. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  2. linux内核usb驱动框架,基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析

    基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析 发布时间:2014-07-18 16:47:31来源:红联作者:linux08071151 driver/usb/host/ ...

  3. Linux PCI驱动框架分析:(Peripheral Component Interconnect,外部设备互联)

    <DPDK 20.05 | rte_pci_bus思维导图 | 第一版> <linux系统下:IO端口,内存,PCI总线 的 读写(I/O)操作> <Linux指令:ls ...

  4. Linux PCIe驱动框架分析(第二章)

    目录 项目背景 1. 概述 2. 数据结构 3. 流程分析 3.1 设备驱动模型 3.2 初始化 3.2.1 pci_bus_match 3.2.2 pci_device_probe 3.3 枚举 项 ...

  5. 深入分析Linux PCI驱动框架分析(二)

    说明: Kernel版本:4.14 ARM64处理器 使用工具:Source Insight 3.5, Visio 1. 概述 本文将分析Linux PCI子系统的框架,主要围绕Linux PCI子系 ...

  6. Linux i2c驱动框架分析 (二)

    Linux i2c驱动框架分析 (一) Linux i2c驱动框架分析 (二) Linux i2c驱动框架分析 (三) 通用i2c设备驱动分析 i2c core i2c核心(drivers/i2c/i ...

  7. USB驱动框架分析1

    本文主要分析usb框架的主要数据结构,usb驱动框架的初始化,usb系统模型的建立过程.先贴一张网上找来的图,很清晰很详细. 上图浓缩了usb设备模型的建立流程,再次感谢网上前辈的经验总结.下面以文字 ...

  8. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

  9. Linux下USB驱动框架分析

    http://www.cnblogs.com/general001/articles/2319552.html http://blog.csdn.net/uruita/article/details/ ...

最新文章

  1. 【js】实现分页查询操作的步骤
  2. 新手熊猫烧香学习笔记
  3. 多线程下使用Jedis
  4. 几款***常用小工具的使用说明
  5. behavior php,behavior.php
  6. 知识图谱之语言计算与信息抽取
  7. python简易发红包_学习python:练习5.简单红包程序
  8. 分享Silverlight/WPF/Windows Phone一周学习导读(11月6日-11月12日)
  9. SQL2005安装及链接
  10. CMU 15-213 Introduction to Computer Systems学习笔记(8) Machine-Level Programming-Advanced
  11. 海贼王---追了好久的动漫了闲来无事发几张图嘿嘿
  12. 【Asesprite】快速自制Tileset瓦片地图集(俯视角)
  13. python需要电脑多大内存合适_编程用surface怎么样发(学python要多大内存)
  14. VM 将宿主机文件夹 映射至 虚拟机以及vm tools【共享文件夹、复制粘贴、拖动上传下载】
  15. Configuring Add-on Service
  16. elk7.7.1【系列十六】java 封装 kql 查询条件
  17. 快速上手 Kotlin 开发系列之数据类和枚举
  18. 爬虫实战17:多线程爱丝APP图片爬虫
  19. git直接打包下载和使用git clone进行项目拷贝的区别(踩坑贴)
  20. freemarker将xml转为html,Freemarker 实现Html 静态化

热门文章

  1. 树状数组O(n)建树
  2. 总结人脸识别的方向(FD,FA,FR,FV)
  3. 分布式电源选址定容 在改进的IEEE33节点系统中分布式电源选择最佳接入点和接入容量
  4. ISACA CISM 一次通过备考经验分享
  5. 查询Oracle用户表每日增长量
  6. 养老院管理系统(Java毕业设计)
  7. 【游戏客户端】实现转盘抽奖活动
  8. 数字电压表的设计(at89c51)
  9. js实现淘宝放大镜效果
  10. 数据结构与算法 -判定树和哈夫曼树