ZYNQ学习之路9.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;
};

欢迎关注亦梦云烟的微信公众号: 亦梦智能计算

 

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值