ARM嵌入式笔记3

ARM 114浏览
<--------------------从USB系统架构开始记笔记-------------------------->
USB系统拓扑结构:
 图片图片

对于每个USB系统来说,都有一个称为Host控制器的设备,该Host控制器和一个根Hub作为一个整体。这个根Hub下可以接多级的Hub,每个Hub又可以接子Hub,每个USB设备作为一个节点接在不同级别的HUB上,每条USB总线最多可以接127个设备。
 
USB主控制器负责处理主机与设备之间的电气和协议层的互联。常见的USB主控制器规格有:
OHCI:主要是非PC系统上的USB芯片。
UHCI:大多是Intel和Via主板上的USB控制器芯片,他们都是USB1.1规格的。
EHCI: 是由Intel等几个厂商研发,兼容OHCI,UHCI,遵循USB2.0规范。

USB设备逻辑结构:在USB设备的逻辑组织中,包含设备,配置,接口和端点4个层次 ,设备通常有一个或多个配置,配置通常有一个或多个接口,接口有0或多个端点:
图片
 
每个USB设备都可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一个) ,配置由多个接口组成,在USB协议中,接口由多个端点组成,代表一个基本功能。是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口,而接口是端口的汇集。
例:
     一个USB播放器带有音频,视频功能,还有旋扭和按钮。
    配置1:音频(接口)+旋扭(接口)
    配置2:音频(接口)+视频(接口)+接扭(接口)
    配置3:视频(接口)+旋扭(接口) 
    音频接口,视频接口,旋扭接口,按钮接口均需要一个驱动程序。

USB设备中唯一可寻址部分是设备端点。它是位于USB设备或主机上的一个数据缓冲区,用来存放和发送USB的各种数据。主机和设备的通讯最终作用于设备上的各个端点,它是主机与设备间通讯流的一个逻辑终端。

每个USB设备有一个唯一的地址,这个地址是在设备连上主机时,由主机分配的,而设备中的每个端点在设备内部有唯一的端点号,这个端点号是在设计设备时给定的。每个端点都是一个简单的连接点,或者支持数据流进设备,或者支持其流出设备,两者不可兼得。 

基于PnP机制,设备被枚举时,它必须向主机报告各个端点的特性,包括端点号,通讯方向,端点支持的最大包大小,带宽要求等(其中端点支持的最大包大小叫做数据有效负载)。每个设备必须有端点0,它用于设备枚举和对设备进行一些基本的控制功能。除了端点0,其余的端点在设备配置之前不能与主机通信,只有向主机报告这些端点的特性并被确认后才能被激活。 
 
 当我们把USB设备插到我们的主机时,主机会自动识别出我们的USB设备类型。主机为何能自动识别出USB的设备类型:在每一个USB内部,有包含类似PCI配置寄存器这样的固定格式的数据,通过这些数据,USB主机就可以获取USB设备的类型,生产厂商等信息。这组固定格式的数据,我们就称之为USB描述符。标准的USB设备有五种USB描述符:设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符。

一个设备只有一个设备描述符, 而一个设备描述符可以包含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,就有几个端点描述符。
 图片
图片
相应的有配置描述符等在《USB总线接口开发指南.pdf》中

针对设备对系统资源需求的不同,在USB规范中规定了四种不同的数据传输方式,相应的有四个端点(等时端点,中断端点,控制端点,批量端点):
    1.等时传输,该方式用来连接对数据的正确性要求不高而对时间极为敏感的外部设备,如麦克风,音箱以及电话等。等时传输方式以固定的传输速率,连续不断地在主机与USB设备之间传输数据,在传送数据发生错误时,USB并不处理这些错误,而是继续传送新的数据。
    2.中断传输,该方式传送的数据量很小,但这些数据需要及时处理,以达到实时效果,此方式主要要在键盘,鼠标,手柄等外部设备上。当USB宿主要求设备传输数据时,中断端点会以一个固定的速率传送数据。要区别于系统中的中断,USB设备不能主动的发起传输,所有的传输只能由主机发起。中断传输理解为轮循传输更为准确。
    3.控制传输,主要用来传输设备控制命令,设备状态查询及确认命令。当USB设备收到这些命令和数据后,将依据先进先出的原则,按队列方式处理到达的数据。这种传输方式是每个USB设备都会有的。当USB插入主机时,需要用控制传输来确定USB设备类型。
    4.批量传输 ,该方式用来传输要求正确无误的数据。通常打印机,扫描仪,数码相机以这种方式与主机连接。
 
USB包,这个包在:国嵌共享版视频课程5(嵌入式LINUX内核驱动进阶班-下)第7天(USB系统架构)USB_Protocolusb_mouse.usb,用软件“Software USBTracerTrainer”(在目录:第7天(USB系统架构)USB协议分析软件CATC_ALL.zip)来打开这个.usb文件,打开后,点选图片图片,这六个键后,会显示如下内容:

图片 
上图的USB包中 ,USB的数据传递首先是基于传输(Transfer)的:Transfer分为中断传输,批量传输,同步传输,控制传输。
一次传输(Transfer)由一个或多个事务(Transaction)构成,事务可分为In事务,Out事务,Setup事务。
一个事务(Transaction)是由一个或多个包(Packet)构成的,包可分为令牌包(Setup),数据包(Data),握手包(ACK)和特殊包。
一个包由多个域构成,域可分为同步域(SYNC),标识域(PID),地址域(ADDR),端点域(ENDP),帧号域(FRAM),数据域(DATA),校验域(CRC)。

USB设备枚举: 
     USB设备在正常工作以前,第一件要做的事就是枚举。枚举是让主机认得这个USB设备,并且为该设备准备资源,建立好主机与设备之间的数据传递机制。
USB设备枚举步骤:
    1.获取设备描述符,如上图Transaction:0先发一个setup包,告诉USB设备要发一个命令,再发一个data包,data包中Data字段对应USB协议中的内容(这里是获取设备描述符)。USB中是小端,Data中左侧的为低位,然后USB设备发回一个握手包,表示已经知道主机要读相应的内容。然后主机发一个Transaction:1,首先IN包,通知USB设备发数据,随后USB发一个Data包给主机,随后主机发一个握手包,表示已经收到数据。Transaction:2,Transaction:3都是类似Transaction:1。最后读完之后必须要有一个OUT事务:Transaction:4,这是规范所规定要有的。

    2.复位,对应上图Transaction:4下面的Reset包。
    3.设置地址,发一个Transaction:5事务,首先发一个Setup包,,告诉USB设备要发一个命令,再发一个data包,data包中Data字段对应USB协议中的内容(这里是设置地址)。然后USB设备发回一个握手包,表示已经设置好相应的内容。然后主机发一个Transaction:6 IN事务,这是规范所规定要有的。
    4.再次获取设备描述符,类似第1步。
    5.获取配置描述符
    6.获取接口,端点描述符
    7.获取字符串描述符
    8.选择设备配置 
 
完成枚举后通讯的包(国嵌共享版视频课程5(嵌入式LINUX内核驱动进阶班-下)第7天(USB系统架构)USB_Protocolusb_mouse_buttion.usb):     
图片 

设备状态:
图片 

 
Linux USB系统:
图片 

下图中“运行Linux的设备”是指运行Linux系统的USB设备,如:运行Linux系统的硬盘,把运行Linux系统的开发板连接Linux主机把开发板当作网卡来使用:
图片
 
Mass Storage(大容量存储设备):
配置编译开发板系统,使其支持Mass Storage设备:
1.设置支持热插拔:
图片 
2. 
 图片
3.
图片 
4.
 图片


USB HID(人体输入学设备:鼠标,键盘,手柄等):
 配置编译开发板系统,使其支持USB HID设备,视频很短只有十二分钟,具体看视频。

 RNDIS (支持热插拔的网卡设备),相应的视频是把MINI2440配置成网卡设备。
 
CDC/ACM (把USB模拟成串口),

USB设备驱动程序:
 图片

USB中一个接口(Interface)对应一个功能,配置(Config)包含多个接口是功能的组合,也相应的要有一个驱动(USB driver),一个USB设备驱动程序对应一个USB接口,而不是整个USB设备:
图片
 
 USB驱动程序位于不同的内核子系统和USB主控制器之间,USB核心为USB驱动提供了一个用于访问和控制USB硬件的软件接口,使得USB设备驱动程序不必考虑USB硬件控制器。

在Linux内核中,使用struct usb_driver结构描述一个USB驱动: 
struct usb_driver{
    const char *name;    //驱动程序名
    int (*probe)(struct usb_interface *intf, const usb_device_id *id);//当USB核心发现了该驱动能够处理的USB接口时,调用该函数
    void (*disconnect)(struct usb_interface *intf);//当相应的USB接口被移除时,调用该函数
    const struct usb_device_id *id_table;    //USB驱动能够处理的设备列表

struct usb_device_id {
/* which fields to match against? */
__u16match_flags;
/* Used for product specific matches; range is inclusive */
__u16idVendor;    //制造商ID
__u16idProduct;    //产品ID
__u16bcdDevice_lo;
__u16bcdDevice_hi;
/* Used for device class matches */
__u8bDeviceClass;
__u8bDeviceSubClass;
__u8bDeviceProtocol;
/* Used for interface class matches */
__u8bInterfaceClass;
__u8bInterfaceSubClass;
__u8bInterfaceProtocol;
/* not matched against */
kernel_ulong_tdriver_info;

}; 

内核提供了宏USB_DEVICE来定义一种USB设备的USB_DEVICE(vend,prod)
    vend: USB Vendor ID
    prod: USB Product ID 
例(USB OV511摄像头):
    #define VEND_OMNIVISION 0x05A9
    #define PROD_OV511 0x0511
    USB_DEVICE( 
VEND_OMNIVISION ,PROD_OV511 )
 
内核提供了宏USB_INTERFACE_INFO来定义一类USB设备的USB_INTERFACE_INFO(cl ,sc, pr)
    cl: bInterfaceClass value
    sc:bInterfaceSubClass value
    pr:bInterfaceProtocol value
例(USB鼠标):
     
USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE)

注册USB驱动程序:
    static inline int usb_register(struct usb_driver *driver);
 
在Linux内核中使用struct usb_device来描述一个USB设备:
struct usb_device{
    int devnum;    //USB设备号
    char devpath[16];    //设备ID字符串
    enum usb_device_state;    //设备状态:已连接,未配置
    enum usb_device_speed;    //高速,全速,低速
    ...... ......
    struct usb_device_descriptor descriptor;    //USB设备描述符
    struct usb_host_config *config;
    ...... ......
    char *product;    //产品名
    char *manufacturer;    //厂商名
    char *serial;    //设备串号

/* USB_DT_DEVICE: Device descriptor  设备描述符*/
struct usb_device_descriptor {
__u8  bLength;
__u8  bDescriptorType;
__le16 bcdUSB;
__u8  bDeviceClass;
__u8  bDeviceSubClass;
__u8  bDeviceProtocol;
__u8  bMaxPacketSize0;
__le16 idVendor;
__le16 idProduct;
__le16 bcdDevice;
__u8  iManufacturer;
__u8  iProduct;
__u8  iSerialNumber;
__u8  bNumConfigurations;
} __attribute__ ((packed)); //按字节对齐
 
在Linux内核中使用struct usb_host_config来描述一个USB配置(这是Linux所特有的,不是USB协议中的结构):
struct usb_host_config{
    struct usb_config_descriptor;    //配置描述符
    char *string;    //配置的字符串(如果存在)
    ...... ......
    struct usb_interface *interface[USB_MAXINTERFACES];    //接口链表

/*配置描述符*/

struct usb_config_descriptor {
__u8  bLength;
__u8  bDescriptorType;
__le16 wTotalLength;
__u8  bNumInterfaces;
__u8  bConfigurationValue;
__u8  iConfiguration;
__u8  bmAttributes;
__u8  bMaxPower;
} __attribute__ ((packed));

 
在Linux内核中使用struct usb_interface来描述USB接口:
struct usb_interface{
    struct usb_host_interface *altsettings;    //接口设置数组
    struct usb_host_interface *cur_altsetting;    //当前设置
    unsigned num_altsetting;    //设置数

一个配置包含一个或多个接口,
一个接口包含一个或多个设置,
 比如一个手机可以有多个配置,比如可以作为电话,可以接在PC上作为U盘,这二种情况属于不同的配置,再来看设置,一个手机作为电话已经确定了,但是通话场景(室外模式,会议模式)可以变,每种场景就可以算一个设置。
 struct usb_host_interface{
    struct usb_interface_descriptor desc;    //接口描述符
    struct usb_host_endpoint;    //接口包含的端点

 /* USB_DT_INTERFACE: Interface descriptor 接口描述符*/

struct usb_interface_descriptor {
__u8  bLength;
__u8  bDescriptorType;
__u8  bInterfaceNumber;
__u8  bAlternateSetting;
__u8  bNumEndpoints;
__u8  bInterfaceClass;
__u8  bInterfaceSubClass;
__u8  bInterfaceProtocol;
__u8  iInterface;
} __attribute__ ((packed));
/*端点*/
struct usb_host_endpoint {
struct usb_endpoint_descriptordesc;    //端点描述符
struct list_headurb_list;
void*hcpriv;
struct ep_device *ep_dev;/* For sysfs info */
struct usb_host_ss_ep_comp*ss_ep_comp;/* For SS devices */
unsigned char *extra;   /* Extra descriptors */
int extralen;
int enabled;
};

/* USB_DT_ENDPOINT: Endpoint descriptor端点描述符*/
struct usb_endpoint_descriptor {
__u8  bLength;
__u8  bDescriptorType;
__u8  bEndpointAddress;
__u8  bmAttributes;
__le16 wMaxPacketSize;
__u8  bInterval;
/* NOTE:  these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8  bRefresh;
__u8  bSynchAddress;

} __attribute__ ((packed)); 
 

USB请求块(URB) :
    USB请求块是USB设备驱动中用来描述与USB设备通讯所用的基本载体和核心数据结构,非常类似于网络设备驱动中的sk_buff结构体,是USB主机与设备通信的“电波”;

URB处理流程:
1.USB设备驱动程序创建并初始化一个访问特定USB设备特定端点的URB,并提交给USB Core;
2. USB Core提交该URB到USB主控制器驱动程序。
3.USB主控制器驱动程序根据该URB描述的信息,来访问USB设备。
4.当设备访问结束后,USB主控制器驱动程序通知USB设备驱动程序。

创建URB的函数为:
    struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
    参数:iso_packets:urb所包含的等时数据包的个数。mem_flags:内存分配标识(如GFP_KERNEL),参考kmalloc
 对于中断urb,使用如下函数来初始化:
    static inline void usb_fill_int_urb(
                                    struct urb *urb,    //要初始化的urb指针

   struct usb_device *dev,    //urb所要访问的设备
   unsigned int pipe,    //要访问的端点所对应的管道,使用usb_sndintpipe()或usb_rcvintpipe()创建。
                                                        (管道:驱动程序的数据缓冲区与一个端点的连接,它代表了一种在两者之间移动数据的能力)
   void *transfer_buffer,    //要传输的数据的缓冲区
   int buffer_length,    //transfer_buffer所指缓冲区长度
   usb_complete_t complete_fn,    //当完成该urb所请求的操作时,要调用的回调函数。
   void *context,    //complete_fn函数所需要的上下文,通常取值为dev
   int interval)     //urb被调度的时间间隔
对于批量urb,控制urb分别使用usb_fill_bulk_urb,usb_fill_control_urb函数来初始化 :
等时urb没有像中断,控制和批量urb那样的初始化函数,我们只能手动的初始化等时urb。
创建并初始化urb后,使用如下函数把urb提交给USB核心:
    int usb_submit_urb(struct *urb, gfp_t mem_flags);
    参数:urb:指向urb的指针,mem_flags:内存分配标识,它告诉USB核心如何分配内存缓冲区。
 
URB被提交到USB核心后,USB核心指定usb主控制器驱动程序来处理该urb,在3种情况下,urb会被认为已经处理完成 :
    1. urb被成功发送给设备,并且设备返回正确的确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送。对于一个输入urb,请求的数据被成功收到。
    2. 如果发送数据到设备或从设备接收数据时发生错误,urb->status将记录错误值。
    3.urb被取消,这发生在驱动通过usb_unlink_urb()或usb_kill_urb()函数取消urb,或urb虽已提交,而USB设备被拔出的情况下。
当URB处理完成后,URB完成函数将被调用。