通用串行总线(USB)是主机和外围设备之间的一种连接。最新USB规范修订增加了理论上高达480Mbps的高速连接。

从拓扑上看,USB子系统并不是以总线的方式来布置的,它是一颗由几个点对点的连接构建而成的树。

USB是四线缆:地线、电源线、两根信号线

  • USB主控制器负责询问每一个USB设备是否有数据需要发送。
  • 一个USB设备在没有主控制器要求的情况下是不能发送数据的。
  • 方便搭建即插即用的系统。
  • 只担当设备和主控制器之间通信通道的角色,对它所发送的数据没有任何特殊的内容和结构上的要求。
  • USB协议规范定义了一套任何特定类型的设备都可以遵循的标准。
  • 不同特定类型称为类(class),包括存储设备、键盘、鼠标、游戏杆、网络设备和调制解调器,和其他需要驱动的设备。

Linux内核支持两种USB驱动程序:

  • 宿主(host)系统上的驱动程序
  • 设备(device)上的驱动程序
  • 宿主系统的USB驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制该设备如何作为一个USB设备和主机通信。

一、USB设备基础

USB设备非常复杂,官网www.usb.org中有详细描述

Linux内部有USB核心(USB core)的子系统来处理大部分的复杂性。

端点

USB通信最基本的形式是通过一个名为端点的东西。 USB端点只能往一个方向传输数据,可以看做单向管道。

USB端点四种不同类型,具有不同的传输数据的方式:

  • 控制:控制端点用来控制对USB设备不同部分的访问。
  • 中断:每当USB宿主要求设备传输数据时,中断端点就以一个固定的速率来传输少量的数据。
  • 批量:批量端点传输大批量的数据。
  • 等时:可以传送大批量的数据,但数据是否到达是没有保证的。

内核中使用struct usb_host_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;
};

struct usb_host_endpoint

结构中包含struct usb_endpoint_descriptor包含了真正的端点信息,所有的USB特定数据。这些数据时设备自己定义的。

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType; __u8 bEndpointAddress; /* 这是特定端点USB地址,端点方向 */
__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));

struct usb_endpoint_descriptor

接口

USB端点被捆绑为接口。USB接口值处理一种USB逻辑连接,一些USB设备具有多个接口,Linux需要不同的驱动程序来处理。

USB接口可以有其他的设置,这些是和接口的参数不同的选择。

内核使用struct usb_interface结构体来描述USB接口,USB核心把该结构体传递给USB驱动程序。

struct usb_interface {
/* array of alternate settings for this interface,
* stored in no particular order */
struct usb_host_interface *altsetting; struct usb_host_interface *cur_altsetting; /* the currently
* active alternate setting */
unsigned num_altsetting; /* number of alternate settings */ /* If there is an interface association descriptor then it will list
* the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc; int minor; /* minor number this interface is
* bound to */
enum usb_interface_condition condition; /* state of binding */
unsigned sysfs_files_created:; /* the sysfs attributes exist */
unsigned ep_devs_created:; /* endpoint "devices" exist */
unsigned unregistering:; /* unregistration is in progress */
unsigned needs_remote_wakeup:; /* driver requires remote wakeup */
unsigned needs_altsetting0:; /* switch to altsetting 0 is pending */
unsigned needs_binding:; /* needs delayed unbind/rebind */
unsigned reset_running:;
unsigned resetting_device:; /* 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 */
};
/* struct usb_host_interface *altsetting 一个接口结构体数组,包含了所有可能用于该接口的可选设置 */
/* unsigned num_altsetting:altsetting指针所指的可选设置的数量 */
/* struct usb_host_interface *cur_altsetting:指向altsetting数组内部的指针,表示该接口的当前活动设置 */
/* int minor:如果捆绑到该接口的USB驱动程序使用USB主设备号 */
/* struct usb_interface 结构体中还有其他的字段,不过USB驱动程序不需要考虑它们 */

struct usb_interface

配置

USB接口本身被捆绑为配置,一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。

Linux使用struct usb_host_config结构体来描述USB配置,使用struct usb_device结构体来描述整个USB设备。

struct usb_host_config {
struct usb_config_descriptor desc; char *string; /* iConfiguration string, if present */ /* List of any Interface Association Descriptors in this
* configuration. */
struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; /* the interfaces associated with this configuration,
* stored in no particular order */
struct usb_interface *interface[USB_MAXINTERFACES]; /* Interface information available even when this is not the
* active configuration */
struct usb_interface_cache *intf_cache[USB_MAXINTERFACES]; unsigned char *extra; /* Extra descriptors */
int extralen;
}; struct usb_device {
int devnum;
char devpath[];
u32 route;
enum usb_device_state state;
enum usb_device_speed speed; struct usb_tt *tt;
int ttport; unsigned int toggle[]; struct usb_device *parent;
struct usb_bus *bus;
struct usb_host_endpoint ep0; struct device dev; struct usb_device_descriptor descriptor;
struct usb_host_config *config; struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[];
struct usb_host_endpoint *ep_out[]; char **rawdescriptors; unsigned short bus_mA;
u8 portnum;
u8 level; unsigned can_submit:;
unsigned persist_enabled:;
unsigned have_langid:;
unsigned authorized:;
unsigned authenticated:;
unsigned wusb:;
int string_langid; /* static strings from the device */
char *product;
char *manufacturer;
char *serial; struct list_head filelist;
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev;
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry;
#endif int maxchild;
struct usb_device *children[USB_MAXCHILDREN]; u32 quirks;
atomic_t urbnum; unsigned long active_duration; #ifdef CONFIG_PM
unsigned long connect_time;
#ifdef CONFIG_SMM6260_MODEM
int autosuspend_delay;
#endif
unsigned do_remote_wakeup:;
unsigned reset_resume:;
#endif
struct wusb_dev *wusb_dev;
int slot_id;
};

struct usb_host_config和struct usb_device

USB设备驱动程序通常需要把一个给定的struct usb_interface结构体数据转换为struct usb_device结构体,USB核心在很多函数调用汇总都需要该结构体。

interface_to_usbdev用于该转换功能的函数。

简而言之,USB之间的逻辑单元之间的关系可以如下描述:

  • 设备通常具有一个或更多的配置
  • 配置经常具有一个或更多的接口
  • 接口通常具有一个或更多的设置
  • 接口没有或具有一个以上的端点

USB和sysfs

USB sysfs设备命名方案为:

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

对于一个两层的树,其设备名类似于:

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

sysfs并没有展示USB设备所有的不同部分,它只限于接口级别。不过可以通过/proc/bus/usb目录中,从/proc/bus/usb/device文件,

可以知道系统中存在的所有USB设备的可选配置。

USB urb

Linux内核中的USB代码通过一个称为Urb的东西和所有的USB设备通信。

struct urb结构体来描述这个请求块,可以在include/linux/usb.h文件中找到。

urb被用来以一种异步的方式往/从特定的USB设备上的特定USB端点发送/接收数据。

创建和销毁urb

struct urb 接口体不能在驱动程序中或者另一个结构体中静态地创建,因为这样会破坏USB核心对urb所使用的引用计数机制。

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
iso_packets:是该urb应该包含的等时数据包的数量,如果不是等时的,设置为0
mem_flags:传递给用于从内核分配内存的kmalloc函数
返回:urb的指针,NULL则是错误 void usb_free_urb(struct urb *urb);
释放urb指针

中断urb

正确地初始化即将被发送到USB设备的中断端点的urb:

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,
void *context, int interval);
struct urb *urb:指向需要初始化的urb指针
struct usb_device *dev:该urb所发送的目标USB设备
unsigned int pipe:该urb所发送的目标USB设备的特定端点
void *transger_buffer:用于保存外发数据或者接受数据的缓冲区的指针
int buffer_length:transfer_buffer指针所指向的缓冲区的大小
usb_complete_t complete:指向当该urb结束之后调用的结束处理例程的指针
void *context:指向一个小数据块,将被添加到urb结构体中以便进行结束处理例程后面的查找
int interval:该urb应该被调度的间隔

批量urb

void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
usngined int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context);

控制urb

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, void *context);
参数和usb_fill_bulk_urb完全一样,除了一个新参数
unsgined char *setup_packet:指向即将被发送到端点的设置数据包的数据

等时urb

等时urb没有中断、控制和批量urb类似的初始化函数,需要手动的进行初始化

urb->dev = dev;
urb->context = uvd;
urb->pipe = urb_rcvisopipe(dev, uvd->video_endp-);
urb->interval = ;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
for(j=;j<FRAMES_PER_DESC;j++) {
urb->iso_frame_desc[j].offset = j;
urb->iso_frame_desc[j].length = ;
}

等时urb初始化

提交urb

int usb_submit_urb(struct urb *urb, int mem_flags);
urb:指向即将被发送到设备的urb指针
mem_flags:传递给kmalloc的同一个参数
GFP_ATOMIC:
GFP_NOIO:在所有存储类型的设备的错误处理路径中也应该使用它
GFP_KERNEL:该值应该在前述类别之外的所有情况中使用

结束urb:结束回调处理例程

取消urb

int usb_kill_urb(struct urb *urb);
int usb_unlink_urb(struct urb *urb);

二、编写USB驱动程序

对于PCI设备,有许多用来初始化该结构体的宏:

USB_DEVICE(vendor, product)
创建一个struct usb_device_id结构体,仅指定制造商和产品ID匹配
USB_DEVICE_VER(vendor, product, lo, hi)
创建一个struct usb_device_id结构体,指定制造商和产品ID值相匹配
USB_DEVICE_INFO(class, subclass, protocol)
创建一个struct usb_device_id结构体,仅和USB设备的指定类型相匹配
USB_INTERFACE_INFO(class, subclass, protocol)
创建一个struct usb_device_id结构体,仅和USB接口的指定类型相匹配 struct usb_device_id表将被定义为:
/* 该驱动程序支持的设备列表 */
static struct usb_device_id skel_table [ ] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, skel_table);

注册USB驱动程序

创建一个有效的struct usb_driver结构体只需要初始化五个字段:

static struct usb_driver skel_driver = {
.owner = THIS_MODULE,
.name = "skeleton",
.id_table = skel_table,
.probe = skel_probe,
.disconnect = skel_disconnect,
};

接口函数原型:

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)
ioctl函数
int (*suspend)(struct usb_interface *intf, u32 state)
挂起函数
int (*resume)(struct usb_interface *intf)
恢复函数

函数接口

探测和断开的细节

/* 设置端点信息 */
/* 只使用第一个批量IN和批量OUT端点 */
iface_desc = interface->cur_altsetting;
for(i=;i<iface_desc->desc.bNumEndpoints;++i) {
endpoint = &iface_desc->endpoint[i].desc;
if(!dev->bulk_in_endpointAddr &&
(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) {
/* 发现一个批量IN类型的端点 */
buffer_size = endpoint->wMaxPacketSize;
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if(!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}
if(!dev->bulk_out_endpointAddr &&
!(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) {
/* 发现一个批量OUT类型的端点 */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
}
if(!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}

探测IN和OUT端点

提交和控制urb

/* 分配urb来把数据传输给设备 */
urb = usb_alloc_urb(, GFP_KERNEL);
if(!urb) {
retval = -ENOMEM;
goto error;
}
/* 创建DMA缓冲区来以最高效的方式发送数据到设备 */
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if(!buf) {
retval = -ENOMEM;
goto error;
}
if(copy_from_user(buf, user_buffer, count)) {
retval = -EFAULT;
goto error;
}
/* 数据被复制到局部缓冲区中,urb必须可以提交给USB核心之前被正确的初始化 */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* urb正确分配,数据被正确复制,urb正确初始化,然后可以提交给USB核心以传输到设备 */
/* 把数据从批量端口发出 */
retval = usb_submit_urb(urb, GFP_KERNEL);
if(retval) {
err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
goto error;
}
/* urb被成功地传输到USB设备之后,urb毁掉函数将被USB核心调用 */
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
/* sync/async解连接故障不是错误 */
if(urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)) {
dbg("%s - nonzero write bulk status received: %d",
__FUNCTION__, urb->status);
} /* 释放已分配的缓冲区 */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
}

urb的使用流程

不使用urb的USB传输

有时候USB驱动程序只是要发送或者接受一些简单的USB数据,而不想把上面的流程走一遍,有两个函数提供了更简单接口和函数的使用。

usb_bulk_msg

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length,
int timeout);
struct usb_device *usb_dev:发送的目标USB指针
unsigned int pipe:目标USB的特定端点
void *data:如果是OUT端点,指向即将发送到设备的数据的指针
int len:data参数所指缓冲区的大小
int *actual_length:指向保存实际传输字节数的位置的指针
int timeout:等待的超时时间

函数调用例子:

/* 进行阻塞的批量读以设别获取数据 */
retval = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
&count, HZ*);
/* 如果读取成功,复制数据到用户空间 */
if(!retval) {
if(copy_to_user(buffer, dev->bulk_in_buffer, count))
retval = -EFAULT;
else
retval = count;
}

使用例子

usb_control_msg

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);
struct usb_device *dev:控制消息所发送的目标USB设备的指针
unsigned int pipe:该控制消息所发送的目标USB设备的特定端点
__u8 request:控制消息的USB请求值
__u8 requesttype:控制消息的USB请求类型值
__u16 value:USB消息值
__u16 index:USB消息索引值
void *data:一个OUT端点,指向即将发送到设备的数据指针
__u16 size:data参数所指缓冲区的大小
int timeout:等待的超时时间

其他USB数据函数

int usb_get_descriptor(struct usb_device *dev, unsigned char type,
unsigned char index, void *buf, int size);
struct usb_device *usb_dev:指向想要获取描述符的目标USB设备的指针
unsigned char type:描述符的类型
unsigned char index:应该从设备获取的描述符的编号
void *buf:指向复制描述符到其中的缓冲区的指针
int size:buf变量所指的内存大小

LDD3 第13章 USB驱动程序的更多相关文章

  1. ldd3 第12章 PCI驱动程序

    PCI接口 PCI寻址 引导阶段 配置寄存器和初始化 MODULE_DEVICE_TABLE 注册PCI驱动程序 佬式PCI探测 激活PCI设备 访问配置空间 访问I/O和内存空间 PCI中断 硬件抽 ...

  2. Linux就这个范儿 第13章 打通任督二脉

    Linux就这个范儿 第13章 打通任督二脉 0111010110……你有没有想过,数据从看得见或看不见的线缆上飞来飞去,是怎么实现的呢?数据传输业务的未来又在哪里?在前面两章中我们学习了Linux网 ...

  3. 第四章 USB库介绍

    4.1 USB库函数简介 Luminary Micro公司提供USB处理器的USB库函数,应用在Stellaris处理器上,为USB设备.USB主机.OTG开发提供USB协议框架和API函数,适用于多 ...

  4. 用DriverStudio开发USB驱动程序

    很多写Windows Device Driver的开发人员基本上都是使用Windows DDK进行开发的.但是,现在也有不少人都开始借助一些辅助工具.笔者去年开始接触到DriverStudio,发现它 ...

  5. USB2.0学习笔记连载(三):通用USB驱动程序解析

    对于USB驱动的开发,读者可以使用Windows DDK.DriverStudio等多种开发工具来实现USB的驱动,但是驱动程序的开发过程都比较复杂,而且很容易致使USB主机内存泄露而死机.那么对于笔 ...

  6. USB驱动程序之USB总线驱动程序学习笔记

    USB总线驱动程序的作用 1. 识别USB设备 1.1 分配地址 1.2 并告诉USB设备(set address) 1.3 发出命令获取描述符 描述符的信息可以在include\linux\usb\ ...

  7. ASM:《X86汇编语言-从实模式到保护模式》第13章:保护模式下内核的加载,程序的动态加载和执行

    ★PART1:32位保护模式下内核简易模型 1. 内核的结构,功能和加载 每个内核的主引导程序都会有所不同,因为内核都会有不同的结构.有时候主引导程序的一些段和内核段是可以共用的(事实上加载完内核以后 ...

  8. 《深入Java虚拟机学习笔记》- 第13章 逻辑运算

    <深入Java虚拟机学习笔记>- 第13章 浮点运算

  9. 《Android开发艺术探索》读书笔记 (13) 第13章 综合技术、第14章 JNI和NDK编程、第15章 Android性能优化

    第13章 综合技术 13.1 使用CrashHandler来获取应用的Crash信息 (1)应用发生Crash在所难免,但是如何采集crash信息以供后续开发处理这类问题呢?利用Thread类的set ...

随机推荐

  1. java 为啥可打印date

    打印一个对象的时候,会打印出它的toString方法的返回值,Date重写了toString方法.

  2. nginx用途

     Nginx常用来做静态内容服务器和代理服务器,用来放置静态资源或者转发请求给后面的应用服务. 1. Nginx作为静态服务器使用 作为一个Web服务器,其最主要的任务是作为静态服务器使用. 你需要将 ...

  3. CDN:目录

    ylbtech-CDN:目录 1. 前端开源项目返回顶部 1. http://www.bootcdn.cn/ 2. https://www.npmjs.com/ 3. 2.返回顶部   3.返回顶部 ...

  4. 深入学习Keras中Sequential模型及方法

    https://www.cnblogs.com/wj-1314/p/9579490.html

  5. windows 命令端口映射

    显示映射列表netsh interface portproxy show all 将本地的1234端口的数据转发至192.168.x.x上的1234端口netsh interface portprox ...

  6. PHP 调试 - Xdebug

    PHP 调试指南.pdf PHP 程序员的调试技术 根据要调试的对象的不同,采取的方法也不一样: 调试 web 应用:对于 web 应用,可以在浏览器中安装插件,或者在 IDE 中设置,下面的设置二选 ...

  7. 2019/10/26 TZOJ

    1001 Flooded Island http://www.tzcoder.cn/acmhome/problemdetail.do?&method=showdetail&id=452 ...

  8. 20190817 On Java8 第七章 封装

    第七章 封装 访问控制权限的等级,从"最大权限"到"最小权限"依次是:public,protected,包访问权限(没有关键字)和 private. 包的概念 ...

  9. VScode 常用快捷键 2019

    窗口操作 Ctrl + b      : 显示/隐藏左侧工作区文件目录 View   Appearance   show Activity bar : 最左侧工具栏 显示/隐藏 Preferences ...

  10. Ngnix VS Apache

    Ngnix和Apache各有优缺点, Ngnix在并发性能上比Apache好太多了 原因是,Ngnix是采用的epoll网络I/O模型, 而Apache采用的是select网络I/O模型 具体参见:  ...