USB从入门到精通-1-Linux那些事儿之USB学习与扩展

本文介绍了USB协议的发展背景、USB子系统的物理拓扑及功能抽象,重点分析了Linux内核中USB核心部分的初始化过程,包括USB总线注册、设备驱动注册以及USB设备的四大组成部分——接口、端点、设备和配置。同时,讨论了设备模型中的匹配函数和设备注册流程,展示了Linux如何管理和驱动USB设备。
摘要由CSDN通过智能技术生成

标签: USB协议 UKey分析


资源来自《Linux那些事儿之USB》,本文主要是读书笔记,附加一些自己的分析和相关知识点说明
内核代码版本2.6.24

目录

1. USB概述

1.1. 为什么要开发USB?

在USB出现以前,电脑的接口很多,串口、并口多方割据,键盘、鼠标、MODEM、打印机、扫描仪等都要链接到不同种类的接口上,一个接口只能链接一个设备。电脑不可能有那么多接口,扩展能力不足,速度也有限,而且不支持热插拔。
USB正是为了解决速度、扩展能力、易用性等问题应景而生的。
USB最初是替代串行、并行等各种低速总线,以单一类型总线链接各种不同的设备,USB2.0速度达到480MB/s,USB3.0是USB2.0速度的十倍。

Linux对USB1.1和USB2.0都是支持的,并抢在Windows前,在2.6.31内核中率先进行了USB3.0的支持

USB能够支持几千字节到几十兆字节的传输速率,来适应不同种类的外设。可以支持多达127个设备同时操作,也支持多功能设备。多功能设备当前指的就是一个设备同时有多个功能,比如USB扬声器。这通过在一个设备中包含多个接口来支持,一个接口支持一个功能。USB可以保证固定的贷款,这对视频/音频设备是利好的。

1.2. USB子系统物理拓扑及功能抽象

USB子系统的拓扑也是一棵树,并不以总线的方式来部署。这颗树包括USB连接、USB Host Controller和USB设备三个部分。而USB设备还包含Hub和功能设备。如下图所示:

USB子系统拓扑结构

USB主机控制器控制所有USB设备的通信,PC和USB主机控制器交互。Hub是对一个USB接口的扩展。现实中通常一个USB控制器和一个Hub集成在一起,该Hub称为Root Hub。

USB连接指的就是USB设备和主机(或Hub)的四线电缆,包括VBUS(电源线)、GND(地线)和两根信号线。

Compound Device就是将Hub和连在Hub上的设备封装在一起的所组成的设备。如下图所示。Composite Device是包含彼此独立的多个接口的设备。Compound Device的地址是独立的,Composite Device不论由多少接口,都只有一个地址。

Compound Device

USB总线是一种轮询式总线,协议规定所有的数据传输必须由主机发起,主机控制器初始化所有的数据传输,各种设备围绕在主机周围。

USB通信通过USB设备中的Endpoint(端点),主机和端点之间通过Pipe(管道)传输数据。端点就是通信的发送点和接受点,将数据给端点即可。管道实际上是为了让我们能够找到端点,常说的编码地址。

端点管道交互

端点是有方向的,in或者out,生来注定。此外,协议规定USB设备必须有0号端点,可以是in也可以是out,实现默认的控制管道,进行设备控制。除了端点0,低速设备最多拥有两个端点,高速设备最多有15个in和15个out,这些端点在设备内部有唯一的端点号,在设备设计时就已经指定的。

管道分两种:message和stream,message要求数据时有格式的,协议规定message管道必须对应两个相同号码的端点:一个用来in,一个用来out,默认管道就是message管道,与之对应的0号端点就必须是有两个同样端点号0的端点。

USB端点有4中类型,对应四种数据传输方式:控制、中断、批量、等时。

  • 控制传输:配置设备、获取信息、发送指令、获取状态报告。传送控制信息,每个USB设备多有一个端点0的控制端点。
  • 中断传输:固定速率传送少量数据,键盘、鼠标和触摸屏,传输数据包含坐标信息。
  • 批量传输:传送大量数据,保证数据没有丢失,但是不保证在特定的时间内完成。U盘用的就是批量传输。
  • 等时传输:传送大量数据,不保证数据没有丢失,以稳定的速率发送和接受等时的信息,对传送延迟非常敏感。音频、视频类的设备。

虽然实际树形物理拓扑比较复杂,但是对于内核来说,所有的Hub和设备都被看做连接在Root Hub上的一个个逻辑设备。一个USB逻辑设备就是一系列端点的集合,与主机之间的通信发生在主机上的一个缓冲区和设备上的一个端点之间,通过管道来传输数据。
USB端点被捆绑为接口(Interface),一个接口代表一个基本功能,有的设备具有多个接口,例如USB扬声器就包含一个键盘接口和一个音频流接口。内核中一个接口对应一个驱动程序。

1.3. sysfs-USB

/sys/devices/pci0000:00/0000:00:09.0/usb2/2-1/2-1:1.0

USB系统中的第一个USB设备是Root Hub,通常包含在PCI设备中,是连接PCI总线和USB总线的bridge,控制着连接到其上的整个USB总线,所有的Root Hub,内核的USB Core都分配有独特的编号,例如上面例子中的USB2。

2-1表示Root Hub的哪个端口上插入了设备。2-1::1.0,后面1.0表示配置编号和接口编号。

通过usbfs可以找到设备的可选配置,usbfs挂载在/proc/bus/usb目录,从/proc/bus/usb/device文件可以直到系统中存在的所有USB设备的可选配置。

2. drivers/usb代码内容说明

drivers/usb目录包含内容:

atm/ 
class/ 
core/ 
gadget/ 
host/ 
image/ 
misc/ 
mon/ 
serial/ 
storage/ 
Kconfig 
Makfile 
README 
usb-skeleton.c

Core,内核开发人员专门写了一些代码,负责实现一些核心的功能,为别的设备驱动程序提供服务,例如申请内存等,实现一些所有的设备都需要的公共函数,美其名曰为USB Core。

Host,各主机控制器单独的代码移到host目录下,负责各种主机控制器的人来维护。主机控制器公共的代码任然保留在core目录下。

USB gadget,配件,一些内部运行Linux的嵌入式设备,设备有USB设备控制器,可以将PC,也即是我们的主机作为master端,将设备作为slave端和主机通过USB进行通信,从主机的观点来看,主机系统的USB驱动程序控制插入其中的USB设备,而USB gadget的驱动程序控制外围设备作为一个USB设备和主机通信。(因为连接的设备端其实也是一个host controller,所以需要模拟,U盘、网卡等)。
gadget目录下大概分为两个模块:

  • 一个udc驱动,针对具体CPU平台的,找不到现成的可以自己实现。
  • 另一个就是gadget驱动,主要有file_storage、ether、serial等。
  • 另外还提供了USB gadget API,就是USB设备控制器硬件和gadget驱动通信的接口。

image、input、media、net、serial、storage:剩下的几个目录分别存放各种USB设备的驱动,U盘驱动在storage目录下,触摸屏和USB键盘鼠标的驱动在input目录下。
class:如果前面几个目录没有该驱动,则在此目录下寻找。USB协议中除了通用的软硬件电气接口规范等,还包含了各种各样的Class协议,来为不同的功能定义各自的标准接口和具体总线上的数据交互格式和内容,例如支持U盘功能的Mass Storage Class。
misc:如果之前的目录都没有该设备的驱动,就在该目录下寻找。

usb-skeleton.c是一个简单的USB driver框架

分析代码时Kconfig和Makefile就是Linux kernel迷宫的地图。

3. usb core

drivers/usb/core/usb.c

3.1. 入口声明

subsystem_initcall(usb_init);

可以理解为module_init,只不过因为这部分代码比较核心,开发人员把它看作一个子系统,而不仅仅是一个模块。usb_init是真正的初始话函数。

module_exit(usb_exit);

usb_exit是整个USB子系统结束时的清理函数。

static int __init usb_init(void)

__init对于内核来说是一种暗示,表示这个函数仅在初始化期间使用,在模块被装载后,它占用的资源就会释放掉用于它处。

#define __init __attribute__((__section__(".init.text")))

__attribute__是GNU C扩展,指示编译器进行特定方面的优化和更仔细的代码检查。GNU支持十几个属性,section就是其中之一。

通常编译器将函数放在.text节,变量放在.data节或.bss节,使用section属性,可以让编译器将函数或变量放在指定的节中。
链接器将相同节的代码或数据安排在一起,__init修饰的所有代码都会被放在.init.text节中,初始化结束后就可以释放这部分内存。

#define subsys_initcall(fn) __define_initcall("4",fn,4)

__define_initcall用于将指定的函数指针fn放到initcall.init节中,subsys_initcall是把fn放到.initcall.init的子节.initcall4.init中。关于.initcall.init、.init.text和.initcall4.init,需要了解一些内核可执行文件相关的概念,说明如下:

内核可执行文件由许多链接在一起的对象文件组成,对象文件有许多节,如文本、数据、init数据、bss等。这些对象文件都是由一个称为链接器脚本的文件链接被装入的。这个链接器脚本的功能就是将输入对象文件的各节映射到输出文件中。
换句话说,它将所有输入对象的文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。
vmlinux.lds是存在于arch//目录中的内核链接器脚本,负责链接内核的各个节并将他们转入内存中特定的偏移量处。

__initcall_start = .;
.initcall.init : AT(ADDR(.initcall.init) -0xC0000000) {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;
  1. subsys_initcall将指定的函数指针放在.initcall4.init子节
  2. core_initcall将函数指针放在.initcall1.init子节
  3. device_initcall将函数指针放在.initcall6.init子节

各个子节中函数指针的调用顺序是确定的,__init修饰的初始化函数在内核初始化过程中调用的顺序和.initcall.init节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此,也决定了他们的调用顺序。

实际执行函数调用的地方,就在/init/main.c文件中,do_initcalls函数慧直接调用这里的__initcall_start、__initcall_end进行判断。

Created with Raphaël 2.1.2 start_kernel rest_init kthread-kernel_init do_basic_setup do_initcalls 遍历__initcall_start到__initcall_end,执行每个函数指针 End

3.2. usb_init概述

if(nousb) {
    pr_info("USB support disabled\n");
    return 0;
}

nousb标识:启动内核时通过内核参数去掉USB子系统。
pr_info只是一个打印信息的可变参数宏,即printk的变体,在include/linux/kernel.h中定义:

#define pr_info(fmt, arg...) \
    printk(KERN_INFO fmt, ##arg)

C99规定了可变参数宏

#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)

其中…表示可变参数,调用时替代__VA_ARGS__
GCC支持更复杂的形式,可以给可变参数取名字,例如:

#define debug(format, args...) fprintf(stderr, format, args)

pr_info定义中还有一个##,是为了处理特殊情况:没有可变参数,如果没有##,就会有一个多余的逗号,使用##使预处理器去掉这个多余的逗号。

这里简单说一下宏定义的特殊符号#和@
#表示将内容变为字符串,添加”“、##表示连接、#@表示将内容变为字符,添加”
##在变参宏__VA_ARGS__中表示去掉多余的逗号

之后就是usb_init最核心的工作了

  1. ksuspend_usb_init电源管理相关
  2. bus_register注册USB总线
  3. usb_host_init执行主机控制器相关初始化
  4. usb_major_init一个总线也是一个设备,必须单独注册,USB是通过快速串行通信来读写数据的,这里把它当作字符设备来注册
  5. usb_register usbfs相关
  6. usb_devio_init usbfs相关
  7. usbfs_init usbfs相关
  8. usb_hub_init Hub初始化
  9. usb_register_device_driver 注册USB设备驱动,这里是USB device driver而不是USB driver,一个设备可以有多个接口,每个接口对应不同的驱动程序,这里的device driver对应的是整个设备,而不是接口。

3.3. Linux设备模型

模型的中心就是:总线设备驱动,bus、device和driver,都有自己的专属结构,定义在include/linux/device.h中:

struct bus_type {
    const char      * name;
    struct module       * owner;

    struct kset     subsys;
    struct kset     drivers;
    struct kset     devices;
    struct klist        klist_devices;
    struct klist        klist_drivers;

    struct blocking_notifier_head bus_notifier;

    struct bus_attribute    * bus_attrs;
    struct device_attribute * dev_attrs;
    struct driver_attribute * drv_attrs;

    int     (*match)(struct device * dev, struct device_driver * drv);
    int     (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int     (*probe)(struct device * dev);
    int     (*remove)(struct device * dev);
    void        (*shutdown)(struct device * dev);

    int (*suspend)(struct device * dev, pm_message_t state);
    int (*suspend_late)(struct device * dev, pm_message_t state);
    int (*resume_early)(struct device * dev);
    int (*resume)(struct device * dev);

    unsigned int drivers_autoprobe:1;
};
struct device_driver {
    const char      * name;
    struct bus_type     * bus;

    struct kobject      kobj;
    struct klist        klist_devices;
    struct klist_node   knode_bus;

    struct module       * owner;
    const char      * mod_name; /* used for built-in modules */
    struct module_kobject   * mkobj;

    int (*probe)    (struct device * dev);
    int (*remove)   (struct device * dev);
    void    (*shutdown) (struct device * dev);
    int (*suspend)  (struct device * dev, pm_message_t state);
    int (*resume)   (struct device * dev);
};
struct device {
    struct klist        klist_children;
    struct klist_node   knode_parent;       /* node in sibling list */
    struct klist_node   knode_driver;
    struct klist_node   knode_bus;
    struct device       *parent;

    struct kobject kobj;
    char    bus_id[BUS_ID_SIZE];    /* position on parent bus */
    struct device_type  *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;

    struct semaphore    sem;    /* semaphore to synchronize calls to
                     * its driver.
                     */

    struct bus_type * bus;      /* type of bus device is on */
    struct device_driver *driver;   /* which driver has allocated this
                       device */
    void        *driver_data;   /* data private to the driver */
    void        *platform_data; /* Platform specific data, device
                       core doesn't touch it */
    struct dev_pm_info  power;

#ifdef CONFIG_NUMA
    int     numa_node;  /* NUMA node this device is close to */
#endif
    u64     *dma_mask;  /* dma mask (if dma'able device) */
    u64     coherent_dma_mask;/* Like dma_mask, but for
                         alloc_coherent mappings as
                         not all hardware supports
                         64 bit addresses for consistent
                         allocations such descriptors. */

    struct list_head    dma_pools;  /* dma pools (if dma'ble) */

    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
                         override */
    /* arch specific additions */
    struct dev_archdata archdata;

    spinlock_t      devres_lock;
    struct list_head    devres_head;

    /* class_device migration path */
    struct list_head    node;
    struct class        *class;
    dev_t           devt;       /* dev_t, creates the sysfs "dev" */
    struct attribute_group  **groups;   /* optional groups */

    void    (*release)(struct device * dev);
};

struct bus_type中有struct kset drivers和struct kset devices;
struct device中有struct bus_type *bus和struct device_driver *driver;
struct device_driver中有struct bus_type *bus和struct klist klist_devices;
相互知道彼此的存在。

这里面包含了Linux设备模型中最基本的元素:kobject和kset。
kobject是所有内核对象的积累,所实现的只是一些公共的接口,kset是同种类型kobject对象的集合,可以说是对象的容器。
这样内核使用kobject将各个对象连接起来组成了一个分层的结构体系,kobject包含了parent成员,kset使用链表来实现。

struct kset {
    struct kobj_type    *ktype;
    struct list_head    list;
    spinlock_t      list_lock;
    struct kobject      kobj;
    struct kset_uevent_ops  *uevent_ops;
};

struct kset_uevent_ops {
    int (*filter)(struct kset *kset, struct kobject *kobj);
    const char *(*name)(struct kset *kset, struct kobject *kobj);
    int (*uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);
};

struct kobj_type {
    void (*release)(struct kobject *);
    struct sysfs_ops    * sysfs_ops;
    struct attribute    ** default_attrs;
};

struct bus_type中drivers和devices表示了一条总线拥有的两条链表。

此外,klist包含了一个链表和一个自旋锁,暂且把它看成链表也可以。

struct klist {
    spinlock_t      k_lock;
    struct list_head    k_list;
    void            (*get)(struct klist_node *);
    void            (*put)(struct klist_node *);
};

struct list_head {
    struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD(xxx_list) ==>
    struct list_head xxx_list = {
        &(xxx_list),
        &(xxx_list)
    }

设备与驱动的匹配过程:

Created with Raphaël 2.1.2 新增驱动 创建struct device_driver 插入bus_type->drivers(kset类型)中,总线可以找到驱动 在bus_type->devices(kset类型)链表中寻找每个还没有绑定驱动的设备(device->driver(device_driver类型)指针为空) 匹配特征,如果支持则调用device_bind_driver函数 该函数,设置device->driver、添加到device_driver->klist_devices(klist类型)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值