ZYNQ学习之路——USB总线学习(二)

本文转载自:亦梦云烟的博客

Linux中USB驱动程序依然遵循标准的设备驱动模型——总线、设备、驱动。和I2C总线设备驱动一样,所有的USB驱动程序都必须创建的主要结构体是struct usb_driver,它们向USB核心代码描述了USB驱动程序,但这只是外壳,只实现了设备与总线的挂接,具体的USB设备是什么,如何实现,还需要编写相应的文件操作接口。本文详细介绍USB的驱动框架。

一. USB设备基础知识
1.1 USB设备的配置与传输类型
USB设备在接入系统时,以设备ID为0和主机通信,然后主机为其分配新的ID。在主机端,D+、D-都是下拉接地,设备端的D-接上拉时,表明此设备为全速设备(12Mbps),D+接上拉时为高速设备(480Mbps)。PC中的USB下拉电阻为15K,而设备中D+和D-则是1.5K上拉电阻。

USB是主从结构,并且是树形结构,在一个USB总线上的所有设备只有一个主机。所有的传输都是由主机发起的,即USB设备没有主动通知USB主机的能力。

USB有四种传输类型:

控制传输:最重要的也是最复杂的传输,控制传输由三个阶段构成(初始配置阶段、可选数据阶段、状态信息步骤),每一个阶段能够看成一个的传输,也就是说控制传输其实是由三个传输构成的,用来于USB设备初次加接到主机之后,主机通过控制传输来交换信息,设备地址和读取设备的描述符,使得主机识别设备,并安装相应的驱动程式,这是每一个USB研发者都要关心的问题。

批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输。例:U盘。

中断传输:由OUT事务和IN事务构成,用于键盘、鼠标等HID设备的数据传输中;

同步传输:由OUT事务和IN事务构成,有两个特别地方,第一,在同步传输的IN和OUT事务中是没有返回包阶段的;第二,在数据包阶段任何的数据包都为DATA0;

USB数据的传输都是在称之为“端点”的对象之间进行的,端点0用于控制传输,可双向传输,除了端点0其他每个端点都是单向传输数据。

OTG协议:OTG设备采用Mini-AB插座,相对于传统的USB数据线,Mini-AB接口多了一个数据线ID,ID线是否接入将Mini-AB接口区分为Mini-A和Mini-B接口两种类型。在OTG设备之间的数据连接过程中,通过OTG数据线Mini-A和Mini-B接口来确定OTG设备的主从:接入Mini-A接口的设备默认为A设备(主机接口);接Mini-B接口的设备默认为B设备(从设备)。

1.2 USB驱动程序框架
USB驱动程序的框架可描述如图1-1所示:

图1-1: USB驱动架构

一个USB设备驱动程序由一些配置、接口和端点组成。一个USB设备可以包含一个或多个配置,每个配置可包含一个或多个接口,在每个接口中可含有若干各端点。这些单元之间的关系如图1-2所示:

图1-2: USB描述符组织结构

二. USB驱动中的描述符
1.1 设备描述符
设备代表一个USB设备,由一个或多个配置组成。设备描述符用于说明设备的总体信息,并指明配置的个数,一个设备只能有一个设备描述符。

struct usb_device_descriptor {
__u8 bLength; //设备描述符字节长度
__u8 bDescriptorType; //设备描述符类型

__le16 bcdUSB; //USB版本号
__u8 bDeviceClass; //接口描述符的类
__u8 bDeviceSubClass; //接口描述符的子类
__u8 bDeviceProtocol; //接口描述符的协议
__u8 bMaxPacketSize0; //端点0可以支持的最大包的长度
__le16 idVendor; //Vendor ID
__le16 idProduct; //Product ID
__le16 bcdDevice; //设备版本
__u8 iManufacturer; //字符串描述符生产商索引
__u8 iProduct; //字符串描述符产品索引
__u8 iSerialNumber; //字符串描述符设备串行号索引
__u8 bNumConfigurations; //配置编号
} __attribute__ ((packed));

1.2 配置描述符
每个设备都有默认的配置描述符,支持至少一个接。配置描述符用于说明USB设备中各个配置的特性,如配置所包含的接口个数等。描述符结构定义如下:
struct usb_config_descriptor {
__u8 bLength; //配置描述符长度
__u8 bDescriptorType; //配置描述符类型

__le16 wTotalLength; //配置返回数据的总长度
__u8 bNumInterfaces; //配置支持接口数目
__u8 bConfigurationValue; //设置配置请求值
__u8 iConfiguration; //描述配置的字符串描述符的索引
__u8 bmAttributes; //配置特性,供电选择
__u8 bMaxPower; //从USB总线上获取的最大电流
} __attribute__ ((packed));

1.3 接口描述符
设备应该至少支持一个接口。接口时端点的集合,可以包含一个或多个可替换设置,用户能够在USB处于配置状态时改变当前接口包含的个数和特性。接口描述符结构定义如下:
struct usb_interface_descriptor {
__u8 bLength; //接口描述符长度
__u8 bDescriptorType; //接口描述符类型

__u8 bInterfaceNumber; //接口数量
__u8 bAlternateSetting; //备用的接口描述符编号
__u8 bNumEndpoints; //该接口使用的端点数量,不包括端点0
__u8 bInterfaceClass; //接口类
__u8 bInterfaceSubClass; //接口子类
__u8 bInterfaceProtocol; //接口协议
__u8 iInterface; //描述该接口的字符串索引
} __attribute__ ((packed));

1.4 端点描述符
端点是USB设备实际传输数据的对象,利用设备地址、端点号和传输方向可以指定一个端点,并与之进行通信。0号端点包含输入IN和输出OUT连个物理单元,且只支持控制传输。0号端点在设备上电后就能使用,而非0号端点必须要在配置以后才可以使用。

根据具体应用的需要,USB设备可以包含有除0号端口以外的其它端口,对于低速设备,其附加的端点数最多为2个,对于全速、高速设备,其附加的端点最多为15个。
struct usb_endpoint_descriptor {
__u8 bLength; //端点描述符长度
__u8 bDescriptorType; //端点描述符类型

__u8 bEndpointAddress; //端点地址:0~3位是端点编号,第7位是方向(0-OUT,1-IN)
__u8 bmAttributes; //端点属性:bit[1:0]的值为00-控制,01-同步,02-批量,03-中断
__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));

1.5 字符串描述符
在USB设备中通常包含字符串描述符,如制造商名称、设备序列号等。字符串描述符结构如下:
struct usb_string_descriptor {
__u8 bLength; //字符串描述符长度
__u8 bDescriptorType; //描述符类型

__le16 wData[1]; /* UTF-16LE encoded */
} __attribute__ ((packed));

三. 搭建驱动框架
3.1 注册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");

对比I2C的设备驱动,它们非常相似。skel_driver是struct usb_driver类型结构体,我们需要实现其成员函数:

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

初始化usb_driver至少需要实现五个字段:模块所有者、模块名字、probe函数、disconnect函数以及id_table。

id_table是struct usb_device_id类型,包括一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测(probe)函数将不会被调用。

首先看看usb_device_id结构体的定义:
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags;

/* Used for product specific matches; range is inclusive */
__u16 idVendor;
__u16 idProduct;
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;

/* Used for device class matches */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;

/* Used for interface class matches */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;

/* Used for vendor-specific interface matches */
__u8 bInterfaceNumber;

/* not matched against */
kernel_ulong_t driver_info
__attribute__((aligned(sizeof(kernel_ulong_t))));
};

USB系统通过vendor ID和Product ID的组合或者class和subclass的组合来识别设备,可以使用USB_DEVICE宏来定义这个ID:

#define USB_DEVICE(vend, prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
.idVendor = (vend), \
.idProduct = (prod)

例如:
static struct usb_device_id skel_table[]={
{USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID)},
{}
};
MODULE_DEVICE_TABLE(usb, skel_table);

MODULE_DEVICE_TABLE宏第一个参数是设备类型,第二个是设备表,这个设备表的最后一个元素是空的,用于标识结束。

当USB设备连接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生成厂商ID和产品的ID,或者是设备所属的class,subclass根protocol,以便确定应该调用哪个驱动处理该设备。

3.2 几个重要的数据结构
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 */
struct usb_anchor submitted; /* in case we need to retract our submissions */
struct urb *bulk_in_urb; /* the urb to read data with */
unsigned char *bulk_in_buffer; /* the buffer to receive data */
size_t bulk_in_size; /* the size of the receive buffer */
size_t bulk_in_filled; /* number of bytes in the buffer */
size_t bulk_in_copied; /* already copied to user space */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
int errors; /* the last request tanked */
bool ongoing_read; /* a read is going on */
spinlock_t err_lock; /* lock for errors */
struct kref kref;
struct mutex io_mutex; /* synchronize I/O with disconnect */
wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */
};

主要属性:

描述usb设备的结构体:udev

一个结构:interface

用于并发访问控制的信号量:limit_sem

用于接收数据的缓冲:bulk_in_buffer

用于接收数据的缓冲尺寸:bulk_in_size

批量数据端口地址:bulk_in_endpointAddr

批量输出端口地址:bulk_out_endpointAddr

usb_interface: usb接口数据结构:
struct usb_interface {
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting; /* the currently * active alternate setting */
unsigned num_altsetting; /* number of alternate settings */
int minor; /* minor number this interface is * bound to */
enum usb_interface_condition condition; /* state of binding */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;
unsigned resetting_device:1; /* true: bandwidth alloc after reset */

struct device dev; /* interface specific device info */
struct device *usb_dev;
atomic_t pm_usage_cnt; /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};

usb_hgost_endpoint: usb端点数据结构
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc;
struct usb_ss_ep_comp_descriptor ss_ep_comp;
struct list_head urb_list;
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */

unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;
int streams;
};

最新文章

最新文章