Linux usb core阅读

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

OHCI和UHCI支持的是usb1.1的控制器,支持的硬件范围不一样;EHCI支持的是usb2.0控制器的高速模式,它本身并不支持全速或者低速模式,硬件上通过TT电路来兼容

CPU不会直接和usb设备通信,而是通过主机控制器HC去控制所有usb设备的通信

usb总线是一种轮询方式的总线,所有的数据传输都必须由主机发起,HC初始化所有的数据传输,各种设备围绕再主机周围

usb通信是通过endpoint,主机和endpoint之间数据传输是通过pipe;usb主机到设备是OUT端点,设备到主机是IN端点;一个usb逻辑设备就是一群端点集合,它与主机之间的通信发生在主机上的一个缓冲区和设备上的一个端点之间,通过管道来传输数据;意思就是管道的一端是主机上的一个缓冲区,一端是设备上的端点; 一个设备可以包括多个接口,一个接口可以有多个端点

pipe通信方式有两种:stream、message;message要求数据有一定格式,stream没有特定格式要求
端点四种类型:
控制传输:配置设备、获取设备信息、发送命令到设备、发送控制信息的,每个usb设备都有EP0控制端点
中断传输:固定速率传输少量数据
批量传输:传输大量数据,保证数据没有丢失
等时传输:传输大量数据,不能保证数据是否送达,以稳定速率发送和接收实时的信息,对传输延迟比较敏感

从usb_init开始

ubsys_initcall(usb_init):
函数开头__init标记,表明这个函数仅在初始化期间使用,在模块装载之后它占用的资源就会释放
#define __init attribute ((section (“.init.text”)))
__arrtibute__标记,它被用来声明一些特殊的属性,这些属性主要用来指示编译器进行特定方面的优化和更仔细的代码检查
__section属性标记,可以让编译器将函数或变量放在指定的节中;通常编译器将函数放在.txt节中,变量放在.data或者.bss节中
那init是如何被调用的:
#define subsys_initcall(fn) __define_initcall(“4”,fn,4) :将指定的函数指针fn放在 .initcall4.init 节中
在vmlinux.lds文件中,搜索initcall.init :
__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) – 0xC0000000) {
*(.initcall1.init)27
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;
subsys_initcall宏便是将指定的函数指针放在了 .initcall4.init 子节,其他比如core_initcall 将函数指针放在 .initcall1.init 子节等等;调用时是顺序调用的,
最初实际的执行函数调用地方是在init/main.c文件中,内核初始化,do_initcalls函数会直接用到 __initcall_start、__initcall_end
nousb:
一个标志,在启动内核时通过内核参数cmdline去掉usb子系统的

设备模型:

总线、设备、驱动、kset、kobject:
总线、设备、驱动是一个个的对象; kobject是它们的基类,实现一些公共接口;kset是同类型的kobject对象的集合,对象的容器;
bus总线有两条链表:devices设备链表和drivers驱动链表
有新设备或者新驱动注册加载时,就把它们分别加入到链表中,这样就可以通过bus总线找到device和driver
新加一个设备或者驱动,就会去驱动或者设备链表中寻找自己合适的另一半,找到了就device_bind_driver绑定
struct usb_bus_type:
const struct bus_type usb_bus_type = {
.name = “usb”,
.match = usb_device_match,
.uevent = usb_uevent,
.need_parent_lock = true,
};
name就是usb总线的名字
match函数指针就是设备和驱动直接的匹配函数,bus上有新设备或者新驱动添加时,这函数总会被调用

接口:

设备可以有多个接口,每个接口代表一个功能和驱动
linux设备模型的device在usb子系统中,成了两个结构:
struct usb_device
struct usb_interface
内核已经被分配的主设备号在Documentation/devices.txt 文件里,使用动态分配的话,则不需要关心
usb_init函数中的usb_major_init函数已经使用USB_MAHOR注册了一个字符设备,名字叫usb,cat /proc/devices可以看到
/proc/devices文件中显示了所有当前系统已经分配的主设备号,
condition字段表示接口和驱动的绑定状态

设置:

struct usb_host_interface:
一个接口可以有多个设置,使用不同的设置,描述接口的信息会有所不同,所以接口描述符没有放在usb_interface中,而是放在了表示接口设置的usb_host_interface中
attribute((packed)):告诉编译器,这数据结构的元素都是1字节对齐的,不要再添加填充位了
bInterfaceClass:usb协议规定,每个device或者interface属于一个class,class下面又分了subclass

端点:

struct usb_host_endpoint:
0号端点依旧特殊,它没有自己的端点描述符
bLength:描述符的字节长度
bDescriptoType:描述符类型
bEndpointAddress :端点的方向,端点的地址,端点的端点号
bmAttributes:属性,bit0和bit1表示传输类型,00控制,01等时,10批量,11中断
wMaxPacketSize:端点一次可以处理的最大字节数
bInterval:usb是轮询的总线,这个值是希望主机轮询自己的时间间隔
urb_list:端点要处理的urb队列

设备

struct usb_device
devnum:设备的地址,它是USB设备再一条usb总线上的编号,在设备插入之后在hub那里使用choose_address来给设备选择一个地址
硬件上:一个host控制器就会连出一条usb总线
软件上:不管有多少个HC,或者多少条总线,它们都属于usb_bus_type这个类型,它在usb_init时就注册了,只是每一个总线对应一个usb_bus结构体变量,这个变量在HC的驱动中申请
bMaxPacketSize0 :端点0的特性,表示可发送和接受的包的最大值,端点0只能是8、16、32、64,
bcdDevice:设备的版本号
product、manufacturer、serial:产品、厂商、序列号
字符串描述符:
usb_cache_string——》usb_string ——》usb_string_sub ——》usb_get_string ——》usb_control_msg

配置:

bmAttritures:bit6为1表示self-powered,bit5为1表示这个配置支持远程唤醒
usb_device_match
struct device_type usb_device_type:设备的类型
struct bus_type usb_bus_type:总线的类型
struct usb_device_driver:usb设备驱动
struct usb_driver:usb的接口驱动
usb_device_driver:一个usb接口对应一个usb驱动,对应一个对立的功能,但是接口是作为一个usb设备而存在的,一个usb设备可以有不同的配置,还可以为设备指定特定的配置,这个和整个usb设备进行对等交流的就由usb_device_driver来完成,即usb设备驱动
dynids动态 id :/sys/bus/usb/drivers目录下面,每一个目录代表一个usb驱动,里面有new_id,将厂商和产品 id 写进去就可以动态加载驱动: echo 0557 2008 > /sys/bus/usb/drivers/foo_driver/new_id ,写到foo_driver驱动的设备 id table 表中

设备的生命线:

将usb设备连接在hub的某个端口上,hub检测到有设备连接了进来,它会给设备分配一个struct usb_device结构的对象并初始化,调用设备模型提供的接口将设备添加到usb总线的设备列表中,然后usb总线会遍历驱动列表中的每个驱动,调用自己的match函数看它们是否和这设备或者接口匹配,又回到了match函数

hub检测到自己的某个端口有设备连接了进来后,它会调用usb_alloc_dev函数为usb_device结构的对象申请内存,

1、usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)

parent是设备连接的那个hub,bus是设备连接的那条总线,port1是设备连接在hub上的那个端口
如果一条分支可能性比较大,可以使用 likely ,可能性非常小,就使用 unlikely
struct usb_device结构体成员:
在这里插入图片描述
设备处于powered状态了,要想到下一个default状态,必须收到一个复位信号,所以hub接下来就需要复位设备,然后获得speed等,再进入address状态,这个address就是上面的devnum

2、usb_control_msg发送SET_ADRESS标准请求来给设备Address:

主要就是创建一个控制 urb ,赋好值并把它发送给usb core中的接口设备,然后找到合适的HC,从而进行数据传输
Token PID类型包:Token令牌包、SETUP事务包、Status状态包、Data数据包、Handshake握手包
控制传输:需要SETUP、 DATA、 STATUS 这三个 transaction 阶段

3、usb_internal_control_msg:

一个中心三个基本点:以一个struct urb结构体为中心; 以usb_alloc_urb、usb_fill_control_urb、usb_start_wait_urb三个函数为基本点
struct urb:usb request block
usb_alloc_urb:创建一个urb,struct urb只能由它来创建
usb_fill_control_urb:初始化一个控制urb
usb_start_wait_urb:将urb提交给usb core,分配给特定的HC驱动进行处理,等待处理结果或者超时
struct urb里面有个 kref 计数,它给urb计数,urb最终要端点给到HC驱动,多一个这个urb的使用者,计数就加1,usb_init_urb、usb_get_urb、usb_free_urb
一个完整的urb通信:驱动发现自己有和usb设备通信的需要(usb请求),于是创建一个urb,并指定它的目的地是设备上的哪个端点,然后提交给usb core,usb core填充美化之后交个HC驱动HCD,HCD去解析这个urb,了解这个urb的目的,并与usb设备通信,urb目的达到之后,HCD再把urb所有权返回最开始的驱动程序
usb_unlink_urb:urb的异步停止方式(不等待)
usb_kill_urb:urb的同步停止方式(等待)

4、urb_list:(一个中心)

每个端点都有一个urb队列,HCD每收到一个urb,就会将它添加到这个urb指定的那个端点的urb队列中urb_list
pipe:端点之间通过管道通信,管道两端,一端是主机上的缓冲区,一端是设备上的端点;transfer_buffer, transfer_dma, transfer_buffer_length 来描述主机上的缓冲区;确定创建一条管道,要知道两端的地址、方向和类型;
URB_SHORT_NOT_OK :表示如果从IN端点收到的一个比较短的DATA数据包,比wMaxPacketSize小,就认为URB是错误的
URB_ZERO_PACKET :表示批量传输的OUT传输必须使用一个short packet来结束;bulk传输数据最后一包小于wMaxPacketSize时,不用额外发0长度包,但是等于时,需要发一个0长度包来结束传输
URB_ISO_ASAP :HCD不忙时开始,不指定开始的帧号,进行等时传输时设置
setup_packet, setup_dma :控制传输要使用的两个缓冲区
interval (等时和中断使用)、context、complate:都是等时传输来使用,等时传输可以指定传输多少个packet,nymber_of_packets,指一个urb里指定了多次的等时传输

5、usb_alloc_urb:usb_fill_control_urb :usb_start_wait_urb :(三个基本点)

usb_alloc_urb(int iso_packets, gfp_t mem_flags)
第一个参数:urb结构体中的那个变长数组,对于控制、中断、批量传输,这个参数为0,对于等时传输,它应该为number_of_packets
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)
usb_fill_bulk_urb 、usb_fill_int_urb
usb_start_wait_urb(struct urb *urb, int timeout, int *actual_length) :
completion是内核一个简单的同步机制,一个线程可以通过它来通知另一个线程某件事情完成了
两种方式:
1、通过DECLARE_COMPLETION宏在编译时就创建好struct completion结构体
2、运行时创建struct completion,然后init_completion初始化
通知完成的函数:
void complete(struct completion *c); 通知一个等候的线程
void complete_all(struct completion *c); 通知所有等候的线程
void wait_for_completion(struct completion *c); 等待通知
unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout); 等待一定时间直到通知或者超时
void usb_api_blocking_completion(struct urb *urb); 通知done结束传输
usb_start_wait_urb函数将urb提交给usb core处理,停在wait_for_completion_timeout等待usb core和HCD的处理结果,这个urb传输完成之后调用结束处理函数usb_api_blocking_completion,从而调用complate来通知usb_start_wait_urb不用再等了,传输完成了;还有一情况就是超过了时间还没等到通知,也不用等了,继续向下走

6、usb_submit_urb:

对urb做前期处理后给HCD
在设备进入Configured状态之前,主机只能使用控制传输,通过缺省管道0来和设备交流

7、HCD:

struct usb_hcd
devmap:devicemap一共128位,busmap:一共64位,最多可以有64条总线
uses_dma:表明这主机控制器支不支持DMA,不支持的话使用PIO方式
devnum_next:表中记录的下一个为0的位,为1的表示已经有设备占据了
bandwidth_allocated:表明总线为中断传输和等时传输预留了多少带宽,协议里说了,对于高速来说,最多可以有 80%,对于低速和全速要多点儿,可以达到 90%。它的单位是微妙,表示一帧或微帧内有多少微妙可以留给中断/等时传输用
*pool [HCD_BUFFER_POOLS],几个 dma 池:hcd_buffer_creat、dma_poll_creat、dma_poll_alloc、hcd_buffer_alloc、hcd_buffer_free

8、usb_hcd_submit_urb:

将提交过来的urb指派给合适的主机控制器驱动
status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags); :将urb给具体的HC驱动
Address:
在这里插入图片描述

9、Configured:

要去获得设备的描述符,然后再去选择配置
usb_get_device_descriptor:通过usb_get_descriptor获得设备描述符,然后给到设备struct usb_device结构体中的descriptor成员中
usb_get_descriptor:调用了usb_control_msg函数发送请求,获得设备描述符
调用usb_get_configuration函数来获得各个配置的配置描述符
得到raw原始的描述符之后,需要将接口、设备、端点、配置等描述符给分开,所以有了usb_parse_configuration函数来完成
usb_parse_configuration:usb_parse_interface、usb_parse_endpoint
设备各个配置信息得到之后,还没进入Configured状态,还要有选择的去配置设备,接下来设备就要去设备模型中找属于自己的驱动了

驱动的生命线

设备的生命线可以认为是从usb设备连接到hub的某个端口时开始,驱动的生命线必须回溯到usb子系统初始化函数usb_init那里

1、usb_generic_driver:

usb设备是struct usb_device结构体出来的,设备类型usb_device_type,总线类型usb_bus_type
linux设备模型根据bus总线类型将设备添加到链表,然后去轮询usb总线的驱动链表,调用usb_device_match函数去匹配;
match函数根据usb_device_type安排设备走设备那条路,从而匹配到那个对所有usb_device_type类型的设备都友好的,usb世界里唯一的那个usb设备驱动(不是usb接口驱动)struct device_driver结构体对象usb_generic_driver
usb_register_device_driver :for_device这里初始化为1, match那个is_usb_device_driver才可以走下去
然后充实了usb_genric_driver里嵌入的struct device_driver结构体
调用usb_probe_device:调用usb_generic_driver的probe函数generic_probe:从设备可能的众多配置中选择一个合适的,然后去配置设备,从而让设备进入Configured状态
调用choose_congiguration:找到合适的配置,返回配置的bConfigurationValue
配置设备,usb_set_configuration:Address阶段到Configured,

2、usb_device_match:

owen:THIS_MODULE:一个struct module * 类型的结构体指针,代表当前模块,每一个都代表了一个内核模块,
前面说for_device是1,走了设备那条路,现在走接口那条路,接口驱动的for_device在usb_register_driver那里被初始化为0,
to_usb_interface和to_usb_driver:用于接口和接口驱动
usb_match_id和usb_match_dynamic_id:实际的匹配工作,一个从id_table中找,一个是动态id链表中找

低功耗蓝牙项目,需要一块懂省电的板

思澈 SF32LB52 芯片,BLE 协议栈深度优化,上手即开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值