精华内容
下载资源
问答
  • USB驱动

    千次阅读 2008-09-28 13:55:00
    usb驱动2006-07-21 15:59标 题: linux下usb驱动 发信站: 天大求实BBS (Wed Jun 28 12:50:56 2006), 转信(bbs.tju.edu.cn) - 下面这篇文章...“高不成低不就” 但个人认为是linux下usb驱动编写的最基本的知识 学习一下还
    usb驱动
    2006-07-21 15:59
    标  题: linux下usb驱动
    发信站: 天大求实BBS (Wed Jun 28 12:50:56 2006), 转信(bbs.tju.edu.cn)

    -
    下面这篇文章是联想软件设计中心嵌入式研发处系统设计工程师 赵明写得
    网上有高手评价这篇文章
    “高不成低不就”
    但个人认为是linux下usb驱动编写的最基本的知识
    学习一下还是受益匪浅的
    希望与大家共勉

    ——————————————————————————————————————————————.
    Linux下的硬件驱动——USB设备(上)(驱动配置部分)







    文档选项




    级别: 初级
    赵明, 联想软件设计中心嵌入式研发处系统设计工程师
    2003 年 7 月 01 日
    USB设备越来越多,而Linux在硬件配置上仍然没有做到完全即插即用,对于Linux怎样配置和使用他们,也越来越成为困扰我们的一大问题。本文着力从Linux系统下设备驱动的架构,去阐述怎样去使用和配置以及怎样编制USB设备驱动。对于一般用户,可以使我们明晰Linux设备驱动方式,为更好地配置和使用USB设备提供了方便;而对于希望开发Linux系统下USB设备驱动的程序员,提供了初步学习USB驱动架构的机会。
    前言
    USB是英文"Universal Serial Bus"的缩写,意为"通用串行总线"。是由Compaq(康柏)、DEC、IBM、Intel、NEC、微软以及Northern Telecom(北方电讯)等公司于1994年11月共同提出的,主要目的就是为了解决接口标准太多的弊端。USB使用一个4针插头作为标准插头,并通过这个标准接头,采用菊花瓣形式把所有外设连接起来,它采用串行方式传输数据,目前最大数据传输率为12Mbps, 支持多数据流和多个设备并行操作,允许外设热插拔。
    目前USB接口虽然只发展了2代(USB1.0/1.1,USB2.0),但是USB综合了一个多平台标准的所有优点 -- 包括降低成本,增加兼容性,可连接大量的外部设备,融合先进的功能和品质。使其逐步成为PC接口标准,进入了高速发展期。
    那么对于使用Linux系统,正确支持和配置常见的USB设备,就是其使用必不可少的关键一步。




    回页首




    相关技术基础
    模块(驱动程序)
    模块(module)是在内核空间运行的程序,实际上是一种目标对象文件,没有链接,不能独立运行,但是可以装载到系统中作为内核的一部分运行,从而可以动态扩充内核的功能。模块最主要的用处就是用来实现设备驱动程序。
    Linux下对于一个硬件的驱动,可以有两种方式:直接加载到内核代码中,启动内核时就会驱动此硬件设备。另一种就是以模块方式,编译生成一个.o文件。当应用程序需要时再加载进内核空间运行。所以我们所说的一个硬件的驱动程序,通常指的就是一个驱动模块。
    设备文件
    对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设备,一般对应着确定的驱动程序;次设备号一般是区分不同属性,例如不同的使用方法,不同的位置,不同的操作。这个设备号是从/proc/devices文件中获得的,所以一般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一个设备文件时,操作系统就已经知道这个设备所对应的驱动程序。
    SCSI 设备
    SCSI是有别于IDE的一个计算机标准接口。现在大部分平板式扫描仪、CD-R刻录机、MO光磁盘机等渐渐趋向使用SCSI接口,加之SCSI又能提供一个高速传送通道,所以,接触到SCSI设备的用户会越来越多。Linux支持很多种的SCSI设备,例如:SCSI硬盘、SCSI光驱、SCSI磁带机。更重要的是,Linux提供了IDE设备对SCSI的模拟(ide-scsi.o模块),我们通常会就把IDE光驱模拟为SCSI光驱进行访问。因为在Linux中很多软件都只能操作SCSI光驱。例如大多数刻录软件、一些媒体播放软件。通常我们的USB存储设备,也模拟为SCSI硬盘而进行访问。
    Linux硬件驱动架构
    对于一个硬件,Linux是这样来进行驱动的:首先,我们必须提供一个.o的驱动模块文件(这里我们只说明模块方式,其实内核方式是类似的)。我们要使用这个驱动程序,首先要加载运行它(insmod *.o)。这样驱动就会根据自己的类型(字符设备类型或块设备类型,例如鼠标就是字符设备而硬盘就是块设备)向系统注册,注册成功系统会反馈一个主设备号,这个主设备号就是系统对它的唯一标识(例如硬盘块设备在/proc/devices中显示的主设备号为3 ,我们用ls -l /dev/had看到的主设备就肯定是3)。驱动就是根据此主设备号来创建一个一般放置在/dev目录下的设备文件(mknod命令用来创建它,它必须用主设备号这个参数)。在我们要访问此硬件时,就可以对设备文件通过open、read、write等命令进行。而驱动就会接收到相应的read、write操作而根据自己的模块中的相应函数进行了。
    其中还有几个比较有关系的东西:一个是/lib/modules/2.4.XX目录,它下面就是针对当前内核版本的模块。只要你的模块依赖关系正确(可以通过depmod设置),你就可以通过modprobe 命令加载而不需要知道具体模块文件位置。 另一个是/etc/modules.conf文件,它定义了一些常用设备的别名。系统就可以在需要此设备支持时,正确寻找驱动模块。例如alias eth0 e100,就代表第一块网卡的驱动模块为e100.o。他们的关系图如下:






    回页首




    配置USB设备
    内核中配置.
    要启用 Linux USB 支持,首先进入"USB support"节并启用"Support for USB"选项(对应模块为usbcore.o)。尽管这个步骤相当直观明了,但接下来的 Linux USB 设置步骤则会让人感到糊涂。特别地,现在需要选择用于系统的正确 USB 主控制器驱动程序。选项是"EHCI" (对应模块为ehci-hcd.o)、"UHCI" (对应模块为usb-uhci.o)、"UHCI (alternate driver)"和"OHCI" (对应模块为usb-ohci.o)。这是许多人对 Linux 的 USB 开始感到困惑的地方。
    要理解"EHCI"及其同类是什么,首先要知道每块支持插入 USB 设备的主板或 PCI 卡都需要有 USB 主控制器芯片组。这个特别的芯片组与插入系统的 USB 设备进行相互操作,并负责处理允许 USB 设备与系统其它部分通信所必需的所有低层次细节。
    Linux USB 驱动程序有三种不同的 USB 主控制器选项是因为在主板和 PCI 卡上有三种不同类型的 USB 芯片。"EHCI"驱动程序设计成为实现新的高速 USB 2.0 协议的芯片提供支持。"OHCI"驱动程序用来为非 PC 系统上的(以及带有 SiS 和 ALi 芯片组的 PC 主板上的)USB 芯片提供支持。"UHCI"驱动程序用来为大多数其它 PC 主板(包括 Intel 和 Via)上的 USB 实现提供支持。只需选择与希望启用的 USB 支持的类型对应的"?HCI"驱动程序即可。如有疑惑,为保险起见,可以启用"EHCI"、"UHCI" (两者中任选一种,它们之间没有明显的区别)和"OHCI"。( 赵明注:根据文档,EHCI已经包含了UHCI和OHCI,但目前就我个人的测试,单独加EHCI是不行的,通常我的做法是根据主板类型加载UHCI或OHCI后,再加载EHCI这样才可以支持USB2.0设备)。
    启用了"USB support"和适当的"?HCI"USB 主控制器驱动程序后,使 USB 启动并运行只需再进行几个步骤。应该启用"Preliminary USB device filesystem",然后确保启用所有特定于将与 Linux 一起使用的实际 USB 外围设备的驱动程序。例如,为了启用对 USB 游戏控制器的支持,我启用了"USB Human Interface Device (full HID) support"。我还启用了主"Input core support" 节下的"Input core support"和"Joystick support"。
    一旦用新的已启用 USB 的内核重新引导后,若/proc/bus/usb下没有相应USB设备信息,应输入以下命令将 USB 设备文件系统手动挂装到 /proc/bus/usb:

    # mount -t usbdevfs none /proc/bus/usb


    为了在系统引导时自动挂装 USB 设备文件系统,请将下面一行添加到 /etc/fstab 中的 /proc 挂装行之后:

    none /proc/bus/usb usbdevfs defaults 0 0


    模块的配置方法.
    在很多时候,我们的USB设备驱动并不包含在内核中。其实我们只要根据它所需要使用的模块,逐一加载。就可以使它启作用。
    首先要确保在内核编译时以模块方式选择了相应支持。这样我们就应该可以在/lib/modules/2.4.XX目录看到相应.o文件。在加载模块时,我们只需要运行modprobe xxx.o就可以了(modprobe主要加载系统已经通过depmod登记过的模块,insmod一般是针对具体.o文件进行加载)
    对应USB设备下面一些模块是关键的。
    usbcore.o
    要支持usb所需要的最基础模块//需要
    usb-uhci.o
    (已经提过)//需要
    usb-ohci.o
    (已经提过)//需要
    uhci.o
    另一个uhci驱动程序,我也不知道有什么用,一般不要加载,会死机的//xx
    ehci-hcd.o
    (已经提过 usb2.0)
    hid.o
    USB人机界面设备,像鼠标呀、键盘呀都需要//需要
    usb-storage.o
    USB存储设备,U盘等用到//需要

    相关模块
    ide-disk.o
    IDE硬盘
    ide-scsi.o
    把IDE设备模拟SCSI接口
    scsi_mod.o
    SCSI支持//需要

    注意kernel config其中一项:

            Probe all LUNs on each SCSI device


    最好选上,要不某些同时支持多个口的读卡器只能显示一个。若模块方式就要带参数安装或提前在/etc/modules.conf中加入以下项,来支持多个LUN。

            add options scsi_mod max_scsi_luns=9  


    sd_mod.o
    SCSI硬盘
    sr_mod.o
    SCSI光盘
    sg.o
    SCSI通用支持(在某些探测U盘、SCSI探测中会用到)

    常见USB设备及其配置
    在Linux 2.4的内核中已经支持不下20种设备。它支持几乎所有的通用设备如键盘、鼠标、modem、打印机等,并不断地添加厂商新的设备象数码相机、MP3、网卡等。下面就是几个最常见设备的介绍和使用方法:
    USB鼠标:
    键盘和鼠标属于低速的输入设备,对于已经为用户认可的PS/2接口,USB键盘和USB鼠标似乎并没有太多更优越的地方。现在的大部分鼠标采用了PS/2接口,不过USB接口的鼠标也越来越多,两者相比,各有优势:一般来说,USB的鼠标接口的带宽大于PS/2鼠标,也就是说在同样的时间内,USB鼠标扫描次数就要多于PS/2鼠标,这样在定位上USB鼠标就更为精确;同时USB接口鼠标的默认采样率也比较高,达到125HZ,而PS/2接口的鼠标仅有40HZ(Windows 9x/Me)或是60HZ(Windows NT/2000)。
    对于USB设备你当然必须先插入相应的USB控制器模块:usb-uhci.o或usb-ohci.o

            modprobe usb-uhci


    USB鼠标为了使其正常工作,您必须先插入模块usbmouse.o和mousedev.o

            modprobe usbmouse
            modprobe mousedev


    若你把HID input layer支持和input core 支持也作为模块方式安装,那么启动hid模块和input模块也是必要的。

            modprobe hid
            modprobe input


    USB键盘:
    一般的,我们现在使用的键盘大多是PS/2的,USB键盘还比较少见,但是下来的发展,键盘将向USB接口靠拢。使用USB键盘基本上没有太多的要求,只需在主板的BIOS设定对USB键盘的支持,就可以在各系统中完全无障碍的使用,而且更可以真正做到在即插即用和热插拔使用,并能提供两个USB连接埠:让您可以轻易地直接将具有USB接头的装置接在您的键盘上,而非计算机的后面。
    同样你当然必须先插入相应的USB控制器模块:usb-uhci.o或usb-ohci.o

            modprobe usb-uhci


    然后您还必须插入键盘模块usbkbd.o,以及keybdev.o,这样usb键盘才能够正常工作。此时,运行的系统命令:

    modprobe usbkbd
    modprobe keybdev


    同样若你把HID input layer支持和input core 支持也作为模块方式安装,那么启动hid模块和input模块也是必要的。
    U盘和USB读卡器:
    数码存储设备现在对我们来说已经是相当普遍的了。CF卡、SD卡、Memory Stick等存储卡已经遍及我们的身边,通常,他们的读卡器都是USB接口的。另外,很多MP3、数码相机也都是USB接口和计算机进行数据传递。更我们的U盘、USB硬盘,作为移动存储设备,已经成为我们的必须装备。
    在Linux下这些设备通常都是以一种叫做usb-storage的方式进行驱动。要使用他们必须加载此模块

            modprobe usb-storage


    当然,usbcore.o 和usb-uhci.o或usb-ohci也肯定是不可缺少的。另外,若你系统中SCSI支持也是模块方式,那么下面的模块也要加载

            modprobe scsi_mod
            modprobe sd_mod


    在加载完这些模块后,我们插入U盘或存储卡,就会发现系统中多了一个SCSI硬盘,通过正确地mount它,就可以使用了(SCSI硬盘一般为/dev/sd?,可参照文章后面的常见问题解答)。

            mount /dev/sda1 /mnt


    Linux支持的其他USB设备。
    MODEM--(比较常见)
    网络设备
    摄像头--(比较常见)例如ov511.o
    联机线--可以让你的两台电脑用USB线实现网络功能。usbnet.o
    显示器--(我没见过)
    游戏杆
    电视盒--(比较常见)
    手写板--(比较常见)
    扫描仪--(比较常见)
    刻录机--(比较常见)
    打印机--(比较常见)
    注意:上面所说的每个驱动模块,并不是都要手动加载,有很多系统会在启动或你的应用需要时自动加载的,写明这些模块,是便于你在不能够使用USB设备时,可以自行检查。只要用lsmod确保以上模块已经被系统加载,你的设备就应该可以正常工作了。当然注意有些模块已经以内核方式在kernel启动时存在了(这些模块文件在/lib/modules/2.4.XX中是找不到的)。




    回页首




    最常遇见的USB问题
    有USB设备的系统安装完redhat 7.3启动死机问题
    有USB设备,当你刚装完redhat 7.3第一次启动时,总会死掉。主要原因是Linux在安装时探测到有usb-uhci和ehci-hcd两个控制器,但在启动时,加载完usb-uhci再加载ehci-hcd就会有冲突。分析认为redhat7.3系统内核在支持USB2.0标准上存在问题。在其他版本的Linux中均不存在此问题。
    解决办法:在lilo或grub启动时用命令行传递参数init=/sbin/init。这样在启动后就不运行其他服务而直接启动shell。然后运行
    mount -o remount,rw / 使/ 可写,init直接启动的系统默认只mount /为只读
    然后vi /etc/modules.config文件
    删除alias usb-controller1 ehci-hcd一行。或前面加#注释掉
    然后mount -o remount,ro / 使/ 只读,避免直接关机破坏文件系统
    然后就可以按Ctrl-Alt-Delete直接重启了
    或许,你有更简单的办法:换USB键盘和鼠标为PS2接口,启动后修改/etc/modules.config文件。
    我们已经知道U盘在Linux中会模拟为SCSI设备去访问,可怎么知道它对应那个SCSI设备呢?
    方法1:推测。通常你第一次插入一个SCSI设备,它就是sda,第二个就是sdb以此类推。你启动Linux插入一个U盘,就试试sda,换了一个就可能是sdb。这里注意两个特例:1) 你用的是联想U盘,它可能存在两个设备区(一个用于加密或启动电脑),这样就可能一次用掉两个sda、sdb,换个U盘就是sdc、sdd。2) 联想数码电脑中,可能已经有了六合一读卡器。它同样也是USB存储设备。它会占掉一个或两个SCSI设备号。
    方法2:看信息。其实,只要你提前把usb-storage.o、scsi_mod.o、sd_mod.o模块加载(直接在kernel中也可以)了,在你插入和拔出U盘时,系统会自动打出信息如下:

    SCSI device sda: 60928 512-byte hdwr sectors ( 31 MB )
    sda: Write Protect is on


    根据此信息,你就知道它在sda上了。当然,可能你的系统信息级别比较高,上述信息可能没有打出,这时候你只要tail /var/log/messages就可以看到了。
    方法3:同样,cat /proc/partitions也可以看到分区信息,其中sd?就是U盘所对应的了。若根本没有sd设备,就要检查你的SCSI模块和usb-storage模块是否正确加载了。
    在使用U盘或存储卡时,我该mount /dev/sda还是/dq2ev/sda1呢?
    这是一个历史遗留问题。存储卡最初尺寸很小,很多厂商在使用时,就直接使用存储,不含有分区表信息。而随着存储卡尺寸的不断扩大,它也就引入了类似硬盘分区的概念。例如/dev/hda你可以分成主分区hda1、hda2扩展分区hda3,然后把扩展分区hda3又分为逻辑分区hda5、hda6、hda7等。这样,通常的U盘就被分成一个分区sda1,类似把硬盘整个分区分成一个主分区hda1。实际上,我们完全可以通过fdisk /dev/sda对存储卡进行完全类似硬盘的分区方式分成sda1、sda2甚至逻辑分区sda5、sda6。实际上,对USB硬盘目前你的确需要这样,因为它通常都是多少G的容量。而且通常,它里面就是笔记本硬盘。
    一个好玩的问题。你在Linux下用fdisk /dev/sda 对U盘进行了多分区,这时候到windows下,你会发现怎么找,怎么格式化,U盘都只能找到第一个分区大小尺寸,而且使用看不出任何问题。这主要是windows驱动对U盘都只支持一个分区的缘故。你是不是可以利用它来进行一些文件的隐藏和保护?你是不是可以和某些人没玩过Linux的人开些玩笑:你的U盘容量变小了J。
    现在较多的数码设备也和windows一样,是把所有U盘容量分为一个,所以在对待U盘的时候,通常你mount的是sda1。但对于某些特殊的数码设备格式化的U盘或存储卡(目前我发现的是一款联想的支持模拟USB软盘的U盘和我的一个数码相机),你就要mount /dev/sda。因为它根本就没分区表(若mount /dev/sda1通常的效果是死掉)。其实,这些信息,只要你注意了/proc/partitions文件,都应该注意到的。
    每次插入U盘,都要寻找对应设备文件名,都要手动mount,我能不能做到象windows那样插入就可以使用呢。
    当然可以,不过你需要做一些工作。我这里只提供一些信息帮助你去尝试完成设置:Linux内核提供了一种叫hotplug支持的东西,它可以让你系统在PCI设备、USB等设备插拔时做一些事情。而automount 功能可以使你的软驱、光盘等设备的分区自动挂载和自动卸载。你甚至可以在KDE桌面中创建相应的图标,方便你操作。具体设置方法就要你自己去尝试了。反正我使用Linux已经麻木了,不就是敲一行命令嘛。




    Linux下的硬件驱动——USB设备(下)(驱动开发部分)







    文档选项


    级别: 初级
    赵明, 联想软件设计中心嵌入式研发处系统设计工程师, 2003年7月
    2003 年 7 月 01 日
    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骨架程序的关键几点如下:
    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[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函数,然后把我们的驱动融入其中。




    回页首




    参考资料
    《LINUX设备驱动程序》
    ALESSANDRO RUBINI著
    LISOLEG 译
    《Linux系统分析与高级编程技术》
    周巍松 编著
    Linux Kernel-2.4.20源码和文档说明




    回页首




    附录1:键盘飞梭驱动

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

            kbd->usbdev = dev;

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

            return kbd;
    }

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

    展开全文
  • usb 驱动

    千次阅读 2012-08-10 19:42:08
    1. USB host Controller驱动程序  * 处理USB状态,管理并报告状态信息  * 数据并/传转换  * 生成frame或者microframe  * 处理数据传输的请求  * 处理USB总线协议  * 进行差错检测和控制  * 处理电源...
    1. USB host Controller驱动程序
        * 处理USB状态,管理并报告状态信息
        * 数据并/传转换
        * 生成frame或者microframe
        * 处理数据传输的请求
        * 处理USB总线协议
        * 进行差错检测和控制
        * 处理电源管理请求, 把总线置为suspended状态以及响应wakeup事件
        * 提供root hub功能, 让设备可以连接到主机控制器

    2. USB请求块 - urb(USB request block)
        * linux内核中通过urb来进行异步数据交换.
        * 设备和多个端点之间可以使用同样或者不同的urb,端点可以处理一个urb队列.
        * urb使用流程:
            1) USB设备驱动创建一个urb
            2) 设置urb, 将它关联到某个端点
            3) 设备驱动程序将urb提交给usbcore
            4) usbcore解析urb关联的设备,并把它发送到适当的USB主控器
            5) 主控器按照urb内容,驱动总线设备完成传输,当传输完成时告知设备驱动
            6) 发送urb的设备驱动或者usbcore也可以取消urb
        * 控制请求(cr) --- 8个字节的数据包(参考USB规范)
            1)请求类型:     bit[1:0]    接受请求:设备/接口/端点/其他
                            bit[6:5]    USB的类型:标准/类/产商/保留
                            bit[7]      请求方向(IN : 到主机)
            2)请求
                USB_REQ_GET_STATUS(00h)     :   获取状态
                  A[ to device ]
                    * Bit[0]    : 0总线供电, 1 自供电
                    * Bit[1]    : 远程唤醒(0:不支持, 1:支持)
                    * Bit[15:2] : 保留
                  B[ to interface ]
                    * 全部保留,返回0
                  C[ to endpoint ]
                    * Bit[0]    : Halt(0表示端点允许,1表示端点禁止)
                    * Bit[15:1] : 保留

                USB_REQ_CLEAR_FEATURE(01h)
                  A[ to device ] : 清除设备的远程唤醒功能,并返回一个空包
                  B[ to endpoint ] : 解禁端点

                USB_REQ_SET_FEATURE(03h)
                  A[ to device ]    : 设置设备远程唤醒功能,并返回一个空包
                  B[ to endpoint ]  : 禁止端点
                
                USB_REQ_SET_ADDRESS(05h)
                  A[ to device ]    : 设置设备地址
                USB_REQ_GET_DESCRIPTOR(06h)
                  A[ to device ]    : 获取设备描述符
                        协议版本,设备端点0的FIFO,以及设备ID号等.
                  B[ to configuration ] : 获取配置描述符
                        描述USB设备接口个数及是否有自供电能力等
                  C[ to interface ] : 获取接口描述符
                        描述端点0以外的物理端点个数等信息
                  D[ to endpoint ]  : 获取端点描述符
                        描述端点的传输类型和最大信息包大小和端点传输方向

                USB_REQ_SET_DESCRIPTOR(07h)     : 可选,无法设置描述符
                USB_REQ_GET_CONFIGURATION(08h)  : 获取配置信息
                USB_REQ_SET_CONFIGURATION(09h)  : 设置配置信息
                  A[ to configuration ] : 设置配置描述符
                  B[ to interface ]     : 设置接口描述符
                  C[ to endpoint ]      : 设置端点描述符
                USB_REQ_GET_INTERFACE(0Ah)      : 获取接口信息
                USB_REQ_SET_INTERFACE(0Bh)      : 设置接口信息
                USB_REQ_SYNCH_FRAME(0Ch)        : 设备设置和报告一个端点的同步帧
                

                US_BULK_GET_MAX_LUN(feh)    :   获取max lun
            3)值
            4)接受请求设备/接口/端点的索引号
            5)数据长度
        * USB设备/管道/发送的包/发送的数据及长度/完成回调及回调数据
        * 发送长度,错误次数,状态,

        


    USB接口驱动结构体(usb_driver): 用来识别USB接口到usb核心.USB接口驱动必须提供名字,探测函数和断开函数,以及一个设备id表,其他是可选的.
        
        @name       :   USB驱动唯一名称,通常与模块名称相同
        @probe      :   探测函数
        @disconnect :   当接口不可访问时调用,由于设备断开或者模块卸载
        @ioctl      :   通过usbfs文件系统与用户空间交互
        @suspend    :   挂起操作
        @resume     :   恢复操作
        @reset_resume   :   设备挂起时复位
        @pre_reset      :   复位前调用
        @post_reset     :   复位后调用
        @id_table       :   驱动支持的USB设备id表
        @dynids         :   内部保存添加到驱动的设备id列表
        @drvwrap        :   驱动-模型核心结构体封装
        @no_dynamic_id  :   1表示USB核心不允许动态设备添加到驱动
        @supports_autosuspend   :   0表示USB核心不允许在驱动外边自动挂起接口
        @soft_unbind    :   1表示在调用驱动disconnect前不会杀掉URBs和禁用端点
        

    USB设备驱动结构体(usb_device_driver) : 识别USB设备到USB核心.必须提供所有函数.
        @name       :   USB驱动名称,通常与模块名相同
        @probe      :   探测函数
        @disconnect :   断开连接,设备断开或者驱动卸载时调用
        @suspend    :   挂起
        @resume     :   恢复
        @drvwarp    :   设备-模型核心结构体封装
        @supports_autosuspend   :   0表示USB核心不允许在驱动外边自动挂起接口
        
        
    USB主机接口结构体(usb_host_interface) : 表示USB接口的配置信息
        @desc       :   接口描述符(描述符类型,端点数量,接口编号/类型/子类/协议等)
        @endpoint   :   端点数组
        @string     :   接口描述串
        @extra      :   额外描述串
        @extralen   :   额外长度

    USB主机端点结构体(usb_host_endpoint) : 表示端点的配置信息
        @desc       :   端点描述符(长度,描述符类型,端点地址/属性等)
        @urb_list   :   urb请求列表
        @hcpriv     :   控制器私有数据
        @ep_dev     :   端点设备
        @ss_ep_comp :   SS设备
        @extra      :   额外描述
        @extralen   :   额外长度
        @enabled    :   使能

    USB设备结构体(usb_device) :
        @devnum     :   usb设备号
        @devpath    :   设备id串(在消息中使用)
        @route      :   

    设备描述符:
        描述符的字节数大小
        描述符类型编号
        USB版本
        USB设备类码
        USB设备子类码
        USB设备协议码
        端点0的最大包大小
        产商编号,产品号,设备出厂编号,产商字符串索引
        产品字符串索引,系列号字符串索引
        配置描述符的数量

    配置描述符:
        描述符的字节数大小,类型编号
        此配置返回的所有数据大小
        此配置支持的接口数量
        配置命令所需的参数值
        该配置描述符的字符串索引值
        供电模式选择
        设备从总线提取的最大电流

    接口描述符
        描述符字节数大小和类型编号
        接口编号
        备用接口描述符编号
        该接口使用的端点数,不包括端点0
        接口类型,子类型,协议
        该接口的字符串索引

    端点描述符:
        属性(bit[1:0])      :   传输类型, 0-控制, 1-等时, 2-批量, 3-中断
        端点地址(bit[7])    :   传输方向, 0-OUT,  1-IN(相对主机而言)
        端点地址(bit[3:0])  :   端点号
        间隔时间            :   中断端点的(两个中断的)间隔时间
        端点收,发的最大包大小
        


    USB数据构成:
        * 二进制流构成域
            1)同步域(SYNC)  : 固定八位为:0000 0001, 用于本地时钟与输入同步
            2)标识域(PID)   : 由四位标识符+四位标识符反码构成, 表明包的类型和格式
            3)地址域(ADDR)  : 七位地址,代表设备在主机上的地址. 0地址是任何一个设备
                                第一次连接到主机时, 在被主机配置,枚举前的默认地址.
            4)端点域(ENDP)  : 四位端点地址
            5)帧号域(FRAM)  : 11位,每个帧都有一个特定的帧号,最大为0x800.
            6)数据域(DATA)  : 长度为0~1023字节
            7)校验域(CRC)   : 对令牌包和数据包中非PID域进行校验.

        * 域构成包,有四种类型,分别为令牌包,数据包,握手包和特殊包.
            1)令牌包可分为输入包,输出包,设置包和帧起始包.
                输入/输出和设置包的格式为: SYNC + PID + ADDR + ENDP + CRC5
                帧起始包格式:  SYNC + PID + FRAM + CRC5
            2)数据包,分为DATA0和DATA1包.当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0和DATA1包交替发送(同步传输时,所有的包都是DATA0).格式如下:
                SYNC + PID + DATA + CRC16
            3)握手包: SYNC + PID.

        * 包构成事务,事务分为IN事务,OUT事务和SETUP事务三种,每一种事务都由令牌包,数据包,握手包构成.每个包的发送有一定的时间先后顺序,因此事务分为三个阶段:
            1)令牌包阶段: 启动一个输入/输出/设置的事务
            2)数据包阶段: 按输入/输出发送相应的数据
            3)握手包阶段: 返回数据接收情况,在同步传输的IN和OUT事务中没有这个阶段.
            @ IN事务:主机发送PID为IN的输入包给设备,设备会作出三种应答:
                A.设备端点正常,设备往主机发送数据包(DATA0和DATA1交替)
                B.设备忙,发送NAK无效包,IN事务提前结束.
                C.相应设备的端点被禁止,发送错误包STALL包,事务提前结束.
                主机正确接收到数据包之后就会向设备发送ACK包.
            @ OUT事务:主机发送PID为OUT的输出包给设备,然后发送数据包,握手时设备反应:
                A.设备端点正确接收, 设备往主机返回ACK,通知主机可以发送新的数据
                B.设备忙,发送NAK无效包,通知主机再次发送数据
                C.端点被禁止,发送STALL包,事务提前结束
            @ SETUP事务:主机发送SETUP输出包,然后发送8字节的DATA0包(设备请求命令),设备接收到主机命令后返回ACK.

        * 事务构成传输(中断/等时/批量/控制传输)
            1)中断传输: 由OUT事务和IN事务构成,用于键盘,鼠标等HID设备的数据传输
            2)批量传输: 由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量传输
            3)等时传输: 由OUT事务和IN事务构成,没有握手包,数据都是DATA0
            4)控制传输: 由三个阶段构成(初始设置,可选数据,状态信息).每个阶段看成一个传输.控制传输其实由三个传输构成的,用来USB设备初次连接到主机之后,主机通过控制传输来交换信息,设备地址和读取设备的描述符,使得主机识别设备,并安装相应的驱动.
            初始设备--- 由SET事务构成的传输
            可选数据--- 由IN或OUT事务构成(可选,由SET事务发送的标准请求命令决定)
            状态信息--- 由IN或OUT事务构成(A.传输方向相反 B.数据包长度为0)


    USB枚举过程:
    第一来回:
        1)检测到设备,主机总线复位(是SIE根据总线状态通知用户的一种复位).
        2)主机发SETUP包(0地址0端点),主机数据包(请求设备描述符),设备握手包ACK.
            设备产生端点0数据中断,端点0输入缓冲区准备好设备描述符.
        3)数据过程,主机先发一个IN令牌包,设备发一个数据包,主机发ACK包
            SIE产生端点0数据输入中断,表明主机已取走设备的数据,用户中断程序处理.
        4)状态过程,主机发OUT包,主机发0字节状态数据包(表明收到设备描述符),设备回ACK.
    第二来回:
        5)主机再次复位总线,进入地址设置控制传输阶段
        6)主机SETUP包,主机数据包(请求设置地址),设备握手包ACK.
        7)数据过程,本次没有数据传输
        8)状态过程,主机发IN包,设备发0字节状态数据包(表明地址设置成功),主机发ACK包.
    第三来回:
        9)主机采用新地址发起第一个控制传输
        10)主机发SETUP包(新地址),主机数据包(请求设备描述符),设备握手ACK包
        11)数据过程,主机先发一个IN令牌包,设备发一个数据包,主机发ACK包.
            数据量大时,重复执行
        12)状态过程,主机发OUT包,主机发0字节状态数据包,设备回ACK包
        获取配置描述符,配置集合,字符串描述符,报告描述符的过程差不多.

        
        






    展开全文
  • linux usb驱动——USB OTG驱动

    千次阅读 2018-03-01 16:45:00
    摘自 linux usb驱动——USB协议架构及驱动架构 OTG驱动 OS_FS: 文件系统 USBD: USB核心 HCD: 主机控制器驱动 UDC: 设备端控制器驱动 OTG设备支持HNP和SRP协议。OTG设备通过USB OTG电缆连接到一起,其中接...

    摘自 linux usb驱动——USB协议架构及驱动架构

    OTG驱动

    \

    OS_FS: 文件系统
    USBD: USB核心
    HCD: 主机控制器驱动
    UDC: 设备端控制器驱动

           OTG设备支持HNP和SRP协议。OTG设备通过USB OTG电缆连接到一起,其中接Mini-A接口的设备为A设备,默认为主机端,Mini-B接口的设备默认为B设备。当A、B设备完成数据交互之后,A、B设备之间的USB OTG电缆进入挂起状态,如下图所示:

    \

            当B设备写入b_bus_req,向A设备发起HNP请求。待A设备响应之后,A设备发送a_set_b_hnp_en,B设备响应之后即进入主机状态,同时发送请求使用A设备set_device,这样A、B设备完成主从交换。

    转载于:https://www.cnblogs.com/mahj/p/8489216.html

    展开全文
  • LINUX USB驱动分析(3)-USB驱动分析

    千次阅读 2017-02-13 11:21:23
    前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和...

    前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。
    USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口 ,下面我们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:

    一、注册USB驱动程序

    Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。所以USB驱动和注册与正常的模块注册、卸载是一样的,下面是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");  

    USB设备驱动的模块加载函数通用的方法是在I2C设备驱动的模块加载函数中使用usb_register(struct *usb_driver)函数添加usb_driver的工作,而在模块卸载函数中利用usb_deregister(struct *usb_driver)做相反的工作。 对比I2C设备驱动中的 i2c_add_driver(&i2c_driver)与i2c_del_driver(&i2c_driver)。
    struct usb_driver是USB设备驱动,我们需要实现其成员函数:

    static struct usb_driver skel_driver = {     
         .owner = THIS_MODULE,      
         .name = "skeleton",    
         .id_table = skel_table,         
         .probe = skel_probe,       
         .disconnect = skel_disconnect,       
    };    

    从代码看来,usb_driver需要初始化五个字段:
    模块的所有者 THIS_MODULE
    模块的名字 skeleton
    probe函数 skel_probe
    disconnect函数skel_disconnect
    id_table
    最重要的当然是probe函数与disconnect函数,这个在后面详细介绍,先谈一下id_table:
    id_table 是struct usb_device_id 类型,包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测回调该函数将不会被调用。对比I2C中struct i2c_device_id *id_table,一个驱动程序可以对应多个设备.
    usb子系统通过设备的production ID和vendor ID的组合或者设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程序作处理。我们可以看看这个id_table到底是什么东西:

    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。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor ID和product ID,如果它们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。
    当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或者是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备。
    我们下面所要做的就是对probe函数与disconnect函数的填充了,但是在对probe函数与disconnect函数填充之前,有必要先学习三个重要的数据结构,这在我们后面probe函数与disconnect函数中有很大的作用:

    二、USB驱动程序中重要数据结构

    1、usb-skeleton
    usb-skeleton 是一个局部结构体,用于与端点进行通信。下面先看一下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设备的结构体udev
    一个接口interface
    用于并发访问控制的semaphore(信号量) limit_sem
    用于接收数据的缓冲bulk_in_buffer
    用于接收数据的缓冲尺寸bulk_in_size
    批量输入端口地址bulk_in_endpointAddr
    批量输出端口地址bulk_out_endpointAddr
    内核使用的引用计数器
    从开发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又可以有多个接口(interface)(我理解就是USB设备的一项功能),每个接口又有多个设置,而接口本身可能没有端点或者多个端点(end point)
    2、USB 接口数据结构 struct usb_interface

    struct usb_interface    
    {             
             struct usb_host_interface *altsetting;     
             struct usb_host_interface *cur_altsetting;          
             unsigned num_altsetting;             
             int minor;                          
             enum usb_interface_condition condition;             
             unsigned is_active:1;               
             unsigned needs_remote_wakeup:1;      
             struct device dev;                  
             struct device *usb_dev;             
             int pm_usage_cnt;                   
    };

    在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程序来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,要求有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用struct usb_interface来描述,以下是该结构体中比较重要的字段:
    a – struct usb_host_interface *altsetting(注意不是usb_interface)
    其实据我理解,他应该是每个接口的设置,虽然名字上有点奇怪。该字段是一个设置的数组(一个接口可以有多个设置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。

    b – struct usb_host_interface *cur_altsetting
    当前活动的设置,指向altsetting数组中的一个
    struct usb_host_interface数据结构:

    struct usb_host_interface     
    {    
             struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的。    
                                     //usb描述符放在usb设备的eeprom里边    
             /* array of desc.bNumEndpoint endpoints associated with this  
              * interface setting. these will be in no particular order.  
              */    
             struct usb_host_endpoint *endpoint;//这个设置所使用的端点    
    
             char *string;           /* iInterface string, if present */    
             unsigned char *extra;   /* Extra descriptors */关于额外描述符    
             int extralen;    
    }; 

    c – unsigned num_altstting
    可选设置的数量,即altsetting所指数组的元素个数
    d – int minor
    当捆绑到该接口的USB驱动程序使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。

    3、USB 端点 struct usb_host_endpoint
    Linux中用struct usb_host_endpoint 来描述USB端点

    struct usb_host_endpoint     
    {    
             struct usb_endpoint_descriptor desc;    
             struct list_head                urb_list;//端点要处理的urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信,就得创建一个urb,并且为它赋好值,    
                                       //交给咱们的usb core,它会找到合适的host controller,从而进行具体的数据传输    
             void                            *hcpriv;//这是提供给HCD(host controller driver)用的    
             struct ep_device                *ep_dev;        /* For sysfs info */    
    
             unsigned char *extra;   /* Extra descriptors */    
             int extralen;    
    };    

    每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息以及设备自定义的各种信息,这些信息包括:
    a – bEndpointAddress(b for byte)
    8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),可以用掩码USB_DIR_OUT和USB_DIR_IN来确定。
    b – bmAttributes
    端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK可以确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。
    c – wMaxPacketSize
    端点一次处理的最大字节数。发送的BULK包可以大于这个数值,但会被分割传送。
    d – bInterval
    如果端点是中断类型,该值是端点的间隔设置,以毫秒为单位

    三、探测和断开函数分析

    USB驱动程序指定了两个USB核心在适当时间调用的函数。

    1、探测函数probe函数

    当一个设备被安装而USB核心认为该驱动程序应该处理时,探测函数被调用;
    探测函数应该检查传递给他的设备信息,确定驱动程序是否真的适合该设备。当驱动程序因为某种原因不应控制设备时,断开函数被调用,它可以做一些清洁的工作。
    系统会传递给探测函数的信息是什么呢?一个usb_interface * 跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认设置也是第0号设置)跟它的设备ID描述(包括Vendor ID、Production ID等)。
    USB驱动程序应该初始化任何可能用于控制USB设备的局部结构体,它还应该把所需的任何设备相关信息保存到局部结构体中。例如,USB驱动程序通常需要探测设备对的端点地址和缓冲区大小,因为需要他们才能和端点通信。
    下面具体分析探测函数做了哪些事情:
    a – 探测设备的端点地址、缓冲区大小,初始化任何可能用于控制USB设备的数据结构
    下面是一个实例代码,他们探测批量类型的IN和OUT端点,把相关信息保存到一个局部设备结构体中:

    /* 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[i].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;     
         }  

    具体流程如下:
    该代码块首先循环访问该接口中存在的每一个端点,赋予该端点结构体的局部指针以使稍后的访问更加容易

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

    然后,我们有了一个端点,而还没有发现批量IN类型的端点时,查看该端点的方向是否为IN。这可以通过检查位掩码 USB_DIR_IN 是否包含在bEndpointAddress 端点变量中来确定。如果是的话,我们测定该端点类型是否批量,这首先通过USB_ENDPOINT_XFERTYPE_MASK 位掩码来取bmAttributes变量的值,然后检查它是否和USB_ENDPOINT_XFER_BULK 的值匹配来完成

    if ( !dev->bulk_in_endpointAddr &&     
                   ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) { 

    如果这些都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点相关的信息保存到一个局部结构体中,就是我们前面的usb_skel ,以便稍后使用它和端点进行通信:

    if ( !dev->bulk_in_endpointAddr &&     
                   ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&     
                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {   

    如果这些都通过了,驱动程序就知道它已经发现了正确的端点类型,可以把该端点相关的信息保存到一个局部结构体中,就是我们前面的usb_skel ,以便稍后使用它和端点进行通信:
    c
    /* 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;
    }

    b – 把已经初始化数据结构的指针保存到接口设备中
    接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构可以是任意的,这段程序向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data可以在以后用usb_get_intfdata来得到usb_set_intfdata(interface, dev);

    c – 注册USB设备
    如果USB驱动程序没有和处理设备与用户交互(例如输入、tty、视频等)的另一种类型的子系统相关联,驱动程序可以使用USB主设备号,以便在用户空间使用传统的字符驱动程序接口。如果要这样做,USB驱动程序必须在探测函数中调用 usb_resgister_dev 函数来把设备注册到USB核心。只要该函数被调用,就要确保设备和驱动陈旭都处于可以处理用户访问设备的要求的恰当状态
    retval = usb_register_dev(interface, &skel_class);
    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,     
    };    

    2、断开函数disconnect

      当设备被拔出集线器时,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);   
    

    四、USB请求块
    USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )
    urb 以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个 USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。
    一个 urb 的典型生命循环如下:
    (1)被创建;
    (2)被分配给一个特定 USB 设备的特定端点;
    (3)被提交给 USB 核心;
    (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动;
    (5)被 USB 主机控制器驱动处理, 并传送到设备;
    (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。
    urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。

      struct urb  
    {  
        /* 私有的:只能由usb核心和主机控制器访问的字段 */  
        struct kref kref; /*urb引用计数 */  
        spinlock_t lock; /* urb锁 */  
        void *hcpriv; /* 主机控制器私有数据 */  
        int bandwidth; /* int/iso请求的带宽 */  
        atomic_t use_count; /* 并发传输计数 */  
        u8 reject; /* 传输将失败*/  
    
        /* 公共的: 可以被驱动使用的字段 */  
        struct list_head urb_list; /* 链表头*/  
        struct usb_device *dev; /* 关联的usb设备 */  
        unsigned int pipe; /* 管道信息 */  
        int status; /* urb的当前状态 */  
        unsigned int transfer_flags; /* urb_short_not_ok | ...*/  
        void *transfer_buffer; /* 发送数据到设备或从设备接收数据的缓冲区 */  
        dma_addr_t transfer_dma; /*用来以dma方式向设备传输数据的缓冲区 */  
        int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向缓冲区的大小 */  
    
        int actual_length; /* urb结束后,发送或接收数据的实际长度 */  
        unsigned char *setup_packet; /* 指向控制urb的设置数据包的指针*/  
        dma_addr_t setup_dma; /*控制urb的设置数据包的dma缓冲区*/  
        int start_frame; /*等时传输中用于设置或返回初始帧*/  
        int number_of_packets; /*等时传输中等时缓冲区数据 */  
        int interval; /* urb被轮询到的时间间隔(对中断和等时urb有效) */  
        int error_count;  /* 等时传输错误数量 */  
        void *context; /* completion函数上下文 */  
        usb_complete_t complete; /* 当urb被完全传输或发生错误时,被调用 */  
        struct usb_iso_packet_descriptor iso_frame_desc[0];  
        /*单个urb一次可定义多个等时传输时,描述各个等时传输 */  
    };  

    1、创建和注销 urb

    struct urb 结构不能静态创建,必须使用 usb_alloc_urb 函数创建. 函数原型:

       struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
    //int iso_packets : urb 包含等时数据包的数目。如果不使用等时urb,则为0
    //gfp_t mem_flags : 与传递给 kmalloc 函数调用来从内核分配内存的标志类型相同
    //返回值          : 如果成功分配足够内存给 urb , 返回值为指向 urb 的指针. 如果返回值是 NULL, 则在 USB 核心中发生了错误, 且驱动需要进行适当清理

    如果驱动已经对 urb 使用完毕, 必须调用 usb_free_urb 函数,释放urb。函数原型:

    void usb_free_urb(struct urb *urb);
    //struct urb *urb : 要释放的 struct urb 指针

    2、初始化 urb

    static inline 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_fn,  
                     void *context,  
                     int interval);  
    
    static inline 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_fn,  
                     void *context);  
    
    static inline 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_fn,  
                        void *context);  
    
    
    //struct urb *urb :指向要被初始化的 urb 的指针  
    //struct usb_device *dev :指向 urb 要发送到的 USB 设备.  
    //unsigned int pipe : urb 要被发送到的 USB 设备的特定端点. 必须使用前面提过的 usb_******pipe 函数创建  
    //void *transfer_buffer :指向外发数据或接收数据的缓冲区的指针.注意:不能是静态缓冲,必须使用 kmalloc 来创建.  
    //int buffer_length :transfer_buffer 指针指向的缓冲区的大小  
    //usb_complete_t complete :指向 urb 结束处理例程函数指针  
    //void *context :指向一个小数据块的指针, 被添加到 urb 结构中,以便被结束处理例程函数获取使用.  
    //int interval :中断 urb 被调度的间隔.  
    //函数不设置 urb 中的 transfer_flags 变量, 因此对这个成员的修改必须由驱动手动完成  
    
    /*等时 urb 没有初始化函数,必须手动初始化,以下为一个例子*/  
    urb->dev = dev;  
    urb->context = uvd;  
    urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);  
    urb->interval = 1;  
    urb->transfer_flags = URB_ISO_ASAP;  
    urb->transfer_buffer = cam->sts_buf[i];  
    urb->complete = konicawc_isoc_irq;  
    urb->number_of_packets = FRAMES_PER_DESC;  
    urb->transfer_buffer_length = FRAMES_PER_DESC;  
    for (j=0; j < FRAMES_PER_DESC; j++) {  
            urb->iso_frame_desc[j].offset = j;  
            urb->iso_frame_desc[j].length = 1;  
    }  

    3、提交 urb

      一旦 urb 被正确地创建并初始化, 它就可以提交给 USB 核心以发送出到 USB 设备. 这通过调用函数 usb_submit_urb 实现:
    
    int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
    //struct urb *urb :指向被提交的 urb 的指针 
    //gfp_t mem_flags :使用传递给 kmalloc 调用同样的参数, 用来告诉 USB 核心如何及时分配内存缓冲
    
    /*因为函数 usb_submit_urb 可被在任何时候被调用(包括从一个中断上下文), mem_flags 变量必须正确设置. 根据 usb_submit_urb 被调用的时间,只有 3 个有效值可用:
    GFP_ATOMIC 
    只要满足以下条件,就应当使用此值:
    1.调用者处于一个 urb 结束处理例程,中断处理例程,底半部,tasklet或者一个定时器回调函数.
    2.调用者持有自旋锁或者读写锁. 注意如果正持有一个信号量, 这个值不必要.
    3.current->state 不是 TASK_RUNNING. 除非驱动已自己改变 current 状态,否则状态应该一直是 TASK_RUNNING .
    
    GFP_NOIO 
    驱动处于块 I/O 处理过程中. 它还应当用在所有的存储类型的错误处理过程中.
    
    GFP_KERNEL 
    所有不属于之前提到的其他情况
    */
    

    在 urb 被成功提交给 USB 核心之后, 直到结束处理例程函数被调用前,都不能访问 urb 结构的任何成员.

    4、urb结束处理例程

      如果 usb_submit_urb 被成功调用, 并把对 urb 的控制权传递给 USB 核心, 函数返回 0; 否则返回一个负的错误代码. 如果函数调用成功, 当 urb 被结束的时候结束处理例程会被调用一次.当这个函数被调用时, USB 核心就完成了这个urb, 并将它的控制权返回给设备驱动.
    

    只有 3 种结束urb并调用结束处理例程的情况:
    (1)urb 被成功发送给设备, 且设备返回正确的确认.如果这样, urb 中的status变量被设置为 0.
    (2)发生错误, 错误值记录在 urb 结构中的 status 变量.
    (3)urb 从 USB 核心unlink. 这发生在要么当驱动通过调用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一个已提交的 urb,或者在一个 urb 已经被提交给它时设备从系统中去除.

    5、取消 urb

    使用以下函数停止一个已经提交给 USB 核心的 urb:
    void usb_kill_urb(struct urb *urb)
    int usb_unlink_urb(struct urb *urb);
    如果调用usb_kill_urb函数,则 urb 的生命周期将被终止. 这通常在设备从系统移除时,在断开回调函数(disconnect callback)中调用.
    对一些驱动, 应当调用 usb_unlink_urb 函数来使 USB 核心停止 urb. 这个函数不会等待 urb 完全停止才返回. 这对于在中断处理例程中或者持有一个自旋锁时去停止 urb 是很有用的, 因为等待一个 urb 完全停止需要 USB 核心有使调用进程休眠的能力(wait_event()函数).

    展开全文
  • 编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和...总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,
  • USB驱动框架

    千次阅读 2019-06-08 21:54:06
    一开始分析USB转串口驱动一头扎到源码,也就是追了个流程,但是还是什么都不懂,再后来看USB驱动框架也还是晕乎乎,之后再看了USB基础才慢慢理解一点。 一、USB基础知识 USB是Universal Serial Bus的缩写,中文译...
  • Linux USB驱动详解

    万次阅读 2018-11-13 10:15:29
    Linux USB驱动详解
  •  前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商...
  • USB驱动查询USB设备信息

    千次阅读 2017-05-26 17:49:46
    USB驱动查询USB设备信息。
  • USB驱动程序

    2014-03-27 10:31:04
    1、USB驱动程序存在于不同的内核子系统(块设备、网络设备、字符设备等等)和USB主控制器之中。 2、USB核心为USB驱动程序提供了一个用于访问和控制USB硬件的接口,而不必考虑系统当前存在的各种不同类型的USB硬件...
  • 编写USB驱动程序

    千次阅读 2019-04-29 23:07:07
    编写USB驱动程序TOC 支持USB驱动程序(识别) struct usb_device_id结构体提供了一系列不同类型的该驱动程序支持的USB设备,USB核心使用该列表判断对于对于一个设备该使用哪个驱动程序,热插拔脚本使用它来确定当...
  • linux usb驱动详解

    千次阅读 2017-05-26 19:25:33
    Linux usb驱动
  • 安装usb驱动

    千次阅读 2016-08-28 21:02:43
    在ubuntu 64位系统目录下,...linux下不需要安装特定的usb驱动程序只需要修改下配置文件 #lsusb ... Bus 002 Device 059: ID 18d1:4e42 Google Inc. 配置文件在/etc/udev/rules.d/50-android.rules # udev rule to
  • 前面我们学习了USB 驱动的一个描述符,下面来学习 USB 驱动的几个重要数据结构 一、struct usb_interface 接口函数 struct usb_interface { struct usb_host_interface *altsetting; struct usb_host_...
  • USB驱动程序分为两个层次 (1) USB总线驱动=》系统自带的; USB总线驱动程序负责: 识别USB设备,给USB设备找到对应的驱动程序。 USB总线驱动程序作用: a.识别USB设备; b.查找
  • USB驱动程序之USB设备驱动程序

    千次阅读 2014-09-23 20:23:10
    USB驱动程序框架: app: -------------------------------------------  USB设备驱动程序 // 知道数据含义 内核 --------------------------------------  USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备...
  • USB 驱动架构浅析

    千次阅读 2019-06-05 10:59:56
    USB 驱动架构浅析 之前一直概念模糊,最近简单总结了一下 1.USB简介 USB,即Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。USB接口支持设备的即插即用...
  • 如何修改USB驱动能力

    千次阅读 2018-08-17 11:53:48
    Android 驱动(15)---如何修改USB驱动能力    如何修改USB驱动能力  修改或查看USB驱动能力有三种方法:  1、工模菜单  通过"*#*#3646633#*#*"进入工程模式,通过如下菜单可以查看修改USB驱动...
  • 获取 Google USB 驱动程序 另请参阅 安装 USB 驱动程序 使用硬件设备 使用任何Google Nexus 设备进行ADB调试时,只有 Windows 需要Google USB 驱动程序。唯一...
  • (一)USB驱动程序_USB基础知识

    千次阅读 2018-10-11 10:20:05
    USB驱动程序可以粗分为两类: 一、主机(Host)系统上的驱动程序 ,这个驱动程序控制插入其中的USB设备 二、设备(Device)上的驱动程序,这个驱动程序控制USB设备如何与主机通信 为了举一个形象的例子,我得先...
  • android-usb-driver.zip(htc G7 usb驱动

    千次下载 热门讨论 2011-04-21 22:20:55
    htc G7 usb驱动,刷机的时候用到。
  • 目前,ST的USB驱动有两套,一套是早期的独立版的驱动,最新版为2.2.0;一套为针对其Cube系列的驱动,根据芯片不同可能有区别,具体见对应芯片的Cube驱动包 。 本文使用的为独立版的USB驱动 本文多出自于ST的官方文档...
  • USB驱动架构

    千次阅读 2013-11-16 22:46:06
    二、USB驱动架构 在WinCE中做USB相关的东西,首先要弄清一些概念,这非常关键。 1. Host与Function 首先要知道USB是主从结构的。拿PC和手机来说,PC端是主端,叫做Host,手机是从端,通常叫做Client。但是在...
  • Android ADB USB 驱动 万能配置方式

    万次阅读 2018-11-02 14:49:11
    Android ADB USB 驱动 万能配置方式latest_usb_driver_windows.zip 下载:新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建...
  • USB驱动程序之驱动框架

    千次阅读 2014-09-23 19:47:46
    这节开始学习USB驱动。当我们把USB设备接到PC上时,有这么一个现象: ① 右下角弹出"发现android phone" ②. 跳出一个对话框,提示你安装驱动程序
  • USB驱动及其源码分析

    千次阅读 2016-07-19 10:28:24
    一、USB理论部分 1.USB概述  USB1.0版本速度1.5Mbps(低速USB)、 USB1.1版本速度12...USB驱动由USB主机控制器驱动和USB设备驱动组成。USB主机控制器是用来控制USB设备和CPU之间通信的,USB主机控制器驱动主要用来驱
  • beaglebone usb驱动

    千次阅读 2013-08-23 19:50:30
    beaglebone usb驱动直接加载既可以。 http://download.csdn.net/detail/minghuiw/6000339
  • linux usb驱动

    千次阅读 2012-04-23 17:26:03
    设备越来越多,而Linux在硬件配置上仍然没有做到完全即插即用,对于Linux怎样配置和使用他们,也越来越成为困扰我们的一大问题。...而对于希望开发Linux系统下USB设备驱动的程序员,提供了初步学习USB驱动

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,881
精华内容 13,952
关键字:

usb驱动