USB入门

简述

  USBUniversal Serial Bus)全称通用串口总线,USB为解决即插即用需求而诞生,支持热插拔。USB协议版本有USB1.0USB1.1USB2.0USB3.1等,USB2.0目前比较常用。由于USB是主从模式的结构,设备与设备之间、主机与主机之间不能互连,为解决这个问题,扩大USB的应用范围,出现了USB OTG,全拼 ON The GoUSB OTG 同一个设备,在不同的场合下可行在主机和从机之间切换。

USB网络的基本拓扑结构是星型的。一个USB系统由一个或多个USB设备(外设)、一个或多个集线器(hub)和一个主机组成。计算机主机有时又叫作主控制器,在一个USB网络中只能有一个主机。主控制器内置了一个根集线器,提供主控制器上的初始附属点。USB是一种高速总线,它连接的设备数量最多可达127个。

主机定时对集线器的状态进行查询。当一个新设备接入一个集线器时,这个集线器就会向主机报告状态改变,主机发出一个命令使端口有效并对其进行重新设置。位于这个端口上的设备进行响应,主机收到关于设备的信息。根据这些信息,主机的操作系统确定对这个设备使用哪种驱动程序,接着设备被分配一个唯一标识的地址,主机向它发出内部设置请求。当一个设备从总线上移走时,主机就从其可用资源列表中将这个设备删除。主机对USB设备的探测和识别叫作总线列举(bus enumeration)。

USB主控制器规格

OHCIOpen Host Controller Interface,为非PC系统上以及带有SiSAli芯片组的PC主板上的USB芯片提供支持,如扩展卡、嵌入式开发板的USB主控等。USB1.1接口标准,它不仅仅针对USB,还支持其他一些接口,比如支持Apple的火线(IEEE1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务就相对简单。

UHCIUniversal Host Controller Interface,用来为大多数PC主板(包括IntelVia)上的USB芯片提供支持。由于UHCI的硬件线路比OHCI简单,所以成本低,但需要复杂的驱动程序,CPU负荷稍重。Intel主导的对USB1.01.1的接口标准,与OHCI不兼容。

EHCIEnhanced Host Controller InterfaceIntel主导的USB2.0接口标准,兼容于OHCIUHCIEHCI仅提供USB2.0的高速功能,而依靠UHCIOHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。

XHCIeXtensible Host controller Interface,可扩展主控制器接口,由Intel开发,面向USB3.0,同时支持USB2.0及以下的设备。XHCI支持所有种类速度的USB设备(USB3.0 SuperSpeedUSB 2.0 Low-Full-and High-speedUSB 1.1 Low- and Full-speed)。

USB1.1支持的数据传输率为12Mbpsfull-speed)和1.5Mbps(用于慢速外设,low-speed),USB2.0支持的数据传输率可达480Mbpshigh-speed),USB3.0支持的传输速率达5.0Gbit/sSupperSpeed)。

主要的控制器IPController IP)提供商有:

  1. synopsys-DesignWareDWC3Synopsys DesignWare C3 IP block)用在QualcommExynosOMAP SOCsDWC2用在Rockchip SoCs

  2. Chipidea-用在QualcommHigh-Speed OTG上。

  3. ST/Ericsson

  4. Mentor Graphics-Inventra IP,用在TI DaVinciOMAP2OMAP3上,AD部分SOCsAllwinner(全志) SoCs

linux内核drivers/usb下有控制器支持的代码:chipideadwc2dwc3等,当然最通用的还是EHCI/XHCI/UHCI/OHCI

USB

USB数据传输有四种类型:

控制传输(Control Transfers,非周期,突发。用于对总线和总线上的设备进行设置,并返回状态信息,即用于命令和状态的传输。

中断传输(Interrupt Transfers,周期性,低频率。用来在一定的时间片发送数据,这个时间片的范围从1ms-255ms。如人机接口设备(HID)中的鼠标、键盘、轨迹球等。

同步传输(Isochronous Transfers,等时传输),周期性。用来发送对实时性要求较高的数据,比如送往一个输出设备的音频数据,与块传输(这种传输是双向的)不同,同步传输是单向的,并且不包含循环冗余校验(CRC)字段。

块传输(Bulk Transfers,批量传输,大容量数据传输),非周期,突发。通过USB设备对数据进行异步发送,大容量数据的通信,数据可以占用任意带宽,并容忍延迟。如USB打印机、扫描仪、大容量存储设备等。

数据在USB设备之间以包的方式进行传输。一个传输由一个或多个包组成,一个包包括1SYNC(同步)字节,1PID(包ID),内容(数据、地址等)和1CRC字段。SYNC字节锁定接收器时钟的相位,它相当于RS232帧的起始位。PID指定包的功能,比如它是否是一个数据包或配置包。包ID的高4位是低4位取反的结果,这样做是为了增加一种错误检测机制。

USB包由四种类型:令牌、数据、握手和先导包。

  1. 令牌包

        令牌是个24字节的包,它决定总线上发生的数据传输的类型,分为输入包、输出包、设置包和帧起始包(注意这里的输入包是用于设置输入命令的,输出包是用来设置输出命令的,而不是放数据的)

2. 数据包

分为DATA0包和DATA1包,当USB发送数据的时候,如果一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0包和DATA1包交替发送,即如果第一个数据包是DATA0,那第二个数据包就是DATA1。但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATA0,格式如下:

                                                        SYNC + PID + 0~1023字节 + CRC16

3. 握手包

        握手包包括 ACKNAKSTALL以及NYET 四种,其中 ACK 表示肯定的应答,成功的数据传输。NAK 表示否定的应答,失败的数据传输,要求重新传输。STALL表示功能错误或端点被设置了STALL属性。NYET表示尚未准备好,要求等待。

描述符是一个用来将设备的性能通知主机的数据包,它包含1个设备生产上的标识符、1个产品标识、1个类别类型和设备的内部设置,比如终端和对电源的需求。每个生产商都有一个唯一的ID,每个产品依次也有一个唯一的ID。计算机主机上的软件根据从描述符中得到的信息判断一个设备有哪些功能,以及主机如何与设备进行交互。

linux USB驱动层次

主机侧与设备侧USB驱动

USB采用树形拓扑结构,主机侧和设备侧的USB控制器分别称为主机控制器(Host Controller)和USB设备控制器(UDC),每条总线上只有一个主控制器,负责协调主机和设备间的通信,而设备不能主动向主机发送任何消息。USB驱动可以从两个角度去观察,一个角度是主机侧,一个角度是设备侧。

主机侧USB驱动包括两类:USB主机控制器驱动和USB设备驱动,前者控制插入其中的USB设备,后者控制USB设备如何与主机通信。USB核心负责USB驱动管理和协议处理,包括:通过定义一些数据结构、宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;通过全局变量维护整个系统的USB设备信息;完成设备热插拔控制、总线数据传输控制等。

USB驱动程序直接访问硬件,控制USB设备和主机间的底层通信,向上提供与硬件相关操作的回调函数。当前Gadget APIUDC驱动程序回调函数的简单封装。Gadget驱动程序具体控制USB设备驱动功能的实现。它使用GadgetAPI控制UDC实现上述功能。GadgetAPI把下层的UDC驱动程序和上层的Gadget驱动程序隔离开,使得在linux系统中编写USB设备侧驱动程序时能够把功能的实现和底层通信分离。

设备、配置、接口、端点

usb设备的逻辑组织中,包含设备、配置、接口和端点4个层次。

每个USB设备都提供了不同级别的配置信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合(在探测/连接期间需从其中选定一个),配置由多个接口组成。

USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口。每个配置中可以有多个接口,而设备接口是端点的汇集(collection)。例如USB扬声器可以包含一个音频接口以及对旋钮和按钮的接口。一个配置中的所有接口可以同时有效,并可被不同的驱动程序连接。每个接口可以有备用接口,以提供不同质量的服务参数。

端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个端点都有唯一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式、总线访问频率、带宽、端点号和数据包的最大容量等。一个USB端点只能在一个方向承载数据,或者从主机到设备(输出端点),或者从设备到主机(输入端点),因此端点可看作一个单向的管道。端点0通常为控制端点,用于设备初始化参数等。只要设备连接到USB上并且上电端点0就可以被访问。端点12等一般用作数据端点,存放主机与设备间往来的数据。

》》设备通常有一个或多个配置;

》》配置通常有一个或多个接口;

》》接口通常有一个或多个设置;

》》接口有0个或多个端点。

这种层次化配置信息在设备中通过一组标准的描述符来描述,如下所示。

》》设备描述符:关于设备的通用信息,如供应商ID、产品ID和修订ID,支持的设备类、子类和使用的协议以及默认端点的最大包大小等。在linux内核中,USB设备用usb_device结构体来描述,USB设备描述符定义为usb_device_descriptor结构体。

 

》》配置描述符:此配置中的接口数、支持的挂起和恢复能力以及功率要求。USB配置在内核中使用usb_host_config结构体描述,USB配置描述符定义为结构体usb_config_descriptor

》》接口描述符:接口类、子类和使用的协议,接口备用配置的数据和端点数目。USB接口在内核中使用usb_interface结构体描述,USB接口描述符定义为结构体usb_interface_descriptor

》》端点描述符:端点地址、方向和类型,支持的最大包大小,如果是终端类型的端点则还包括轮询频率。USB端点使用usb_host_endpoint结构体描述,USB端点描述符定义为usb_endpoint_descriptor

》》字符串描述符:在其他描述符中会为某些字段提供字符串索引,它们可被用来检索描述性字符串,可以以多种语言形式提供。字符串描述符是可选的,有的设备有,有的设备没有,字符串描述符对应于usb_string_descriptor结构体。

可通过lsusb查看USB设备各类描述符。

Usage: lsusb [options]...

List USB devices

-v, --verbose

Increase verbosity (show descriptors)

-s [[bus]:][devnum]

Show only devices with specified device and/or

bus numbers (in decimal)

-d vendor:[product]

Show only devices with the specified vendor and

product ID numbers (in hexadecimal)

-D device

Selects which device lsusb will examine

-t, --tree

Dump the physical USB device hierarchy as a tree

-V, --version

Show version of program

-h, --help

Show usage and help

一个FT2232H设备的lsusb显示(FT2232H包含两个独立的usb口):

wang@ubuntu:~$ lsusb -s 001:002 -v
Bus 001 Device 002: ID 0403:6010 Future Technology Devices International, Ltd FT2232C Dual USB-UART/FIFO IC
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x0403 Future Technology Devices International, Ltd
idProduct 0x6010 FT2232C Dual USB-UART/FIFO IC
bcdDevice 7.00
iManufacturer 1 FTDI
iProduct 2 Dual RS232-HS
iSerial 0
bNumConfigurations 1
Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 55 bNumInterfaces 2 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA
Interface Descriptor: bLength
9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 2 Dual RS232-HS Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 2 Dual RS232-HS Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 Device Status: 0x0000 (Bus Powered)

USB主机驱动

主控制器驱动

usb_hcd结构体描述USB主控制器驱动,它包含USB主机控制器的“家务”信息,硬件资源、状态描述和用于操作主机控制器的hc_driver等。

struct usb_hcd {


/*

* housekeeping

*/

struct usb_bus self; /* hcd is-a bus */

struct kref kref; /* reference counter */


const char *product_desc; /* product/vendor string */

int speed; /* Speed for this roothub.

* May be different from

* hcd->driver->flags & HCD_MASK

*/

char irq_descr[24]; /* driver + bus # */


struct timer_list rh_timer; /* drives root-hub polling */

struct urb *status_urb; /* the current status urb */

#ifdef CONFIG_PM

struct work_struct wakeup_work; /* for remote wakeup */

#endif


/*

* hardware info/state

*/

const struct hc_driver *driver; /* hw-specific hooks */


/*

* OTG and some Host controllers need software interaction with phys;

* other external phys should be software-transparent

*/

struct usb_phy *usb_phy;

struct phy *phy;

/* more shared queuing code would be good; it should support

* smarter scheduling, handle transaction translators, etc;

* input size of periodic table to an interrupt scheduler.

* (ohci 32, uhci 1024, ehci 256/512/1024).

*/

……

/* The HC driver's private data is stored at the end of

* this structure.

*/

unsigned long hcd_priv[0]

__attribute__ ((aligned(sizeof(s64))));

};

hc_driver包含用于操作主机控制器的钩子函数,即“hw-specific hooks”

struct hc_driver {

const char *description; /* "ehci-hcd" etc */

const char *product_desc; /* product/vendor string */

size_t hcd_priv_size; /* size of private data */


/* irq handler */

irqreturn_t (*irq) (struct usb_hcd *hcd);


int flags;


/* called to init HCD and root hub */

int (*reset) (struct usb_hcd *hcd);

int (*start) (struct usb_hcd *hcd);

……

/* cleanly make HCD stop writing memory and doing I/O */

void (*stop) (struct usb_hcd *hcd);


/* shutdown HCD */

void (*shutdown) (struct usb_hcd *hcd);


/* return current frame number */

int (*get_frame_number) (struct usb_hcd *hcd);


/* manage i/o requests, device state */

int (*urb_enqueue)(struct usb_hcd *hcd,

struct urb *urb, gfp_t mem_flags);

int (*urb_dequeue)(struct usb_hcd *hcd,

struct urb *urb, int status);

……

/* Allocate endpoint resources and add them to a new schedule */

int (*add_endpoint)(struct usb_hcd *, struct usb_device *,

struct usb_host_endpoint *);

/* Drop an endpoint from a new schedule */

int (*drop_endpoint)(struct usb_hcd *, struct usb_device *,

struct usb_host_endpoint *);

……

int (*check_bandwidth)(struct usb_hcd *, struct usb_device *);

/* Reset the device schedule to the last known good schedule,

* which was set from a previous successful call to

* check_bandwidth(). This reverts any add_endpoint() and

* drop_endpoint() calls since that last successful call.

* Used for when a check_bandwidth() call fails due to resource

* or bandwidth constraints.

*/

void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);

/* Returns the hardware-chosen device address */

int (*address_device)(struct usb_hcd *, struct usb_device *udev);

/* prepares the hardware to send commands to the device */

int (*enable_device)(struct usb_hcd *, struct usb_device *udev);

/* Notifies the HCD after a hub descriptor is fetched.

* Will block.

*/

int (*update_hub_device)(struct usb_hcd *, struct usb_device *hdev,

struct usb_tt *tt, gfp_t mem_flags);

int (*reset_device)(struct usb_hcd *, struct usb_device *);

/* Notifies the HCD after a device is connected and its

* address is set

*/

int (*update_device)(struct usb_hcd *, struct usb_device *);

int (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int);

/* USB 3.0 Link Power Management */

/* Returns the USB3 hub-encoded value for the U1/U2 timeout. */

int (*enable_usb3_lpm_timeout)(struct usb_hcd *,

struct usb_device *, enum usb3_link_state state);

/* The xHCI host controller can still fail the command to

* disable the LPM timeouts, so this can return an error code.

*/

int (*disable_usb3_lpm_timeout)(struct usb_hcd *,

struct usb_device *, enum usb3_link_state state);

int (*find_raw_port_number)(struct usb_hcd *, int);

/* Call for power on/off the port if necessary */

int (*port_power)(struct usb_hcd *hcd, int portnum, bool enable);


};

urb_enqueue()函数非常关键,上层通过usb_submit_urb()提交1USB请求后,该函数调用usb_hcd_submit_urb(),并最终调用至usb_hcddriver成员(hc_driver类型)的urb_enqueue()函数。

linux中,如下函数创建、增加和移除HCD

struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,

struct device *dev, const char *bus_name);

int usb_add_hcd(struct usb_hcd *hcd,

unsigned int irqnum, unsigned long irqflags);

void usb_remove_hcd(struct usb_hcd *hcd);

drivers/usb/host中分别实现了不同USB协议的主控制器驱动,如UHCIOHCIEHCIXHCI

USB设备驱动

从主机角度看,怎样访问被插入的USB设备。

USB设备和USB接口在sysfs中均表示为单独的USB设备,其目录命名规则如下:

根集线器-集线器端口号(-集线器端口号-…):配置.接口

tree /sys/bus/usb

/sys/bus/usb

├── devices

│   ├── 1-0:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-0:1.0

│   ├── 1-1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

│   ├── 1-1:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

│   ├── 1-1:1.1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.1

│   ├── 2-0:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-0:1.0

│   ├── 2-1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1

│   ├── 2-1:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0

│   ├── 2-2 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2

│   ├── 2-2:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2:1.0

│   ├── usb1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1

│   └── usb2 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2

├── drivers

│   ├── ftdi_sio

│   │   ├── 1-1:1.1 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.1

│   │   ├── bind

│   │   ├── module -> ../../../../module/usbserial

│   │   ├── uevent

│   │   └── unbind

│   ├── hub

│   │   ├── 1-0:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-0:1.0

│   │   ├── 2-0:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-0:1.0

│   │   ├── 2-2:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2:1.0

│   │   ├── bind

│   │   ├── module -> ../../../../module/usbcore

│   │   ├── new_id

│   │   ├── remove_id

│   │   ├── uevent

│   │   └── unbind

│   ├── usb

│   │   ├── 1-1 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1

│   │   ├── 2-1 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1

│   │   ├── 2-2 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2

│   │   ├── bind

│   │   ├── uevent

│   │   ├── unbind

│   │   ├── usb1 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1

│   │   └── usb2 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2

│   ├── usbfs

│   │   ├── 1-1:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0

│   │   ├── bind

│   │   ├── module -> ../../../../module/usbcore

│   │   ├── new_id

│   │   ├── remove_id

│   │   ├── uevent

│   │   └── unbind

│   ├── usbhid

│   │   ├── 2-1:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0

│   │   ├── bind

│   │   ├── module -> ../../../../module/usbhid

│   │   ├── new_id

│   │   ├── remove_id

│   │   ├── uevent

│   │   └── unbind

│   ├── usbserial

│   │   ├── bind

│   │   ├── module -> ../../../../module/usbserial

│   │   ├── uevent

│   │   └── unbind

│   └── usbserial_generic

│   ├── bind

│   ├── module -> ../../../../module/usbserial

│   ├── uevent

│   └── unbind

├── drivers_autoprobe

├── drivers_probe

└── uevent

linux中用usb_driver描述一个USB设备驱动。

/**

* struct usbdrv_wrap - wrapper for driver-model structure

* @driver: The driver-model core driver structure.

* @for_devices: Non-zero for device drivers, 0 for interface drivers.

*/

struct usbdrv_wrap {

struct device_driver driver;

int for_devices;

};


/**

* struct usb_driver - identifies USB interface driver to usbcore

* @name: The driver name should be unique among USB drivers,

* and should normally be the same as the module name.

* @probe: Called to see if the driver is willing to manage a particular

* interface on a device. If it is, probe returns zero and uses

* usb_set_intfdata() to associate driver-specific data with the

* interface. It may also use usb_set_interface() to specify the

* appropriate altsetting. If unwilling to manage the interface,

* return -ENODEV, if genuine IO errors occurred, an appropriate

* negative errno value.

* @disconnect: Called when the interface is no longer accessible, usually

* because its device has been (or is being) disconnected or the

* driver module is being unloaded.

* @unlocked_ioctl: Used for drivers that want to talk to userspace through

* the "usbfs" filesystem. This lets devices provide ways to

* expose information to user space regardless of where they

* do (or don't) show up otherwise in the filesystem.

* @suspend: Called when the device is going to be suspended by the

* system either from system sleep or runtime suspend context. The

* return value will be ignored in system sleep context, so do NOT

* try to continue using the device if suspend fails in this case.

* Instead, let the resume or reset-resume routine recover from

* the failure.

* @resume: Called when the device is being resumed by the system.

* @reset_resume: Called when the suspended device has been reset instead

* of being resumed.

* @pre_reset: Called by usb_reset_device() when the device is about to be

* reset. This routine must not return until the driver has no active

* URBs for the device, and no more URBs may be submitted until the

* post_reset method is called.

* @post_reset: Called by usb_reset_device() after the device

* has been reset

* @id_table: USB drivers use ID table to support hotplugging.

* Export this with MODULE_DEVICE_TABLE(usb,...). This must be set

* or your driver's probe function will never get called.

* @dynids: used internally to hold the list of dynamically added device

* ids for this driver.

* @drvwrap: Driver-model core structure wrapper.

* @no_dynamic_id: if set to 1, the USB core will not allow dynamic ids to be

* added to this driver by preventing the sysfs file from being created.

* @supports_autosuspend: if set to 0, the USB core will not allow autosuspend

* for interfaces bound to this driver.

* @soft_unbind: if set to 1, the USB core will not kill URBs and disable

* endpoints before calling the driver's disconnect method.

* @disable_hub_initiated_lpm: if set to 1, the USB core will not allow hubs

* to initiate lower power link state transitions when an idle timeout

* occurs. Device-initiated USB 3.0 link PM will still be allowed.

*

* USB interface drivers must provide a name, probe() and disconnect()

* methods, and an id_table. Other driver fields are optional.

*

* The id_table is used in hotplugging. It holds a set of descriptors,

* and specialized data may be associated with each entry. That table

* is used by both user and kernel mode hotplugging support.

*

* The probe() and disconnect() methods are called in a context where

* they can sleep, but they should avoid abusing the privilege. Most

* work to connect to a device should be done when the device is opened,

* and undone at the last close. The disconnect code needs to address

* concurrency issues with respect to open() and close() methods, as

* well as forcing all pending I/O requests to complete (by unlinking

* them as necessary, and blocking until the unlinks complete).

*/

struct usb_driver {

const char *name;


int (*probe) (struct usb_interface *intf,

const struct usb_device_id *id);


void (*disconnect) (struct usb_interface *intf);


int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,

void *buf);


int (*suspend) (struct usb_interface *intf, pm_message_t message);

int (*resume) (struct usb_interface *intf);

int (*reset_resume)(struct usb_interface *intf);


int (*pre_reset)(struct usb_interface *intf);

int (*post_reset)(struct usb_interface *intf);


const struct usb_device_id *id_table;


struct usb_dynids dynids;

struct usbdrv_wrap drvwrap;

unsigned int no_dynamic_id:1;

unsigned int supports_autosuspend:1;

unsigned int disable_hub_initiated_lpm:1;

unsigned int soft_unbind:1;

};

#define to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)

在编写新的USB设备驱动时,主要应该完成的工作是probe()disconnect()函数,即探测和断开函数,它们分别在设备被插入和拔出的时候被调用,用于初始化和释放软硬件资源。对usb_driver的注册和注销通过两个函数完成:

/*

* use these in module_init()/module_exit()

* and don't forget MODULE_DEVICE_TABLE(usb, ...)

*/

extern int usb_register_driver(struct usb_driver *, struct module *, const char *);


/* use a define to avoid include chaining to get THIS_MODULE & friends */

#define usb_register(driver) \

usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)


extern void usb_deregister(struct usb_driver *);

usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id数组,usb_device_id结构体用于包含USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息及其要匹配标志成员match_flags(标明要与哪些成员匹配,包含DEV_LODEV_HIDEV_CLASSDEV_SUBCLASSDEV_PROTOCOLINT_CLASSINT_SUBCLASSINT_PROTOCOL)。可用如下宏生产相应usb_device_id

 /**

* USB_DEVICE - macro used to describe a specific usb device

* @vend: the 16 bit USB Vendor ID

* @prod: the 16 bit USB Product ID

*

* This macro is used to create a struct usb_device_id that matches a

* specific device.

*/

#define USB_DEVICE(vend, prod) \

.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \

.idVendor = (vend), \

.idProduct = (prod)

/**

* USB_DEVICE_VER - describe a specific usb device with a version range

* @vend: the 16 bit USB Vendor ID

* @prod: the 16 bit USB Product ID

* @lo: the bcdDevice_lo value

* @hi: the bcdDevice_hi value

*

* This macro is used to create a struct usb_device_id that matches a

* specific device, with a version range.

*/

#define USB_DEVICE_VER(vend, prod, lo, hi) \

.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \

.idVendor = (vend), \

.idProduct = (prod), \

.bcdDevice_lo = (lo), \

.bcdDevice_hi = (hi)

USB核心检测到某个设备的属性和某个驱动程序的usb_device_id结构体所携带的信息一致时,这个驱动程序的probe()函数就被执行了。拔掉设备或者卸载掉驱动模块后,USB核心就执行disconnect()函数来响应这个动作。

上述usb_driver结构体中的函数是USB设备驱动中与USB相关的部分,而USB只是一个总线,USB设备驱动真正的主体工作仍然是USB设备本身所属类型的驱动,如字符设备、tty设备、块设备、输入设备等。因此USB设备驱动包含其作为总线上挂接设备的驱动和本身所属设备类型的驱动两部分。

usb_driver起到了牵线的作用,即在probe()里注册相应的字符、tty设备,在disconnect()注销相应的字符、tty设备。

usb_driver本身只是完成找到USB设备、管理USB设备连接和断开的作用。

USB UDCUSB Gadget

USB设备控制器(UDC)驱动指的是作为其他USB主机控制器外设的USB硬件设备上底层硬件控制器的驱动,该硬件和驱动负责将一个USB设备依附于一个USB主控制器上。例如,linux手机连接PC时,手机中的底层USB控制器行使USB设备控制器的功能,这时候在底层的是UDC驱动。

代码位于drivers/usb/gadget

USB OTG

USB OTG标准在完全兼容USB2.0标准的基础上,允许设备既可作为主机,也可作为外设操作,OTG新增了主机通令协议(HNP)和对话请求协议(SRP)。

OTG中,初始主机设备称为A设备,外设称为B设备。可用电缆的连接方式来决定初始角色。两用设备使用新型Mini-AB插座,从而使Mini-A插头、Mini-B插头和Mini-AB插座增添了第5个引脚(ID),以用于标识不同的电缆端点。Mini-AB插头中的ID引脚接地,Mini-B插头中的ID引脚浮空。当OTG设备检测到接地的ID引脚时,标识默认的是A设备(主机),而检测到ID引脚悬空的设备则认为是B设备(外设)。系统一旦连接后,OTG的角色还可以更换,以采用新的HNP协议。而SRP允许B设备请求A设备打开VBUS电源并启动一次对话。一次OTG对话可通过A设备提供VBUS电源的时间确定。

usb_otg的代码一般在usbphy端实现,位于drivers/usb/phydrivers/usb/musb

USB请求块(URB

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

USB枚举

USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,如厂商ID和产品ID等等,并配置设备使其工作,这个过程称为BUS枚举。USB设备从接入HUB到正常工作之前,都属于设备枚举阶段。所谓设备枚举,就是让host控制器认识USB设备,并为其准备资源,建立好主机与设备间的数据传递机制。该阶段的工作,是USB通信协议规定的,所以属于ISO标准流程。

USB枚举实例

2440USB HOST进行初始化完毕(主要包括对符合OHCI规范的寄存器的初始化—总线复位、中断使能、清除中断标志、电源管理、内存指针寄存器的初始化,各种数据结构的初始化等),等待USB设备的插入,当2440检测到有设备插入,就要对设备进行枚举了。枚举就相当于主机和设备建立连接的过程(接头)HostDevice询问一些东西,Device将自身的设备类型、如何进行通信等报告给Host,这样Host就知道怎么对Device进行操作了。

枚举的过程实际上只用到了总线的“控制传输(Control Transfer)”这种传输方式通常用于配置/命令/状态等情形,其中的设置操作setup和状态操作status过程的数据包具有USB协议定义的数据结构,因此,控制传输只能通过消息管道进行。

一个完整的控制传输包括三个过程:

  1. 建立连接。   

  2. 数据过程(可选) 。       

  3. 状态过程。

建立连接的过程都是由Host发起,它开始于一个Setup令牌包,后面紧跟一个DATA0包。如果是控制输入传输,数据过程则为输入数据,若是控制输出传输,则数据过程是输出数据。

数据过程的可选型是指设置过程需要指定数据长度,如果指定为0,则没有数据过程。状态过程跟在数据过程之后,状态过程恰好和数据过程的数据传输方向相反,因为此阶段主要是用来确认之前两阶段的所有数据都已经正确传输了。

下面就结合实例来看看枚举的详细过程:

  1. 控制2440U盘发送第一个Setup包,内容是80 06 00 01 00 00 08 00,其中最后的00 08表示得到DEVICE_DCESCRIPTOR的前8个字节,因为这个包的主要目的是要获得USB Device中端点0的最大包的大小(8个字节),所以只需要8个字节就可以了。USB Device返回的设备标识符为12 01 10 01 00 00 00 40,下面我们需要把0x40记录下来,将其放到Endpoint Descriptor数据结构的DWORD0MPSbit16~bit32)块中去。

  2. 接下来2440发送第二个Setup包,内容是00 05 01 00 00 00 00 00,这一次的作用是为USB设备分配地址。如果USB Device接收并接受了此地址设置包,会返回一个长度为0的数据包。主机接收到长度为0的状态包之后就会返回一个ACKDeviceDevice再接收到这个ACK之后,就可以启用新地址了。这样Device就得到了一个唯一的设备地址,作为主机通信的唯一表示。

  3. 发送第三个Setup包,内容是80 06 00 02 00 00 09 00,这次是为了获取配置描述符集合的大小,此位位于读回数据的第三个字节。U盘返回的数据为09 02 20 00 01 01 00 80 32,即描述符集合总大小为0x20

  4. 发送第四个Setup包,内容是80 06 00 02 00 00 09 00,和上次不同的仅仅是,这次要读回来的数据是整个配置描述符区域。U盘返回来的数据是09 02 20 00 01 01 00 80 32 09 04 00 00 02 08 06 50 00 07 05 82 02 40 00 00 07 05 02 02 40 00 00。这时候我们就可以知道该设备是什么类型的设备,支持什么样的操作了。

上述这两个过程也有的程序就是直接读取0xff个字符大小,当然同样可以达到读回设备描述符集合的目的。

至此,我们已经得到了所需要的设备信息,之后就可以对设备进行配置了。

  1. 向设备发送第五个Setup包,数据为00 09 01 00 00 00 00 00USB Device返回一个长度为0的数据包,表明数据正确接收。至此,USB枚举过程就完成了。

多接口设备

多接口的器件驱动使得一个从USB设备可以作为多个设备同时工作,典型的就是GSMCDMA模块。3G4G模块常使用option.c驱动实现USB多接口功能,option.c就是GSM modems驱动。

此类设备提供的端口及功能,需要配置到设备的配置、接口等描述符中,并实现相应的功能(符合USB协议ISO标准)。

option.cThis driver is named "option" because the most common device it's used for is a PC-Card (with an internal OHCI-USB interface, behind which the GSM interface sits), made by Option Inc.

USB_SERIAL_OPTION: Say Y if you have a GSM or CDMA modem that's connected to USB.

 

参考:

  1. linux设备驱动开发详解 宋宝华

  2. [RK3399][Android7.1] USB模块中的控制器和PHY

  3. [RK3288][Android6.0] USB OTG模式及切换

  4. https://elinux.org/Tims_USB_Notes

  5. usb2.0 协议分析

  6. USB2.0概述及协议基础

  7. USB协议枚举过程详解

  8. LinuxUSB器件驱动多接口设备初探

posted @ 2019-03-15 23:29  yuxi_o  阅读(3219)  评论(0编辑  收藏  举报