标签: USB协议 UKey分析


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

目录

  • 目录
  • 1. USB概述
    • 1.1. 为什么要开发USB?
    • 1.2. USB子系统物理拓扑及功能抽象
    • 1.3. sysfs-USB
  • 2. drivers/usb代码内容说明
  • 3. usb core
    • 3.1. 入口声明
    • 3.2. usb_init概述
    • 3.3. Linux设备模型
    • 3.4. USB总线type
      • 3.4.1. bus_register(&usb_bus_type)
      • 3.4.2. kobject_uevent函数
      • 3.4.3. usb_bus_type的match函数Part1
    • 3.5. USB设备的四大部分
      • 3.5.1. USB接口
      • 3.5.2. USB端点
      • 3.5.3. USB设备
      • 3.5.4. USB配置
      • 3.5.5. 接口&设备驱动
      • 3.5.6. usb_bus_type的match函数Part2
    • 3.6. usb_init关键函数详细分析
      • 3.6.1. ksuspend_usb_init电源管理相关-TODO
      • 3.6.2. bus_register注册USB总线
      • 3.6.3. usb_host_init执行主机控制器相关初始化 (模块+struct class)
      • 3.6.4. usb_major_init总线设备注册(register_chrdev)
      • 3.6.5. usbfs相关-TODO
      • 3.6.6. usb_hub_init HUB初始化(usb_register->usb_register_driver->driver_register)
      • 3.6.7. usb_register_device_driver 注册USB设备驱动(driver_register)

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设备的通信,PC和USB主机控制器交互。Hub是对一个USB接口的扩展。现实中通常一个USB控制器和一个Hub集成在一起,该Hub称为Root Hub。

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

Compound Device就是将Hub和连在Hub上的设备封装在一起的所组成的设备。如下图所示。Composite Device是包含彼此独立的多个接口的设备。Compound Device的地址是独立的,Composite 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 thisdevice */void        *driver_data;   /* data private to the driver */void        *platform_data; /* Platform specific data, devicecore doesn't touch it */struct dev_pm_info  power;#ifdef CONFIG_NUMAint     numa_node;  /* NUMA node this device is close to */
#endifu64     *dma_mask;  /* dma mask (if dma'able device) */u64     coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */struct list_head    dma_pools;  /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride *//* 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

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

  1. python编程从入门到精通 叶维忠 pdf-零基础如何学习python?十本精品python书籍推荐...

    你想要学习python编程,却不知道该看哪本书?今天小编精选了学习python的十本精品书籍,并且还编写了推荐理由分享给你,希望可以给有选择困难症的同学一点帮助! 1.<"笨办法学&q ...

  2. usb接口驱动_乾坤合一~Linux设备驱动之USB主机和设备驱动

    这一章从主机侧角度看到的USB 主机控制器驱动和设备驱动从主机侧的角度而言,需要编写的USB 驱动程序包括主机控制器驱动和设备驱动两类,USB 主机控制器驱动程序控制插入其中的USB 设备,而USB ...

  3. linux判断usb进程命令,一种在Linux系统下审计USB设备历史使用情况的方法与流程...

    本发明涉及计算机审计技术领域,具体涉及一种在Linux系统下审计USB设备历史使用情况的方法. 背景技术: 如今,在linux系统中,对于USB设备的插入拔出事件,系统自身是不带有审计功能的,这使得普 ...

  4. linux usb 批量传输文件,一种Linux系统下提升usb批量传输速度的方法及系统与流程...

    本发明涉及通信传输技术领域,具体地说是一种linux系统下提升usb批量传输速度的方法及系统. 背景技术: linux系统访问usb设备有两种方式:编写内核驱动模块ko和在用户空间编写程序,通过内核提 ...

  5. SCI论文从入门到精通——IEEE论文那些事儿

    要保研了,但是没有科研成果镀身 评国奖了,但是没有像样的成果加分 要毕业了,但是论文还远远未达标      忧愁!   导师不管,师兄不济,纵有深造之心,却难为无米之炊 天赋异禀,才智超群,然无仙人指 ...

  6. 致Andorid初学者-从入门到精通,这一篇文带你省略学习路上的歪歪扭扭

    什么是安卓? 安卓系统即Android (Google公司开 发的操作系统).Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑. Android ...

  7. Linux视频教学从入门到精通-任亮-专题视频课程

    Linux视频教学从入门到精通-4106人已学习 课程介绍         不管你是Linux小白还是有linux基础,通过本课程学习都能让你掌握足够多的linux的实战经验,本课程从Linux安装开 ...

  8. linux从入门到精通脚本之家,Linux服务器从入门到精通52问_LINUX技术_操作系统_脚本之家.pdf...

    服务器从入门到精通 问 Linux 52 来源:互联网 作者:佚名 时间:11-05 15:08 :26 [大中小] 点评:本文为 服务器从入门到精通的 问 Linux 52 Q1 Linux的开发者 ...

  9. 《网络安全入门到精通》-1.2 - Linux系统 - firewalld防火墙iptables防火墙

    「作者简介」:CSDN top100.阿里云博客专家.华为云享专家.网络安全领域优质创作者 「订阅专栏」:此文章已录入专栏<网络安全入门到精通> Linux防火墙 Frewalld 1.常 ...

最新文章

  1. pip安装kolla-ansible时报错Cannot install ‘PyYAML‘的解决方法
  2. 口语学习Day5:今天聊聊美国路牌PED XING是什么?
  3. 【一起玩光剑】光剑第二期:新建GitHub上传OTA初始程序
  4. 牛客 - 「土」巨石滚滚(贪心)
  5. 863. 二叉树中所有距离为 K 的结点
  6. LeetCode 475. 供暖器(双指针二分查找)
  7. 目标检测——使用loss发现噪声数据
  8. 关于在CLASSWIZARD中找不到工作区间中的类的解决方法
  9. ArcGIS 起伏度、坡度、交通便利度数据生成
  10. 批量修改mac系统文件的可读写权限
  11. 郭克华老师java视频教程下载地址
  12. C语言常见面试题汇总
  13. WIFI计量插座之计量芯片选型
  14. 经济应用文写作【5】
  15. 科普:什么是IPV4?什么是IPV6?
  16. 【年度总结】回顾2021,展望2022,老杨来了
  17. subscript下标
  18. 新网站关键词优化小技巧
  19. K8S使用habor作为私有仓库
  20. 如何查看电脑jdk/jre版本以及安装路径

热门文章

  1. Java实现 LeetCode 124 二叉树中的最大路径和
  2. 【大咖专访】热爱美术的优质青年-李佳男
  3. matlab中采样函数,matlab采样相关函数用法详解
  4. Postman报错Unsupported Media Type
  5. http 502 和 504 的区别
  6. vue——Nprogress进度条功能实现
  7. Java 实现九九乘法表
  8. python搭建ip池(多线程)
  9. k8s服务注册与发现
  10. Standard Templete Library —— Containers