【驱动】USB驱动·入门

时间:2022-08-23 16:20:12

Preface

   USB是目前最流行的系统总线之一。随着计算机周围硬件的不断扩展,各种设备使用不同的总线接口,导致计算机外部总线种类繁多,管理困难。USB总线正是因此而诞生的。

USB总线提供了所有外部设备的统一连接方式,并且支持热插拔,方便了厂商开发设备和用户使用设备。

 

 


USB遵循原则

   USB的设计目标是对现有的PC机体系进行扩充,但是目前不仅是PC机,许多的嵌入式系统都开始支持USB总线和接口标准。USB设计主要遵循下面几个原则

    • 易于扩充外部设备:USB支持一个接口最多127个设备。

    • 灵活的传输协议: 支持同步和异步数据传输。

    • 设备兼容性好: 可以兼容不同类型的设备。

    • 接口标准统一:不同的设备之间使用相同的设备接口。

 


USB体系概述

   USB接口标准支持主机和外部设备之间进行数据传输。

   在USB体系结构中,主机预定了各种类型外部设备使用的总线带宽。当外部设备和主机在运行时,USB总线允许添加、设置、使用和拆除外设。

   在USB体系结构中,一个USB系统可以分成USB互联、USB设备和USB主机三个部分。

   USB互联是USB设备和USB主机之间进行连接通信的操作,主要包括:

    • 总线拓扑结构:USB主机和USB设备之间的连接方式。

    • 数据流模式:描述USB通信系统中数据如何从产生方传递到使用方。

    • USB调度:USB总线是一个共享连接,对可以使用的连接进行了调试以支持同步数据传输,并且避免优先级判定的开销。

   USB的物理连接是一个有层次的星形结构。

【驱动】USB驱动·入门

   在一个节点上连接多个设备需要使用 USB集线器(USB HUB)。

   USB体系结构规定,在一个 USB系统中,只有唯一的一个主机。USB和主机系统的接口称做主机控制器,主机控制器由主机控制器芯片、固件程序和软件共同实现的。

   USB设备包括USB集线器和功能器件。其中USB集线器的作用是扩展总线端点,向总线提供更多的连接点;功能器件是用户使用的外部设备,如键盘,鼠标等。

   USB设备需要支持 USB总线协议,对主机的操作提供反馈并且提供设备性能的描述信息。

 


USB体系工作流程

   USB总线采用轮询方式控制,主机控制设置初始化所有的数据传输。

   USB总线每次执行传输动作最多可以传输三个数据包。每次开始传输时,主机控制器发送一个描述符描述传输动作的种类和方向,这个数据包称作标志数据包(Token Packet)。USB设备收到主机发送的标志数据包后解析出数据自己的数据。

   USB数据传输的方向只有两种:主机到设备或者设备到主机。

   在一个数据传输开始时,由标志包标示数据的传输方向,然后发送端开始发送包含信息的数据。接收端发送一个握手的数据包表明数据是否传送成功。

   在主机和设备之间的USB数据传输可以看做一个通道。USB数据传输有流和消息两种通道。消息是有格式的数据,而流是没有数据格式的。

   USB有一个缺省的控制消息通道,在设备启动的时候被创建,因此设备的设置查询和输入控制信息都可以使用缺省消息控制通道完成。

 


USB驱动程序框架

   Linux内核提供了完整的USB驱动程序框架。

   USB总线采用树形结构,在一条总线上只能有唯一的主机设备。

   Linux内核从主机和设备两个角度观察USB总线结构。

 


Linux内核USB驱动框架

【驱动】USB驱动·入门

 

 

   左侧是主机驱动结构。

   主机驱动的最底层是 USB主机控制器,提供了 OHCI/EHCI/UHCI这3种类型的总线控制功能。

   在USB控制器的上一层是主机控制器的驱动,分别对应OHCI/EHCI/UHCI这3种类型的总线接口。

   USB核心部分连接了 USB控制器驱动和设备驱动,是两者之间的转换接口。

   USB设备驱动层提供了各种设备的驱动程序。

   所有类型的 USB设备都是用相同的电气接口,使用的传输协议也基本相同。

   向用户提供某种特定类型的 USB设备时,需要处理 USB总线协议。内核完成所有的 USB总线协议处理,并且向用户提供编程接口。

 

   右侧是设备驱动结构。

   与USB主机类似,USB设备提供了相同的层次结构与之对应。但是在 USB设备一侧使用名为 Gadget API的结构作为核心。

   Gadget API是 Linux内核实现的对应 USB设备的核心结构。Gadget API屏蔽了 USB设备控制器的细节,控制具体的 USB设备实现。

 


设备

   每个 USB设备提供了不同级别的配置信息。

   一个 USB设备可以包含一个或多个配置,不同的配置使设备表现出不同的特点。其中,设备的配置是通过接口组成的。

   Linux内核定义了 USB设备描述结构如下:

//源定义在Usb_ch9.h
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
__u8 bLength; //设备描述符长度
__u8 bDescriptorType; //设备类型
__le16 bcdUSB; // USB版本号(使用 BCD编码)
__u8 bDeviceClass; // USB设备类型
__u8 bDeviceSubClass; // USB设备子类型
__u8 bDeviceProtocol; // USB设备协议号
__u8 bMaxPacketSize0; //传输数据的最大包长
__le16 idVendor; //厂商编号
__le16 idProduct; //产品编号
__le16 bcdDevice; //设备出厂号
__u8 iManufacturer; //厂商字符串索引
__u8 iProduct; //产品字符串索引
__u8 iSerialNumber; //产品序列号索引
__u8 bNumConfigurations; //最大的配置数量
} __attribute__ ((packed));

 

   从 usb_device_descrptor结构定义看出,一个设备描述定义了与 USB设备有关的所有信息。

 


接口

   在 USB体系中,接口是由多个端点组成的。

   一个接口代表一个基本的功能,是 USB设备驱动程序控制的对象。

   一个 USB设备最少有一个接口,功能复杂的 USB设备可以有多个接口。接口描述定义如下:Usb

//源定义在 Usb_ch9.h
/* 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));

 


端点

   端点是 USB总线通信的基本形式,每个 USB设备接口可以认为是端点的集合。

   主机只能通过端点与设备通信。

   USB体系结构规定每个端点都有一个唯一的地址,由设备地址和端点号决定端点地址。

   端点还包括了与主机通信用到的属性,如传输方式、总线访问频率、带宽和端点号等。

   端点的通信是单向的,通过端点传输的数据只能是从主机到设备或者从设备到主机。

   端点的定义描述如下:

/* 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));

 


配置

   配置是一个接口的集合。

   Linux内核配置的定义如下:

struct usb_config_descriptor {
__u8 bLength; //描述符长度
__u8 bDescriptorType; //描述符类型
__le16 wTotalLength; //配置返回数据长度
__u8 bNumInterfaces; //最大接口数
__u8 bConfigurationValue; //配置参数值
__u8 iConfiguration; //配置描述字符串索引
__u8 bmAttributes; //供电模式
__u8 bMaxPower; //接口的最大电流
} __attribute__ ((packed));

 


主机驱动结构

   USB主机控制器有三种类型:

    • OHCI,英文全称是Open Host Controller Interface。OHCI是用于SiS和Ali芯片组的USB控制器。

    • UHCI,英文全称是Universal Host Controller Interface。UHCI用于Intel和AMD芯片组的USB控制器。UHCI类型的控制器比OHCI控制器硬件结构要简单,但是需要额外的驱动支持,因此从理论上说速度要慢。

    • EHCI,USB2.0规范提出的一种控制器标准,可以兼容UHCI和OHCI。

 


USB主机控制器驱动

   Linux内核使用 usb_hcd结构描述 USB主机控制器驱动。

   usb_hcd结构描述了 USB主机控制器的硬件信息、状态和操作函数。定义如下:

//源定义在Hcd.h
struct usb_hcd { /* usb_bus.hcpriv points to this */
/*
* housekeeping //控制器基本信息
*/
struct usb_bus self; /* hcd is-a bus */
const char *product_desc; /* product/vendor string */ //厂商名称字符串
char irq_descr[24]; /* driver + bus # */ //驱动和总线类型
struct timer_list rh_timer; /* drives root-hub polling */ //根 hub轮询时间间隔
struct urb *status_urb; /* the current status urb */ //当前 urb状态
/*
* hardware info/state //硬件信息和状态
*/
const struct hc_driver *driver; /* hw-specific hooks */ //控制器驱动使用的回调函数
/* Flags that need to be manipulated atomically */
unsigned long flags;
#define HCD_FLAG_HW_ACCESSIBLE 0x00000001
#define HCD_FLAG_SAW_IRQ 0x00000002
unsigned rh_registered:1;/* is root hub registered? */ //是否注册根 hub
/* The next flag is a stopgap, to be removed when all the HCDs
* support the new root-hub polling mechanism. */
unsigned uses_new_polling:1; //是否允许轮询根 hub状态
unsigned poll_rh:1; /* poll for rh status? */
unsigned poll_pending:1; /* status has changed? */ //状态是否改变
int irq; /* irq allocated */ //控制器的中断请求号
void __iomem *regs; /* device memory/io */ //控制器使用的内存和 I/O
u64 rsrc_start; /* memory/io resource start */ //控制器使用的内存和 I/O起始地址
u64 rsrc_len; /* memory/io resource length */ //控制器使用的内存和 I/O资源长度
unsigned power_budget; /* in mA, 0 = no limit */
#define HCD_BUFFER_POOLS 4
struct dma_pool *pool [HCD_BUFFER_POOLS];
int state;
# define __ACTIVE 0x01
# define __SUSPEND 0x04
# define __TRANSIENT 0x80
# define HC_STATE_HALT 0
# define HC_STATE_RUNNING (__ACTIVE)
# define HC_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE)
# define HC_STATE_RESUMING (__SUSPEND|__TRANSIENT)
# define HC_STATE_SUSPENDED (__SUSPEND)
#define HC_IS_RUNNING(state) ((state) & __ACTIVE)
#define HC_IS_SUSPENDED(state) ((state) & __SUSPEND)
/* 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(unsigned long))));
};

 


OHCI控制器驱动

   usb_hcd结构可以理解为一个通用的 USB控制器描述结构,OHCI主机控制器是 usb_hcd结构的具体实现。

   内核使用 ohci_hcd结构描述 OHCI主机控制器,定义如下:

struct ohci_hcd {
spinlock_t lock;
/*
* I/O memory used to communicate with the HC (dma-consistent) //用于 HC通信的 I/O内存地址
*/
struct ohci_regs __iomem *regs;
/*
* main memory used to communicate with the HC (dma-consistent)。 //用于 HC 通告的主内存地址
* hcd adds to schedule for a live hc any time, but removals finish
* only at the start of the next frame.
*/
struct ohci_hcca *hcca;
dma_addr_t hcca_dma;
struct ed *ed_rm_list; /* to be removed */ //将被移除列表
struct ed *ed_bulktail; /* last in bulk list */ //列表最后一项
struct ed *ed_controltail; /* last in ctrl list */ //控制列表最后一项
struct ed *periodic [NUM_INTS]; /* shadow int_table */
/*
* OTG controllers and transceivers need software interaction;
* other external transceivers should be software-transparent
*/
struct otg_transceiver *transceiver;
/*
* memory management for queue data structures //内存管理队列使用的数据结构
*/
struct dma_pool *td_cache;
struct dma_pool *ed_cache;
struct td *td_hash [TD_HASH_SIZE];
struct list_head pending;
/*
* driver state
*/
int num_ports;
int load [NUM_INTS];
u32 hc_control; /* copy of hc control reg */ // HC控制寄存器复制
unsigned long next_statechange; /* suspend/resume */ //挂起 恢复
u32 fminterval; /* saved register */ //保存的寄存器
struct notifier_block reboot_notifier;
unsigned long flags; /* for HC bugs */
#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */
#define OHCI_QUIRK_SUPERIO 0x02 /* natsemi */
#define OHCI_QUIRK_INITRESET 0x04 /* SiS, OPTi, ... */
#define OHCI_BIG_ENDIAN 0x08 /* big endian HC */
#define OHCI_QUIRK_ZFMICRO 0x10 /* Compaq ZFMicro chipset*/
// there are also chip quirks/bugs in init logic //芯片的初始化逻辑里也同样会有怪异的 Bug
};

   OHCI主机控制器是嵌入式系统最常用的一种 USB主机控制器。

 


设备驱动结构

   USB协议规定了许多种USB设备类型。Linux内核实现了音频设备、通信设备、人机接口、存储设备、电源设备、打印设备等几种USB设备类。

 


基本概念

   Linux内核实现的 USB设备驱动都是针对通用的设备类型设计的。

   只要 USB存储设备是按照标准的 USB存储设备规范实现的,就可以直接被内核 USB存储设备驱动。如果一个 USB设备是非标准的,则需要编写对应设备的驱动程序。

 


设备驱动结构

   内核使用 usb_driver结构体描述 USB设备驱动,定义如下:

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 (*ioctl) (struct usb_interface *intf, unsigned int code,
void *buf); // I/O控制函数
int (*suspend) (struct usb_interface *intf, pm_message_t message); //挂起函数
int (*resume) (struct usb_interface *intf); //恢复函数
void (*pre_reset) (struct usb_interface *intf);
void (*post_reset) (struct usb_interface *intf);
const struct usb_device_id *id_table;
struct usb_dynids dynids;
struct device_driver driver;
unsigned int no_dynamic_id:1;
};
    • 实现一个 USB设备的驱动主要是实现 probe()和 disconnect()函数接口。

    • probe()函数在插入 USB设备的时候被调用,disconnect()函数在拔出 USB设备的时候被调用。

       


USB请求块

   USB请求块(USB request block,urb)的功能类似于网络设备中的 sk_buff,用于描述 USB设备与主机通信的基本数据结构。

   urb结构在内核中定义如下:

//源定义在 Usb.h
struct urb
{
/* private: usb core and host controller only fields in the urb */
struct kref kref; /* reference count of the URB */ // urb引用计数
spinlock_t lock; /* lock for the URB */ // urb锁
void *hcpriv; /* private data for host controller */ //主机控制器私有数据
int bandwidth; /* bandwidth for INT/ISO request */ //请求带宽
atomic_t use_count; /* concurrent submissions counter */ //并发传输计数
u8 reject; /* submissions will fail */ //传输即将失败标志
/* public: documented fields in the urb that can be used by drivers */ //公有数据,可以被驱动使用
struct list_head urb_list; /* list head for use by the urb's //链表头
* current owner */
struct usb_device *dev; /* (in) pointer to associated device */ //关联的 USB设备
unsigned int pipe; /* (in) pipe information */ //管道信息
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 */ //DMA使用的缓冲区
int transfer_buffer_length; /* (in) data buffer length */ //缓冲区大小
int 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/ISO) */
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];
/* (in) ISO ONLY */
};

   内核提供了一组函数 urb类型的结构变量。urb的使用流程如下:

    1. 创建 urb。在使用之前,USB设备驱动需要调用 usb_alloc_urb()函数创建一个 urb;内核还提供释放 urb的函数,在不使用 urb的时候(退出驱动种马或者挂起驱动),需要使用 usb_free_urb()函数释放 urb。

    2. 初始化 urb。设置 USB设备的端点。使用内核提供的 usb_init_urb()函数设置 urb初始结构。

    3. 提交 urb到 USB核心。在分配并设置 urb完毕后,使用 urb_submit_urb()函数把新的 urb提交到 USB核心。

 


USB驱动程序框架

   Linux内核代码driver/usb/usb-skeleton.c文件是一个标准的USB设备驱动程序。

   编写一个USB设备的驱动可以参考usb-skeleton.c文件,实际上,可以直接修改该文件驱动新的USB设备。

 


基本数据结构

   usb-skel设备使用自定义结构 usb_skel记录设备驱动用到的所有描述符,该结构定义如下:

/* Structure to hold all of our device specific stuff */
struct usb_skel {
struct usb_device * udev; /* the usb device for this device */
// USB设备描述符
struct usb_interface * interface; /* the interface for this device */
// USB接口描述符
struct semaphore limit_sem; /* limiting the number of writes in progress */
// 互斥信号量
unsigned char * bulk_in_buffer; /* the buffer to receive data */
// 数据接收缓冲区
size_t bulk_in_size; /* the size of the receive buffer */
// 数据接收缓冲区大小
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
// 入端点地址
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
// 出端点地址
struct kref kref;
};

   usb-skel设备驱动把 usb_skel结构存放在了 urb结构的 context指针里。通过 urb,设备的所有操作函数都可以访问到 usb_skel结构。

   其中,limit_sem成员是一个信号量,当多个 usb-skel类型的设备存在于系统中的时候,需要控制设备之间的数据同步。

 


驱动程序初始化和注销

   与其他所有的 Linux设备驱动程序一样,usb-skel驱动使用 module_init()宏设置初始化函数,使用 module_exit()宏设置注销函数。

   usb-skel驱动的初始化函数是 usb_skel_init()函数,定义如下:

static int __init usb_skel_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&skel_driver); //注册 USB设备驱动
if (result)
err("usb_register failed. Error number %d", result);
return result;
}

   usb_skel_init()函数调用内核提供的 usb_register()函数注册了一个 usb_driver类型的结构变量,该变量定义如下:

static struct usb_driver skel_driver = {
.name = "skeleton", // USB设备名称
.probe = skel_probe, // USB设备初始化函数
.disconnect = skel_disconnect, // USB设备注销函数
.id_table = skel_table, // USB设备 ID映射表
};

   skel_driver结构变量中,定义了 usb-skel设备的名、设备初始化函数、设备注销函数和 USB ID映射表。

   其中 usb-skel设备的 USB ID映射表定义如下:

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};

   skel_table中只有一项,定义了一个默认的 usb-skel设备的 ID,其中,USB_SKEL_VENDOR_ID是 USB设备的厂商 ID,USB_SKEL_PRODUCT_ID是 USB设备 ID。

 


设备初始化

   从 skel_driver结构可以知道 usb-skel设备的初始化函数是 skel_probe()函数。

设备初始化主要是探测设备类型,分配 USB设备用到的 urb资源,注册 USB设备操作函数等。

skel_class结构变量记录了 usb-skel设备信息,定义如下:

/*
* usb class driver info in order to get a minor number from the usb core,
* and to have the device registered with the driver core
*/
static struct usb_class_driver skel_class = {
.name = "skel%d", //设备名称
.fops = &skel_fops, //设备操作函数
.minor_base = USB_SKEL_MINOR_BASE,
};

   name变量使用 %d通配符表示一个整形变量,当一个 usb-skel类型的设备连接到 USB总线后会按照子设备编号自动设置设备名称。

   fops是设备操作函数结构变量,定义如下:

static struct file_operations skel_fops = {
.owner = THIS_MODULE,
.read = skel_read, //读操作
.write = skel_write, //写操作
.open = skel_open, //打开操作
.release = skel_release, //关闭操作
};

   skel_ops定义了 usb-skel设备的操作函数。当在 usb-skel设备上发生相关事件时,USB文件系统会调用对应的函数处理。

 


设备注销

   skel_disconnect()函数在注销设备的时候被调用,定义如下:

static void skel_disconnect(struct usb_interface *interface)
{
struct usb_skel *dev;
int minor = interface->minor;
/* prevent skel_open() from racing skel_disconnect() */
lock_kernel(); //在操作之前加锁
dev = usb_get_intfdata(interface); //获得 USB设备接口描述
usb_set_intfdata(interface, NULL); //设置 USB设备接口描述无效
/* give back our minor */
usb_deregister_dev(interface, &skel_class); //注销 USB设备操作供述
unlock_kernel(); //操作完毕解锁
/* decrement our usage count */
kref_put(&dev->kref, skel_delete); //减小引用计数
info("USB Skeleton #%d now disconnected", minor);
}
static struct usb_driver skel_driver = {
.name = "skeleton", // USB设备名称
.probe = skel_probe, // USB设备初始化函数
.disconnect = skel_disconnect, // USB设备注销函数
.id_table = skel_table, // USB设备 ID映射表
};

   skel_disconnect()函数释放 usb-skel设备用到的资源。

   首先获取 USB设备接口描述,之后设置为无效;然后调用 usb_deregister_dev()函数注销 USB设备的操作描述符,注销操作本身需要加锁;注销设备描述符后,更新内核对 usb-skel设备的引用计数。

 

本文出自 “成鹏致远” 博客,请务必保留此出处http://infohacker.blog.51cto.com/6751239/1226257