通用串行总线(USB)用于连接主机和外围设备。USB总线采用拓扑结构,USB主机和USB设备的连接构成了一颗树,树的结点为USB节点或USB集线器(HUB),USB集线器(HUB)用于扩展设备接口,一个集线器(HUB)可接多个USB设备或多个集线器。主机侧的USB节点为根节点,所有子节点都连接在根节点集线器(ROOT HUB)上,根节点由USB主机控制器(USB Host Controller)控制,设备侧的节点为子节点,由USB设备控制器(USB Device Controller)控制。在USB总线中,只能有一个USB主机控制器(根节点),可以有多个USB设备控制器(子节点)。USB主机负责协调主机和设备之间的通讯,USB设备不能主动向主机发送任何数据。
USB1.1协议包括OHCI(Open Host Controller Interface Specification)和UHCI(Universal Host Controller Interface Specification)规范。UHCI对硬件的要求较低,但驱动程序开发复杂,CPU处理负担较高,OHCI则使用硬件实现了较多的功能,对软件的要求降低,减轻了CPU的处理负担。USB2.0增加了EHCI(Enhanced Host Controller Interface),为USB 2.0主机高速数据传输控制器的软硬件设计提供了统一的接口标准,大大简化了USB 2.0的主机设计,提高了软件的可移植性。EHCI本身并不支持全速和低速设备,为了兼容USB 1.1,USB 2.0的主机控制器由EHCI和CHC(Companion Host Controller)两部分组成,CHC由OHCI和UHCI组成。xHCI是由Intel公司开发的可扩展主机控制器接口,主要面向USB3.0,同时也支持USB 2.0及以下设备。
USB设备包括设备、配置、接口和端点这四个层次,如下图所示。设备中包含若干个配置,配置中包含若干个接口,接口中包含若干个端点。
端点是USB最基本的通信形式,只能往一个方向传输数据,从主机到设备(输出设备)或者从设备到主机(称为输入端点),端点可以看作是单向的管道。每个端点都有唯一的地址和对应的属性,地址由设备地址和端点号给出,属性包括传输方向、总线访问频率、带宽、端点号和数据包的最大容量等。端点0通常为控制端点,用于设备的初始化。USB端点有四种不同的类型,分别具有不同的数据传递方式。
(1)控制
控制端点用于配置设备、获取设备信息、发送命令到设备、获取设备的状态。每个USB设备都有端点0的控制端点,当USB设备插入到USB主机拓扑网络中时,USB主机就通过端点0与USB设备通信,对USB设备进行配置,便于后续的数据传输。USB协议保证控制端点有足够的带宽。控制端点的数据传输方式为控制传输,控制传输可靠,时间有保证,但传输的数据量不大。如USB设备的识别过程就采用的时控制传输。
(2)中断
当USB主机请求USB设备传输数据时,中断端点以一个固定的速率传送少量的数据。中断端点的数据传输方式为中断传输,数据传输可靠,实时性高,这里的中断并不是USB设备产生中断,而是USB主机每隔一个固定的时间主动查询USB设备是否有数据要传输,以轮询的方式提高实时性。如USB鼠标采用的是中断传输。
(3)批量
批量端点用于传输大量数据,这些端点一次可以保存更多的数据。USB协议不保证这些数据传输可以在特定的时间内完成,但保证数据的准确性。如果总线上的空间不足以发送整个批量包,则将数据拆分为多个包传输。批量传输数据可靠,但实时性较低。如USB硬盘、打印机等设备就采用的是批量传输方式。
(4)等时
等时端点也可以传输大量数据,但数据的可靠性无法保证。采用等时传输的USB设备更加注重保持一个恒定的数据传输速度,对数据的可靠性要求不高。如USB摄像头就使用的是等时传输方式。
Linux内核使用struct usb_endpoint_descriptor
结构体描述端点。
[include/uapi/linux/usb/ch9.h]
struct usb_endpoint_descriptor {
__u8 bLength; // 端点描述符长度
__u8 bDescriptorType; // 端点描述符类型
// 端点地址,低四位是端点号,最高位表示数据传输方向,0为输出,1为输入
__u8 bEndpointAddress;
__u8 bmAttributes; // 端点类型,为0表示控制,1表示等时,2表示批量,3表示中断
__le16 wMaxPacketSize; // 本端点接收或发送数据包的最大字节数
// 中断传输轮询周期,批量传输忽略,等时传输为1,中断传输范围为1-255
__u8 bInterval;
__u8 bRefresh;
__u8 bSynchAddress;
// __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,
// 按照实际占用字节数进行对齐,是GCC特有的语法
} __attribute__ ((packed));
Linux内核使用struct usb_host_endpoint
描述主机侧的端点。
[include/linux/usb.h]
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; // 端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp;
struct list_head urb_list; // USB请求块链表节点,由USB核心层管理
void *hcpriv; // 主机控制器使用,通常用于硬件DMA队列头
struct ep_device *ep_dev; /* For sysfs info */
unsigned char *extra; /* Extra descriptors */
int extralen; // extra的字节数
int enabled; // URBs may be submitted to this endpoint
int streams; // number of USB-3 streams allocated on the endpoint
};
USB接口代表了一个USB设备的基本功能,USB接口由多个USB端口组成。USB接口只处理一种USB逻辑连接,例如鼠标、键盘或者音频流。一些USB设备具有多个接口,例如USB扬声器可以包括两个接口:USB按键和USB音频流。
Linux内核使用struct usb_interface_descriptor
描述端点。
[include/uapi/linux/usb/ch9.h]
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));
Linux内核使用struct usb_host_interface
描述主机侧的接口。
[include/linux/usb.h]
struct usb_host_interface {
struct usb_interface_descriptor desc; // 接口描述符
int extralen; // extra的字节数
unsigned char *extra; /* Extra descriptors */
struct usb_host_endpoint *endpoint; // 主机侧的端点
char *string; /* 接口字符串 */
};
USB配置由一个或多个USB接口组成,每个配置具有一个或多个基本功能。Linux使用struct usb_config_descriptor
描述配置。
[include/uapi/linux/usb/ch9.h]
struct usb_config_descriptor {
__u8 bLength; // 描述符长度
__u8 bDescriptorType; // 描述符类型编号
__le16 wTotalLength; // 配置返回的数据长度
__u8 bNumInterfaces; // 配置所支持的接口数量
__u8 bConfigurationValue; // Set_Configuration命令所需的参数值
__u8 iConfiguration; // 描述该配置字符串的索引值
__u8 bmAttributes; // 供电模式选择
__u8 bMaxPower; // 设备从总线提取的最大电流
} __attribute__ ((packed));
Linux内核使用struct usb_host_config
描述主机侧的配置。
[include/linux/usb.h]
#define USB_MAXINTERFACES 32
#define USB_MAXIADS (USB_MAXINTERFACES/2)
struct usb_host_config {
struct usb_config_descriptor desc; // 配置描述符
char *string; // 配置字符串
// 配置的接口中关联的描述符
struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];
/* 配置关联的接口 */
struct usb_interface *interface[USB_MAXINTERFACES];
// 接口的可用信息
struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
unsigned char *extra; /* Extra descriptors */
int extralen; // extra字节数
};
USB设备有一个或多个配置组成。USB设备可以在这些配置之间切换,以改变设备的状态。Linux使用struct usb_device_descriptor
描述设备。
[include/uapi/linux/usb/ch9.h]
struct usb_device_descriptor {
__u8 bLength; // 描述符长度
__u8 bDescriptorType; // 描述符类型编号
__le16 bcdUSB; // USB版本号
__u8 bDeviceClass; // USB分配的设备类code
__u8 bDeviceSubClass; // USB分配的子类code
__u8 bDeviceProtocol; // USB分配的协议code
__u8 bMaxPacketSize0; // 端点0最大包大小
__le16 idVendor; // 厂商编号
__le16 idProduct; // 产品编号
__le16 bcdDevice; // 设备出场编号
__u8 iManufacturer; // 描述厂商字符串的索引
__u8 iProduct; // 描述产品字符串的索引
__u8 iSerialNumber; // 描述设备序列号字符串的索引
__u8 bNumConfigurations; // 可能的配置数量
} __attribute__ ((packed));
在Linux系统中,USB驱动可以从两个角度观察,一个角度是主机侧,另一个角度是设备侧。主机测,处于USB驱动底层的是USB主机控制器硬件,在其上运行的是USB主机控制器驱动,再往上是USB核心层,最上层是USB设备驱动层(插入主机上的U盘、鼠标、键盘等设备驱动)。因此在主机侧,需要实现USB主机控制器驱动和USB设备驱动,前者用于控制USB主机控制器和插入USB总线的USB设备之间的通信,后者描述主机应该怎么和插入的USB设备通信。USB核心完成驱动管理和协议处理的主要工作,向上为主机侧USB设备驱动提供统一的编程接口,向下为USB主机控制器驱动提供统一的编程接口。设备侧驱动程序分为UDC驱动程序、Gadget Function API和Gadget Function驱动程序。UDC驱动程序直接访问硬件,控制USB设备控制器与USB主机控制器通信,向上提供与硬件交互的接口。Gadget Function API封装了UDC驱动程序向上提供的接口。Gadget Function驱动程序实现具体的USB设备功能。Linux内核支持的USB设备类包括USB打印设备、通信类设备、HID设备类、存储设备类、语音设备类等。驱动工程师一般需要实现USB设备驱动,USB主机控制器驱动通常由芯片厂家实现。
Linux USB核心层使用struct usb_driver
结构体表示一个USB设备驱动,编写USB设备驱动时,主要实现probe
和disconnect
函数,分别用于初始化和释放软硬件资源,设备和驱动匹配成功后,probe
函数被调用,设备断开时disconnect
函数被调用。使用宏usb_register
注册USB设备驱动程序,使用函数usb_deregister
注销USB设备驱动程序,还可以使用宏module_usb_driver
注册USB驱动程序,其同时完成注册、注销、module_init
及module_exit
功能。在注册USB驱动程序时,还需要定义MODULE_DEVICE_TABLE
宏,将设备驱动匹配表导出到用户空间,常用于设备热插拔时进行设备识别。USB协议支持设备的热拔插。
这里需要注意的时USB只是一种总线,而连接到总线上的USB设备可以是字符设备、tty设备、块设备、输入设备等。usb_driver
结构体处理了设备和USB总线相关的工作,至于设备的功能,需要根据具体的设备类型来编写具体的设备驱动。因此USB设备驱动包含了其作为总线上挂接设备的驱动和所属设备类型的驱动。这和platform_driver
、i2c_driver
等类似,usb_driver
起到桥梁的作用,即在xxx_driver
结构体的probe
函数中注册具体的设备,如注册字符、tty等设备,在disconnect
函数中注销字符、tty设备。
[include/linux/usb.h]
struct usb_driver {
const char *name; // 驱动名称
// probe函数
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; // 描述了USB驱动支持的USB设备列表
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为struct usb_driver结构体指针
#define usb_register(driver) usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
#define module_usb_driver(__usb_driver) module_driver(__usb_driver,
usb_register, usb_deregister)
// 注销USB设备驱动程序
void usb_deregister(struct usb_driver *driver)
[include/linux/device.h]
// 简介的注册宏定义,同时完成初始化和卸载功能
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
id_table
描述了USB驱动支持的USB设备列表,其指向了struct usb_device_id
类型的数组。struct usb_device_id
包含了USB设备的制造商ID、产品ID、产品版本、设备类、接口类等信息及其要匹配标志成员match_flags(表明要与那些成员匹配)。match_flags
的取值从下面的宏定义中选取。
[include/linux/mod_devicetable.h]
#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 // 按供应商ID匹配
#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 // 按产品ID匹配
#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010 // 按设备类型匹配
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020 // 按设备子类型匹配
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040 // 按设备协议匹配
#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080 // 按接口类型匹配
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100 // 按接口子类型匹配
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200 // 按接口协议匹配
#define USB_DEVICE_ID_MATCH_INT_NUMBER 0x0400
struct usb_device_id {
__u16 match_flags; // 匹配方式
__u16 idVendor; // 供应商ID,由USB协会分配
__u16 idProduct; // 产品ID,供应商自己分配
__u16 bcdDevice_lo; // 产品版本号的最小值
__u16 bcdDevice_hi; // 产品版本号的最大值
__u8 bDeviceClass; // 设备类型
__u8 bDeviceSubClass; // 设备子类型
__u8 bDeviceProtocol; // 设备协议
__u8 bInterfaceClass; // 接口类
__u8 bInterfaceSubClass; // 接口子类
__u8 bInterfaceProtocol; // 接口协议
__u8 bInterfaceNumber;
/* not matched against */
kernel_ulong_t driver_info
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
也可使用下面的宏定义来初始化usb_device_id
。USB_DEVICE
宏根据制造商ID和产品ID生成一个usb_device_id
结构体实列,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID相匹配的设备。USB_DEVICE_VER
宏根据制造商ID、产品ID、产品版本号范围(在最大值与最小值之间)生成一个usb_device_id
结构体的实列,在数组中增加该元素将意味着该驱动可支持与制造商ID、产品ID、lo~hi产品版本号范围内的产品匹配。USB_DEVICE_INFO
宏用于创建一个匹配指定设备类型的usb_device_id
结构体实列。USB_INTERFACE_INFO
宏创建一个匹配指定接口类型的usb_device_id
结构体实列。创建完usb_device_id
结构体实列后,还需要使用MODULE_DEVICE_TABLE
宏将usb_device_id
导出到用户空间。
[include/linux/usb.h]
// 匹配方式的组合,用于设置struct usb_device_id的match_flags标志
#define USB_DEVICE_ID_MATCH_DEVICE \
(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE \
(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION \
(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO \
(USB_DEVICE_ID_MATCH_DEV_CLASS | \
USB_DEVICE_ID_MATCH_DEV_SUBCLASS | \
USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO \
(USB_DEVICE_ID_MATCH_INT_CLASS | \
USB_DEVICE_ID_MATCH_INT_SUBCLASS | \
USB_DEVICE_ID_MATCH_INT_PROTOCOL)
#define USB_DEVICE(vend, prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
.idVendor = (vend), \ // 制造商ID
.idProduct = (prod) // 产品ID
#define USB_DEVICE_VER(vend, prod, lo, hi) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
.idVendor = (vend), \ // 制造商ID
.idProduct = (prod), \ // 产品ID
.bcdDevice_lo = (lo), \ // 产品版本号范围的最小值
.bcdDevice_hi = (hi) // 产品版本号范围的最大值
#define USB_DEVICE_INFO(cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, \
.bDeviceClass = (cl), \ // 设备类型
.bDeviceSubClass = (sc), \ // 设备子类
.bDeviceProtocol = (pr) // 设备协议
#define USB_INTERFACE_INFO(cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
.bInterfaceClass = (cl), \ // 接口类型
.bInterfaceSubClass = (sc), \ // 接口子类
.bInterfaceProtocol = (pr) // 接口协议
......
下面是定义USB设备属性的例子,当USB核心检测到某个插入设备的属性和某个驱动程序的属性一致时,这个驱动程序的probe()函数就被调用,当拔掉设备或者卸载驱动后,USB核心层就执行disconnect()函数,将设备和驱动断开。
// 先定义一个usb_device_id数组,使用USB_DEVICE初始化内部的元素,然后使用
// MODULE_DEVICE_TABLE将usb_device_id导出到用户空间,便于热插拔时进行设备识别
static struct usb_device_id id_table[] = {
{USB_DEVICE(VEND_TD, PRODUCT_ID)},
{ }, // 最后一个元素必须是空
}
MODULE_DEVICE_TABLE(usb, id_table);
USB核心层使用struct usb_device
结构来表示一个USB设备。当USB控制器检测到设备连接时,就会分配一个struct usb_device
结构体,然后注册到总线设备列表中,然后匹配对应的驱动。
[include/linux/usb.h]
struct usb_device {
int devnum; // 该设备在总线上的序号
char devpath[16]; // USB拓扑路径
u32 route;
enum usb_device_state state; // 状态
enum usb_device_speed speed; // 速度
struct usb_tt *tt; // 事务转换(如高速接口兼容低速设备)
int ttport;;
struct usb_device *parent;
struct usb_bus *bus;
struct usb_host_endpoint ep0; // 端点0
struct device dev;
struct usb_device_descriptor descriptor; // 设备描述符
struct usb_host_bos *bos;
struct usb_host_config *config;
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16]; // 输入端口
struct usb_host_endpoint *ep_out[16]; // 输出端口
char **rawdescriptors; // GET_DESCRIPTOR命令返回的描述符原始字符串
unsigned short bus_mA; // 总线电流
u8 portnum; // HUB端口号
u8 level; // USB设备树层级
......
};
在Linux内核中,使用struct usb_hcd
描述USB主机控制区驱动,包含了主机控制器的‘家务’信息、硬件资源、状态描述和用于控制主机控制器的hc_driver
等。struct usb_hcd
的hc_driver
成员非常重要,包含了操作主机控制器的所有方法。
[include/linux/usb/hcd.h]
struct usb_hcd {
struct usb_bus self; /* hcd is-a bus */
struct kref kref; /* reference counter */
const char *product_desc; /* 厂商字符串 */
int speed; /* 主机控制器根HUB的速度
char irq_descr[24]; /* driver + bus # */
struct timer_list rh_timer; /* drives root-hub polling */
struct urb *status_urb; /* the current status urb */
// USB主机控制器操作函数集合
const struct hc_driver *driver; /* hw-specific hooks */
// OTG和某些USB控制器需要和PHY交互
struct usb_phy *usb_phy;
struct phy *phy;
unsigned long flags;
unsigned int irq; /* irq allocated */
void __iomem *regs; /* device memory/io */
resource_size_t rsrc_start; /* memory/io resource start */
resource_size_t rsrc_len; /* memory/io resource length */
unsigned power_budget; /* in mA, 0 = no limit */
struct giveback_urb_bh high_prio_bh;
struct giveback_urb_bh low_prio_bh;
struct mutex *bandwidth_mutex;
struct usb_hcd *shared_hcd;
struct usb_hcd *primary_hcd;
#define HCD_BUFFER_POOLS 4
struct dma_pool *pool[HCD_BUFFER_POOLS];
int state;
unsigned long hcd_priv[0] // 私有数据
__attribute__ ((aligned(sizeof(s64))));
};
urb_enqueue
函数非常关键,上层通过usb_submit_urb
提交一个USB请求后,该函数内部通过调用usb_hcd_submit_urb
函数,最终调用到urb_enqueue
。
[include/linux/usb/hcd.h]
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);
int (*pci_suspend)(struct usb_hcd *hcd, bool do_wakeup);
int (*pci_resume)(struct usb_hcd *hcd, bool hibernated);
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);
int (*map_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb,gfp_t mem_flags);
void(*unmap_urb_for_dma)(struct usb_hcd *hcd, struct urb *urb);
void(*endpoint_disable)(struct usb_hcd *hcd,struct usb_host_endpoint *ep);
void(*endpoint_reset)(struct usb_hcd *hcd,struct usb_host_endpoint *ep);
int (*hub_status_data) (struct usb_hcd *hcd, char *buf);
int (*hub_control) (struct usb_hcd *hcd,u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength);
int (*bus_suspend)(struct usb_hcd *);
int (*bus_resume)(struct usb_hcd *);
......
};
在Linux内核中,使用usb_create_hcd
函数创建主机控制器,使用usb_add_hcd
和usb_remove_hcd
函数注册、注销主机控制器。
[include/linux/usb/hcd.h]
// 创建主机控制器struct usb_hcd结构体并初始化。driver为struct hc_driver结构体指针,dev为主机
// 控制器的设备结构体,保存在hcd->self.controller中。bus_name为总线名称,保存在hcd->self.bus_name。
struct usb_hcd *usb_create_hcd(const struct hc_driver *driver,
struct device *dev, const char *bus_name)
// 初始化并注册主机控制器struct usb_hcd结构体。hcd为struct usb_hcd结构体指针。irqnum为中断号。
// irqflags为中断标志
int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
// 注销主机控制器struct usb_hcd结构体。hcd为struct usb_hcd结构体指针。
void usb_remove_hcd(struct usb_hcd *hcd)
USB请求块(USB Request Block,URB)是USB设备驱动中用来描述与USB设备通信的基本载体和核心数据结构,使用urb
结构体描述,类似与网络设备驱动中的sk_buff
结构体。
struct urb
结构体由两部分组成,第一部分是私有的,只能被usb核心层或主机控制器使用,第二部分是公有的,可被usb设备驱动程序使用。
[include/linux/usb.h]
struct urb {
// 以下私有:usb核心层或主机控制器用
struct kref kref; /* URB引用计数 */
void *hcpriv; /* 主机控制器私有数据 */
atomic_t use_count; /* 并发提交个数 */
atomic_t reject; /* 提交失败的数量 */
int unlinked; /* unlink error code */
// 以下公用:可被驱动使用的成员
struct list_head urb_list; /* urb组成的链表 */
struct list_head anchor_list; /* the URB may be anchored */
struct usb_anchor *anchor;
struct usb_device *dev; /* 关联的设备 */
struct usb_host_endpoint *ep; /* (internal) pointer to endpoint */
pipe用来查找端点队列,队列的特性定义在端点描述符中。pipe的各位定义如下:
bit7-0:bit7数据传输方向,0 = Host-to-Device [Out],1 = Device-to-Host [In]
bit8-14:USB设备地址(编号),bit positions known to uhci-hcd
bit15-18:端点地址(编号),... bit positions known to uhci-hcd,可找到对应的端点
bit30-31:pipe的类型,00 = isochronous, 01 = interrupt, 10 = control, 11 = bulk)
unsigned int pipe; /* (in) pipe information */
unsigned int stream_id; /* (in) stream ID */
int status; /* (return) non-ISO status */
unsigned int transfer_flags; /* (in) URB_SHORT_NOT_OK | ...*/
void *transfer_buffer; /* (in) associated data buffer */
dma_addr_t transfer_dma; /* (in) dma addr for transfer_buffer */
struct scatterlist *sg; /* (in) scatter gather buffer list */
int num_mapped_sgs; /* (internal) mapped sg entries */
int num_sgs; /* (in) number of entries in the sg list */
u32 transfer_buffer_length; /* (in) data buffer length */
u32 actual_length; /* (return) actual transfer length */
unsigned char *setup_packet; /* (in) setup packet (control only) */
dma_addr_t setup_dma; /* (in) dma addr for setup_packet */
int start_frame; /* (modify) start frame (ISO) */
int number_of_packets; /* (in) number of ISO packets */
int interval; /* (modify) transfer interval
int error_count; /* (return) number of ISO errors */
void *context; /* (in) context for completion */
usb_complete_t complete; /* (in) completion routine */
struct usb_iso_packet_descriptor iso_frame_desc[0]; // 等时数据包
};
使用usb_sndctrlpipe
和usb_rcvctrlpipe
宏获取控制传输的发送和接收pipe,使用usb_sndisocpipe
和usb_rcvisocpipe
宏获取等时传输的发送和接收pipe,使用usb_sndbulkpipe
和usb_rcvbulkpipe
宏获取批量传输的发送和接收pipe,使用usb_sndintpipe
和usb_rcvintpipe
宏获取中断传输的发送和接收pipe。
#define USB_DIR_OUT 0 /* 数据传输方向 to device */
#define USB_DIR_IN 0x80 /* 数据传输方向 to host */
#define PIPE_ISOCHRONOUS 0 // pipe类型
#define PIPE_INTERRUPT 1
#define PIPE_CONTROL 2
#define PIPE_BULK 3
static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}
#define usb_sndctrlpipe(dev, endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint))
#define usb_rcvctrlpipe(dev, endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndisocpipe(dev, endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev, endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev, endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint))
#define usb_rcvbulkpipe(dev, endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndintpipe(dev, endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))
#define usb_rcvintpipe(dev, endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
(1)分配和释放URB
使用usb_alloc_urb
分配一个urb
结构体,使用usb_free_urb
释放一个urb
结构体。参数iso_packets
表示分配等时数据包的数量,若为0则不分配,mem_flags
为分配内存的标志,调用kmalloc
时使用。urb
不宜静态创建,因为这可能破环USB核心层使用的URB引用计数。
[include/linux/usb.h]
// 成功返回urb的指针,否则返回NULL
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
void usb_free_urb(struct urb *urb)
(2)初始化URB
urb
结构体在使用之前要和USB设备端点对应起来,需要根据数据传递方式进行初始化。
中断传输方式(端点)使用usb_fill_int_urb
初始化urb
结构体,参数pipe
比较重要,根据pipe
可以知道对应的USB设备、端点、数据传输方向及数据传输类型。对应中断传输,可使用usb_sndintpipe
和usb_rcvintpipe
宏来获取pipe。
[include/linux/usb.h]
// urb-urb结构体的指针
// dev-urb关联的usb设备结构体指针
// pipe-端点的管道,
// transfer_buffer-传输缓冲区指针,和urb一样,不能是静态缓冲区,不能使用kmalloc分配,
// 必须使用专门的函数usb_alloc_coherent分配
// buffer_length-传输缓冲区transfer_buffer的长度
// complete_fn-传输完成的回调函数
// context-complete_fn函数的上下文
// interval-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)
[include/linux/usb.h]
// 批量传输方式(端点)使用usb_fill_bulk_urb初始化urb结构体
// pipe-可使用usb_sndbulkpipe和usb_rcvbulkpipe宏来获取。批量传输没有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)
[include/linux/usb.h]
// 控制传输方式(端点)使用usb_fill_control_urb初始化urb结构体
// pipe-可使用usb_sndctrlpipe和usb_rcvctrlpipe宏来获取
// setup_packet-控制传输和批量传输相比,多了一个setup_packet参数,setup_packet指向设置数据包的缓冲区。
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)
等时传输方式没有现成的初始化函数用来初始化urb
结构体。需要手动初始化并设置URB_ISO_ASSP
标志。
(3)异步提交URB
urb
结构体分配和初始化完成后,就可以使用usb_submit_urb
函数将请求异步的提交到USB核心层了,提交成功返回0,否则返回小于0的错误代码,请求完成后回调函数complete
将被调用。urb
指向要提交的urb
结构体,mem_flags
是kmalloc
函数分配内存时的标志。在将URB提交到USB核心层后,直到完成函数被调用之前,不要操作urb
结构体中的任何成员。usb_submit_urb
在原子上下文和进程上下文中都可以调用,mem_flags
参数需要根据调用环境进行设置。如下所示:
GFP_ATOMIC:在原子上下文环境中使用,如中断处理函数、中断下半本部分、tasklet、定时器处理函数、USR完成函数complete
、持有自旋锁或读写锁时将current->stat
设置为非TASK_RUNNING
。
GFP_NOIO:在存储设备的块I/O和错误处理路径中使用。
GFP_KERNEL:没有使用GFP_ATOMIC
和GFP_NOIO
的理由,就使用此标志。
GFP_NOFS:没有使用过。
[include/linux/usb.h]
// 异步提交请求,complete回调函数被调用表明请求已完成
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
(4)同步提交URB(信息)
下面三个接口可以同步提交URB(信息),常用于提交一些简单的信息,所以省略了创建和初始化urb
结构体的步骤,在请求处理完后或者超时时间到才返回,提交完成返回0,否则返回小于0的错误码。这三个接口都不能用在硬件中断、软件中断及持有自旋锁的上下文环境(!in_interrupt ()
)中。usb_control_msg
用于提交控制传输的请求,usb_interrupt_msg
用于传输中断类型的请求,usb_bulk_msg
用于传输批量类型的请求。
[include/linux/usb.h]
// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,request:USB信息请求值,
// requesttype:USB信息请求类型,value:USB消息值,index:USB消息索引值,data:发送或接收缓冲区指针,
// size:发送或接收缓冲区的大小,timeout:等待消息完成的时间,单位为毫秒,0为一直等待
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)
// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,data:发送或接收缓冲区指针,
// len:缓冲区中发送的字节数,actual_length:保存实际传输的字节数,
// timeout:等待消息完成的时间,单位为毫秒,0为一直等待
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout)
// dev:USB设备结构体指针,pipe:pipe值,用于找到发送的USB设备端点,data:发送或接收缓冲区指针,
// len:缓冲区中发送的字节数,actual_length:保存实际传输的字节数,
// timeout:等待消息完成的时间,单位为毫秒,0为一直等待
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout)
(5)取消URB
usb_kill_urb
和usb_kill_urb
可以取消一个urb
,前者是异步取消,不等待取消成功就返回,若取消的urb正在执行,则执行完成后取消,后者是同步的,直到取消成功才返回。
[include/linux/usb.h]
void usb_unlink_urb(struct urb *urb)
void usb_kill_urb(struct urb *urb)
(6)URB传输数据缓冲区的分配和释放
初始化USB请求块URB的参数transfer_buffer
不能静态定义和使用kmalloc
等,必须使用usb_alloc_coherent
函数分配。因为USB数据内部传输用到了DMA,为了保证数据一致性,则必须要使用和DMA相关的内存分配的函数,usb_alloc_coherent
函数内部调用了dma_alloc_coherent
。此函数分配的内存是uncache的。使用usb_free_coherent
释放分配的内存。
[include/linux/usb.h]
// dev:USB设备结构体
// size:缓冲区长度
// mem_flags:内存分配标志
// dma:分配的内存对应的物理地址
void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags,
dma_addr_t *dma)
void usb_free_coherent(struct usb_device *dev, size_t size, void *addr,
dma_addr_t dma)
首先取消内核中的usb mouse驱动支持,路径如下,取消USB HID transport layer的驱动支持。
Device Drivers >
HID support >
USB HID support >
<> USB HID transport layer
使用insmod usb_mouse.ko
,插入USB鼠标,系统会打印鼠标信息,同时在dev
目录下生成/dev/input/event4
设备节点。使用hexdump /dev/input/event4
命令,此时移动鼠标,会看到输出的数据。
/*===========================usb_mouse.h================================*/
#ifndef USB_MOUSE_H
#define USB_MOUSE_H
#include
#include
#include
#include
struct usb_mouse
{
struct usb_host_interface *intf;
struct usb_device* dev;
struct usb_endpoint_descriptor* endpoint;
struct urb* irq_urb;
struct input_dev* input;
int maxlen;
unsigned int pipe;
char* data; // 虚拟地址
dma_addr_t data_dma; // 物理地址
char name[128];
char phys[64];
};
#endif // USB_MOUSE_H
/*===========================usb_mouse.c================================*/
#include "usb_mouse.h"
#include
#include
#include
#include
#include
#include
// 参考usbmouse.c文件,路径:usbmouse.c drivers/hid/usbhid/usbmouse.c
struct usb_mouse* usb_mouse_dev = NULL;
static int usb_mouse_open(struct input_dev *dev)
{
int ret = 0;
struct usb_mouse* usb_mouse = input_get_drvdata(dev);
usb_mouse->irq_urb->dev = usb_mouse->dev;
if ((ret = usb_submit_urb(usb_mouse->irq_urb, GFP_KERNEL)) < 0) {
dev_err(&usb_mouse->dev->dev, "usb_submit_urb failed, errno %d\n", ret);
return -EIO;
}
return 0;
}
static void usb_mouse_close(struct input_dev *dev)
{
struct usb_mouse* usb_mouse = input_get_drvdata(dev);
usb_kill_urb(usb_mouse->irq_urb);
}
static void usb_mouse_irq(struct urb *urb)
{
struct usb_mouse* usb_mouse = urb->context;
char* data = usb_mouse->data;
struct input_dev* dev = usb_mouse->input;
int status, i;
for (i = 0; i < usb_mouse->maxlen; i++) {
printk("%02x ", data[i]);
}
printk("\n");
// 有标准的完成函数,不需要判断usb的状态
/*
switch (urb->status) { // 获取urb提交结果
case 0: // success
break;
case -ECONNRESET: // unlink
case -ENOENT:
case -ESHUTDOWN:
return;
// -EPIPE: should clear the halt
default: // error
goto resubmit;
}
*/
input_report_key(dev, BTN_LEFT, data[0] & 0x01);
input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
input_report_rel(dev, REL_X, data[1]);
input_report_rel(dev, REL_Y, data[2]);
input_report_rel(dev, REL_WHEEL, data[3]);
input_sync(dev);
//resubmit:
status = usb_submit_urb(urb, GFP_ATOMIC);
if (status)
dev_err(&usb_mouse->dev->dev,
"can't resubmit intr, %s-%s/input0, status %d\n",
usb_mouse->dev->bus->bus_name,
usb_mouse->dev->devpath, status);
}
static int usb_mouse_probe(struct usb_interface* intf, const struct usb_device_id* id)
{
int error = -ENOMEM;
struct usb_device* usbdev;
usbdev = interface_to_usbdev(intf);
usb_mouse_dev = kmalloc(sizeof(struct usb_mouse), GFP_KERNEL);
if (NULL == usb_mouse_dev) {
dev_err(&usbdev->dev, "usb_mouse_dev kmalloc failed\n");
return error;
}
memset(usb_mouse_dev, 0, sizeof(struct usb_mouse));
usb_mouse_dev->dev = usbdev;
dev_info(&usbdev->dev, "bcdUSB = 0x%x\n", usb_mouse_dev->dev->descriptor.bcdUSB); // USB版本号
dev_info(&usbdev->dev, "idVendor = 0x%x\n", usb_mouse_dev->dev->descriptor.idVendor); // 厂商编号
dev_info(&usbdev->dev, "idProduct = 0x%x\n", usb_mouse_dev->dev->descriptor.idProduct); // 设备出场编号
usb_mouse_dev->intf = intf->cur_altsetting;
// 检查此端点数量是否为1(除端点0)
if (usb_mouse_dev->intf->desc.bNumEndpoints != 1) {
error = -ENODEV;
dev_err(&usbdev->dev, "bNumEndpoints error, bNumEndpoints %u\n",
usb_mouse_dev->intf->desc.bNumEndpoints);
goto free_usb_mouse;
}
// 获取主机侧与鼠标通信的端点
usb_mouse_dev->endpoint = &usb_mouse_dev->intf->endpoint[0].desc;
// 判断是否是中断输入端点
if (!usb_endpoint_is_int_in(usb_mouse_dev->endpoint)) {
error = -ENODEV;
dev_err(&usbdev->dev, "endpoint isn't int in\n");
goto free_usb_mouse;
}
// 获取pipe,根据pipe可以知道对应的USB设备、端点、数据传输方向及数据传输类型
usb_mouse_dev->pipe = usb_rcvintpipe(usb_mouse_dev->dev,
usb_mouse_dev->endpoint->bEndpointAddress);
// 获取本端点接收或发送数据包的最大字节数
usb_mouse_dev->maxlen = usb_maxpacket(usb_mouse_dev->dev, usb_mouse_dev->pipe,
usb_pipeout(usb_mouse_dev->pipe));
dev_info(&usbdev->dev, "wMaxPacketSize 0x%x\n", usb_mouse_dev->maxlen);
// 分配usb数据传输的缓冲区,不能使用kmalloc分配
usb_mouse_dev->data = usb_alloc_coherent(usb_mouse_dev->dev,
usb_mouse_dev->maxlen, GFP_ATOMIC, &usb_mouse_dev->data_dma);
if (NULL == usb_mouse_dev->data) {
dev_err(&usbdev->dev, "usb mouse alloc usb coherent buffer error\n");
goto free_usb_mouse;
}
// 分配一个usb请求块
usb_mouse_dev->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
if (NULL == usb_mouse_dev->irq_urb) {
dev_err(&usbdev->dev, "usb mouse alloc usb urb error\n");
goto free_usb_coherent;
}
// 填充usb请求块
usb_fill_int_urb(usb_mouse_dev->irq_urb, usb_mouse_dev->dev,
usb_mouse_dev->pipe, usb_mouse_dev->data, usb_mouse_dev->maxlen,
// usb_mouse_dev保存到struct urb的context变量中
usb_mouse_irq, usb_mouse_dev, usb_mouse_dev->endpoint->bInterval);
dev_info(&usbdev->dev, "bInterval 0x%x\n", usb_mouse_dev->endpoint->bInterval);
usb_mouse_dev->irq_urb->transfer_dma = usb_mouse_dev->data_dma;
usb_mouse_dev->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_set_intfdata(intf, usb_mouse_dev);
usb_mouse_dev->input = input_allocate_device();
if (NULL == usb_mouse_dev->input) {
dev_err(&usbdev->dev, "usb mouse allocate input device error\n");
goto free_usb_urb;
}
if (usb_mouse_dev->dev->manufacturer)
strlcpy(usb_mouse_dev->name, usb_mouse_dev->dev->manufacturer,
sizeof(usb_mouse_dev->name));
if (usb_mouse_dev->dev->product) {
if (usb_mouse_dev->dev->manufacturer)
strlcat(usb_mouse_dev->name, " ", sizeof(usb_mouse_dev->name));
strlcat(usb_mouse_dev->name, usb_mouse_dev->dev->product,
sizeof(usb_mouse_dev->name));
}
if (!strlen(usb_mouse_dev->name))
snprintf(usb_mouse_dev->name, sizeof(usb_mouse_dev->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(usb_mouse_dev->dev->descriptor.idVendor),
le16_to_cpu(usb_mouse_dev->dev->descriptor.idProduct));
usb_make_path(usb_mouse_dev->dev, usb_mouse_dev->phys, sizeof(usb_mouse_dev->phys));
strlcat(usb_mouse_dev->phys, "/input0", sizeof(usb_mouse_dev->phys));
usb_mouse_dev->input->name = usb_mouse_dev->name;
usb_mouse_dev->input->phys = usb_mouse_dev->phys;
dev_info(&usbdev->dev, "name = %s\n", usb_mouse_dev->name);
dev_info(&usbdev->dev, "phys = %s\n", usb_mouse_dev->phys);
usb_to_input_id(usb_mouse_dev->dev, &usb_mouse_dev->input->id);
usb_mouse_dev->input->dev.parent = &intf->dev;
usb_mouse_dev->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
usb_mouse_dev->input->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
usb_mouse_dev->input->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
usb_mouse_dev->input->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA);
usb_mouse_dev->input->relbit[0] |= BIT_MASK(REL_WHEEL);
input_set_drvdata(usb_mouse_dev->input, usb_mouse_dev);
usb_mouse_dev->input->open = usb_mouse_open;
usb_mouse_dev->input->close = usb_mouse_close;
error = input_register_device(usb_mouse_dev->input);
if (error < 0) {
dev_err(&usbdev->dev, "usb mouse register input device error, errno %d\n", error);
goto free_input_device;
}
#if 0 // 测试用
// 提交urb
if (error = usb_submit_urb(usb_mouse_dev->irq_urb, GFP_ATOMIC)) {
dev_err(&usbdev->dev, "usb_submit_urb failed, errno %d\n", error);
return error;
}
#endif
return 0;
free_input_device:
input_free_device(usb_mouse_dev->input);
free_usb_urb:
usb_free_urb(usb_mouse_dev->irq_urb);
free_usb_coherent:
usb_free_coherent(usb_mouse_dev->dev, usb_mouse_dev->maxlen,
usb_mouse_dev->data, usb_mouse_dev->data_dma);
free_usb_mouse:
kfree(usb_mouse_dev);
usb_mouse_dev = NULL;
return error;
}
static void usb_mouse_disconnect(struct usb_interface *intf)
{
dev_info(&usb_mouse_dev->dev->dev, "usb_mouse_disconnect\n");
usb_kill_urb(usb_mouse_dev->irq_urb);
input_unregister_device(usb_mouse_dev->input);
input_free_device(usb_mouse_dev->input);
usb_free_urb(usb_mouse_dev->irq_urb);
usb_free_coherent(usb_mouse_dev->dev, usb_mouse_dev->maxlen,
usb_mouse_dev->data, usb_mouse_dev->data_dma);
kfree(usb_mouse_dev);
usb_mouse_dev = NULL;
}
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_mouse_id_table);
static struct usb_driver usb_mouse_driver = {
.name = "usb_mouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
#if 0
static int __init usb_mouse_init(void)
{
int ret = usb_register(&usb_mouse_driver);
if (ret < 0)
pr_err("usb mouse register failed, errno: %d\n", ret);
return ret;
}
static void __exit usb_mouse_exit(void)
{
usb_deregister(&usb_mouse_driver);
}
module_init(usb_mouse_init);
module_exit(usb_mouse_exit);
#else
module_driver(usb_mouse_driver, usb_register, usb_deregister);
#endif // 1
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");