操作系统学习笔记(三) 进程和线程的调度

对于多道程序设计的系统,就会有多个进程或者线程在同时竞争CPU。对于单核系统,调度问题,就是选择下一个要运行的进程或者线程是哪一个。

线程的调度与进程类似,对于按内核级别的调度,与线程所属的进程基本没有关系。

进程切换的代价是比较大的,包括用户态到内核态的切换、保存当前进程的状态、内存映像的改变、调度程序以及载入新进程的状态;另外,会导致高速缓存的失效。

调度程序要考虑的要素:

(1)进程是CPU密集型还是I/O密集型。I/O密集型可能会需要尽快运行,并且很快就阻塞。CPU密集型可能会长期占用CPU。

(2)何时调度。创建进程、进程退出、进程阻塞以及I/O中断发生。

(3)是否支持抢占。支持抢占的调度,可以不必等待当前进程运行完或阻塞,直接上CPU运行。

一、调度算法的分类。

不同的环境需要不同的调度算法,也有不同的考量指标:

(1)批处理。批处理系统下,不需要特别快的响应速度,所以可以考虑非抢占以及每个进程都有长时间的抢占算法。指标主要是:吞吐量、周转时间以及CPU利用率。

(2)交互式。交互式系统下,抢占是必须的,因为操作者会希望比较快的得到响应。考虑的指标主要是最小响应时间、均衡性。

(3)实时。实时系统下,最重要的是满足截止时间要求,即在一定的时间内完成任务。

二、批处理系统中的调度

1、先来先服务(first-come-first-serve)。最简单的方式,非抢占式的先来先服务,从字面意思就可以看出,把任务作为一个队列,先来先服务。缺点是,无法体现任务的轻重缓急,达不到理想的指标性能。

2、最短作业优先。非抢占式下,即当一个任务完成时,从任务队列中挑选最短的一个作业执行。相对于先来先服务,提高了一些性能。运行时间必须提前掌握。

3、最短剩余时间优先。即最短作业优先的抢占版本。调度程序总是选择剩余运行时间最短的作业执行。每当一个新的作业到达,如果运行时间比当前进程的剩余运行时间短,就挂起当前进程并切换到新的进程。

三、交互式系统中的调度

1、轮转调度。最简单且最公平的方法,给每个进程分配一个时间片。时间片耗尽时,进程会下CPU并加入到就绪队列的末尾。问题的关键是选择合适的时间片。

2、优先级调度。进程有轻重缓急,于是给进程设置不同的优先级,每次调度优先级最高的进程运行。当然,这样可能会导致低优先级进程饥饿。解决方案是,实行奖惩机制,高优先级进程耗尽时间片时会降低它的优先级。

3、多级队列。给不同优先级的进程队列,设置不同单位的时间片。同时,也实行奖惩机制,耗尽时间片会改变进程的队列级别。

四、线程的调度

线程的调度,取决于支持的是内核级线程还是用户级线程。

对于用户级线程,内核不知道线程的存在,就给了进程很大的自主权。内核只是调度进程,进程中的调度程序选择哪个线程来运行。

对于内核级线程,线程的调度就交给了系统完成。

五、Linux中的进程与线程调度

首先明确一个概念,Linux系统中甚至没有真正的线程。不过,可以认为Linux是系统的线程是内核线程,所以调度是基于线程的。

一个进程由于其运行空间的不同, 从而有内核线程和用户进程的区分, 内核线程运行在内核空间, 之所以称之为线程是因为它没有虚拟地址空间, 只能访问内核的代码和数据, 而用户进程则运行在用户空间, 但是可以通过中断, 系统调用等方式从用户态陷入内核态。

用户进程运行在用户空间上, 而一些通过共享资源实现的一组进程我们称之为线程组, Linux下内核其实本质上没有线程的概念, Linux下线程其实上是与其他进程共享某些资源的进程而已。但是我们习惯上还是称他们为线程或者轻量级进程。

因此, Linux上进程分3种,内核线程(或者叫核心进程)、用户进程、用户线程, 当然如果更严谨的,也可以认为用户进程和用户线程都是用户进程。

Linux中,进程和线程都被维护为一个task_struct结构,线程和进程被同等对待来进行调度。

Linux将线程区分为3类:

(1)实时先入先出。

(2)实时轮转。

(3)分时。

实时先入先出有最高的优先级,不会被其他线程抢占,除非是另外一个刚刚准备好的且优先级更高的实时先入先出线程。

实时轮转线程与实时先入先出类似,不过有一个轮转体系,即分配一个时间片,时间到了就可以被抢占。时间片消耗完就进入实时轮转线程列表的末尾。其实,这两种都不是真的实时,因为执行的最后期限无法确定,只是比分时线程有更高的优先级。

实时线程的优先级从0-99,0是实时线程的最高优先级,99是实时线程的最低优先级。

传统的非实时线程,优先级从100-139。Linux系统根据非实时线程的优先级分配时间量。

Linux使用一个重要的结构,调度队列。每个CPU有自己的调度队列,包括两个数组:活动的和过期失效的。每个数组包括了140个链表头,对应140个优先级的链表。

调度器从正在活动数组中选择一个优先级最高的任务,如果时间片耗尽失效,就加入到过期失效数组中。如果进程在时间片内被阻塞,那么在时间片失效之前,等待的事件发生就可以继续运行,放回到正在活动的数组中。如果活动数组没有任务了,调度器交换指针,使得活动数组和失效数组调换。

不同的优先级被赋予不同的时间片长度,优先级越高的进程,时间片越长。

Linux采用静态优先级与动态优先级结合的方式。Linux采用了奖惩机制,目的在于奖励互动进程以及惩罚占用CPU的进程。一个进程初始被赋予了一个优先级,耗尽和阻塞会改变nice值,活动与过期进程转换时,动态改变进程优先级。

另外,对于多核处理器,运行队列数据结构与某一个处理器相对应,调度器尽量进行亲和调度,即将之前在某个处理器上运行过的任务再次调入该处理器。

调度器只考虑可以运行的任务,不可运行的任务和正在等待各种I/O操作的或内核事件的任务被放入等待队列中。每一种等待某种事件的任务组成一个等待队列。等待队列的头部包含一个指向任务链表的指针及一个自旋锁。

操作系统学习笔记(二) 信号量、条件变量、互斥量、读写锁

在有了进程和线程的模型之后,一个很大的问题就摆在眼前:进程和线程的执行顺序是不可预知的,那么,如何使得两个进程按照我们想要的顺序执行,从而得出正确的结果呢?

竞争条件:两个或者多个进程读写某些共享数据,最后的结果依赖于进程运行的精确时序。

临界区:把对共享内存进行访问的程序片段称作临界区。如果能使两个进程不可能同时处于临界区内,就能够避免竞争。

先引入一个经典的进程同步问题:生产者-消费者问题。

生产者-消费者问题:有一个缓冲区,一个(或多个)进程在生产某种产品,它生产的东西会放入缓冲区内;一个(或多个)进程在消费产品,它会从缓冲区内取走产品。当缓冲区满时,生产者应当暂时停止生产;当缓冲区为空时,消费者应当暂时停止消费。

很显然,这个问题用简单的判断缓冲区是否为0或N是无法解决的。如果在消费者判断缓冲区为0时,恰好遇到了进程切换,生产者进程开始运行,此时应当唤醒消费者,然而这个信号丢失了,因为切换到消费者才进行了睡眠。这时,生产者会不断运行,直到缓冲区满,两个进程全部睡眠,造成了死锁。代码如下:

复制代码

#define N 1000
int count=0;
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
if(count==N) sleep();//一段时间后,缓冲区满,生产者进程也睡眠了
insert_item(item);
count=count+1;
if(count==1) wakeup(consumer);//设想判断条件成立时,切换了进程,再次切回时,唤醒消费者进程,然而消费者进程此时没有睡眠,信号丢失
}
}

void consumer(void)
{
int item;
while(TRUE)
{
if(count==0) sleep();//第一次count=1,消费者进程不会睡眠;第二次确实睡眠了
item=remove_item();
count=count-1;//此时缓冲区确实为空了
if(count==N-1) wakeup(producer);
consume_item(item);
}
}
复制代码

一、信号量

信号量是一种数据结构,可以理解为一个用来计数的整数和一个队列。整数用来记录唤醒次数,而队列被用来记录因为该信号量而阻塞的进程。

信号量只支持两种操作:P/V操作。

P操作,可以理解为测试并减一。P(signal1),如果signal1大于0,那么把它减一,进程继续执行;如果signal为0,那么执行P操作的进程将会被阻塞,从而变为阻塞态,添加到因为signal1信号而阻塞的进程队列中。

V操作,可以理解为+1并唤醒。V(signal1)后,如果signal1本来就大于0,那么执行+1;如果有进程在该信号量上被阻塞,那么从队列中根据某种策略选择一个进程唤醒。如果多个进程在该信号量上阻塞,那么V操作后,signal1仍然可能为负数。

需要注意的是,P/V操作均应当是原子操作,即作为一个整体执行而不会被打断。

有了信号量,我们再来看生产者-消费者问题:

复制代码

#define N 1000
typedef int semaphore;
semaphore mutex=1;//控制对临界区的访问,其实就是互斥量
semaphore empty=N;//表示空槽的数量
semaphore full=0;//填满的槽的数量
int count=0;
void producer(void)
{
int item;
while(TRUE)
{
item=produce_item();
down(&empty);
down(&mutex);//要改变共享区(缓冲区),加锁
insert_item(item);
up(&mutex);//解锁
up(&full);
}
}

void consumer(void)
{
int item;
while(TRUE)
{
down(&full);
down(&mutex);
item=remove_item();
up(&mutex);
up(&empty);
consume_item(item);
}
}
复制代码
有了信号量,这个问题就好解决多了:用信号量full、empty来表示已用和未用的数量,这样不管是满了还是空了,都不会造成死锁的问题。mutex的操作就是我们接下来要介绍的互斥锁。

二、互斥锁

互斥量其实可以理解为一个简化的信号量,它只有两种状态:0和1。互斥锁是用来解决进程(线程)互斥问题的。所谓进程互斥,就是两个进程实际上是一种互斥的关系,两者不能同时访问共享资源。

互斥量和信号量原理比较类似,一旦一个线程获得了锁,那么其它线程就无法访问共享资源,从而被阻塞,直到该线程交还出了锁的所有权,另外一个线程才能获得锁。

互斥锁的例子就不再给出,上面程序中已经有了,下面的程序中也会出现。

三、条件变量

条件变量是另外一种同步机制,可以用于线程和管程中的进程互斥。通常与互斥量一起使用。

条件变量允许线程由于一些暂时没有达到的条件而阻塞。通常,等待另一个线程完成该线程所需要的条件。条件达到时,另外一个线程发送一个信号,唤醒该线程。

条件变量对应的一组操作是pthread_cond_wait和pthread_cond_signal。

条件变量与互斥量一起使用,一般情况是:一个线程锁住一个互斥量,然后当它不能获得它期待的结果时,等待一个条件变量;最后另外一个线程向它发送信号,使得它可以继续执行。

需要注意的是,pthread_cond_wait会暂时解开持有的互斥锁。

四、读写锁

读写锁相对上面的问题会复杂一些,它被用来解决一个经典的问题:读者-写者问题。

读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。

下面的代码考虑的是读者优先的读者-写者问题,对于共享区域的读写规则如下:

1.只要有一个读者在读,后来的读者可以进入共享区直接读。

2.只要有一个读者在读,写者就必须阻塞,直到最后一个读者离开。

3.不考虑抢占式,写者在写时,即使有读者到达,也会在就绪态等待。

复制代码
typedef int semaphore;
semaphore mutex=1; //互斥锁,控制对rc的访问
semaphore db=1; //控制对数据库的访问
int rc=0; //当前读者计数

void reader(void)
{
while(TRUE)
{
down(&mutex);//加锁
rc=rc+1;
if(rc==1) down(&db);//第一个读者,加锁
up(&mutex);
read_data_base();
down(&mutex);
rc=rc-1;
if(rc==0) up(&db);//最后一个读者离开,解锁
up(&mutex);
use_data_read();
}
}

void writer(void)
{
while(TRUE)
{
think_up_data();
down(&db);//获取数据库访问的锁
write_data_base();
up(&db);
}
}
复制代码

这里,我们其实用了两个互斥锁来实现了读写锁。一个互斥锁用来保护共享区,另外一个互斥锁用来保护读者计数器。

读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。

读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。

读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。

五、总结

这里,主要是简单总结一下这几种同步量的用法。

1、互斥锁只用在同一个线程中,用来给一个需要对临界区进行读写的操作加锁。

2、信号量与互斥量不同的地方在于,信号量一般用在多个进程或者线程中,分别执行P/V操作。

3、条件变量一般和互斥锁同时使用,或者用在管程中。

4、互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。

5、互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性。

参考书籍:《现代操作系统》

操作系统学习笔记(一) 进程与线程模型

进程可以说是操作系统最为核心的一个抽象,而线程可以认为是一种轻量级的进程,或者说一个进程内的多个迷你进程。

一、进程的模型

进程(process):进程是一个正在运行的程序的实例。对于一个单核的处理器,每个时刻只能运行一个程序,但在每一个时间段,它可能运行多个进程,这样就产生了并行的错觉。

从概念上说,每个进程拥有自己的虚拟CPU,好像它独占了CPU的使用权一样,虽然实际的CPU是不断切换的。进程的另外一大特性,是独立的虚拟地址空间。

进程的创建

有四种事件导致进程的创建:
(1)系统初始化。系统初始化会创建许多进程,如windows刚开机的时候。

(2)执行了正在运行的进程所调用的系统调用。如程序运行了一个fork()调用。

(3)用户请求创建一个进程。如命令行中输入./a.out。

(4)一个批处理作业的初始化。

进程的终止

进程是正在运行的程序的实例,一个程序是会运行完的,所以进程也有终止的时候:

(1)正常退出(自愿)。如程序正常结束。

(2)出错退出(自愿)。如编写一个程序,当错误时调用exit(num)。

(3)严重错误(非自愿)。

(4)被其他进程杀死(非自愿)。如另外一个进程调用了kill(pid)。

进程的层次结构

不同操作系统有不同的概念。Linux系统中区分父进程与子进程,windows系统则不区分。

进程的状态

进程有三种主要状态:

(1)运行态:正在CPU上运行的进程。

(2)就绪态:已经就绪但是还没有被调度程序选中。

(3)阻塞态:因为某种原因(如等待I/O完成)暂时无法执行,需要等待外部事件。

下面有一幅图画出了可能的转换关系。需要注意的是,阻塞态的进程必须先进入就绪态,等待处理器的调度。

就绪态<------->运行态----->阻塞态
      <----------------------

进程的实现

进程的实现,相对于线程来说是比较确定的。操作系统内核维护一个进程表,也称为进程控制块(PCB)。进程表项为一个进程启动的必要信息,包括进程管理(寄存器,PC,PSW,调度信息,打开文件的状态等)、存储管理(代码段、数据段、堆栈段指针等)、文件管理(目录、PID等)信息。

当进程从运行态进入其他状态时,PCB保存着启动它的所有信息,当该进程再次被调度程序选中时,就要恢复这些信息。保存的步骤,包括:硬件压入PC等,把中断向量装入新的PC,然后通过汇编语言保存寄存器并设置新的堆栈,运行中断服务程序(通常为C),再通过调度程序选中一个进程,把它的运行信息载入寄存器以及PC等。

二、线程的模型

线程的使用,是因为进程创建、撤销、切换的代价很大,并且需要共享内存空间和数据,以及提高运行速度。一个例子是Web服务器,它如果采用进程的方式,那么一个页面请求被响应时,进程就进入了阻塞态,从而无法提供其他服务。而采用线程,可以使用一个线程接受请求,然后把请求分派给工作线程,实现同时响应,并且共享内存。

线程模型基于两个概念:资源分组处理与执行。线程的目的,是共享资源,并共同完成一个任务。

线程与进程非常类似,不过比进程轻量,原因在于:进程拥有独立的虚拟地址空间,而同一个进程中的线程,共享内存空间与资源。因此,线程只需要保存PC、寄存器、堆栈等,无需保存大量的文件、进程管理信息。线程之间也是没有保护并且平等的,因为它们共享了同样的资源,包括打开的文件、子进程等。

POSIX(portable operating system interface of UNIX)规定了UNIX系统的通用线程包pthread。

线程的实现

线程的实现,总的来说有两种:在用户空间中实现线程,以及在内核中实现线程。

用户空间中实现线程

这种情况下,内核是不知道线程存在的,调度单位是进程。因此,为了体现多线程的作用,就必须考虑阻塞的问题。

因为用户管理线程,所以每个进程都要有线程表,与进程表类似,不过记录的内容比较少。

用户级线程的优点有很多:

(1)可以在不支持线程的系统上运行。

(2)速度非常快,因为它不需要陷入内核,不需要上下文切换,也不需要cache刷新。

(3)可以允许每个进程定制自己的线程调度算法。

当然,因为内核不知道线程的存在,需要解决阻塞的问题,可以通过非阻塞系统调用,或者检查调用是否会阻塞(如select,包装器)。

另外,因为内核不知道线程的存在,进程内部没有时钟中断,所以线程必须主动让出CPU,不然其他线程无法抢占。

内核中实现线程

内核中实现线程,即在内核中维护一个线程表,而进程不维护。与用户空间中实现相比,优缺点基本是相反的。

内核中的实现,自然不需要非阻塞I/O,但是代价就是,线程的操作花销很大。

Android4.2移植rtl8192cu驱动要点

我的板子上的android系统是4.2版本的,由于板子上没有wifi模块,所以买了一个usb接口的wifi模块,现在要将驱动以及wifi sdk移植到android系统中。

usb wifi模块上搭载了realtek8188cu芯片,rtl8192cu的驱动兼容rtl8188cus,相关驱动文件可以在realtek的官网上下载到。

官方提供的文件包里有比较详细的教程,且网上有很多关于驱动移植的详细步骤,这些我就不再赘述了,这里记录下我在移植过程中碰到的种种问题:

一、驱动移植篇

官方提供了详细的文档供我们移植的时候参考,但是要注意的是,在driver/include/目录下有一个autoconf.h文件,这个文件保存了整个驱动的相关配置信息。在文档中有提到一个autoconf_rtl8192c_usb_linux.h文件,这个文件时官方提供给你参考用的,可以根据这个文件里的内容区配置autoconf.h里的内容,但是我在下载的文件包里没有找到这个文件,导致之后调试驱动的时候出了不少问题。     

Android4.2支持station与p2p共存,所以在加载驱动的时候会同时创建wlan0与p2p0两个设备节点。其加载命令为:

insmod wlan.ko ifname=wlan0 if2name=p2p0
这条命令在android系统中的hardware层中在开启wifi功能的时候被自动执行。在没有修改autoconf.h之前,在加载驱动的时候只能认第一个参数,而第二个参数驱动死活不认,搞得只能创建wlan0这个设备。若要使驱动支持android系统的这个特性,则要修改autoconf.h:

-//#define CONFIG_CONCURRENT_MODE 1
+#define CONFIG_CONCURRENT_MODE
将被屏蔽的这个选项选中,这样驱动在加载的时候同时创建wlan0和p2p0设备节点。

驱动编译成模块即可,然后将ko文件放到目标板中的system/lib/modules中,这样在使能wifi功能的时候,android底层会自动调用驱动。

二、WIFI SDK移植篇

 官方文件包里提供了4.0,4.1,4.2等几个版本的SDK packages给我们,每个packages里都配有说明文档,移植起来并不是很困难。

但!是!

移植完毕后的所出现的问题就会相当得麻烦(对于像我这种新手来说)。

1.android系统的wifi工作流程

先将这个流程整理一下,分析问题的时候就会简单得多。

当我们使能wifi功能时,wifi framework层便会去调用相关JNI接口去加载驱动和wpa_supplicant,JNI接口的具体实现是由hardware层中的相关文件实现的(需要移植的部分)。按照realtek的文件来说,就是hardware/realtek/wlan/libhardware_legacy/wifi/wifi_realtek.c文件。

wifi_realtek.c文件负责加载wifi驱动和wap_supplicant,若有一星半点问题都会导致wifi无法开启。

以下是我的调试过程中遇到的问题与解决方法:

①Cannot load driver!(*)

这里是framework层报的错误,跟踪下来就发现是调用了wifi_realtek.c文件中的intwifi_load_driver()函数,若是使用模块方式加载驱动,wifi_load_driver会去调用int load_driver()函数。驱动加载很简单,出现这种问题一般是忘记将ko文件放到指定目录,或者驱动已被加载所导致的。

②I/wpa_supplicant(2254): rfkill: Cannot open RFKILL control device

出现这个问题原因是无法打开dev/rfkill设备节点,修改其权限即可:

chmod 0777 /dev/rfkill

③wifi_start_supplicantget wifi_ifname(SECONDARY) fail

出现这个报错是因为没有 创建p2p0设备节点,导致

insmod wlan.ko ifname=wlan0 if2name=p2p0

命令执行不成功,修改内核即可解决问题

④E/WifiStateMachine(1783): Failed to start supplicant!

这是framework层中的报错,是由于wap_supplicant没有正确开启所导致的。主要还是在wifi_realtek.c中的int wifi_start_supplicant(int p2p_supported)中找问题。

将中的

wifi_stop_supplicant(p2p_supported);
wifi_close_supplicant_connection(NULL);
wifi_close_supplicant_connection(“sec”);
屏蔽掉就好。
关于这个问题我也没有弄明白,只不过屏蔽之后就能正确开启wifi了,所以详细解决办法还在研究中。。。

MTK 平台LCM ESD客制化及代码分析和案例

参考文档:
[FAQ14251]如何配置LCM ESD Check——读寄存器方式
https://onlinesso.mediatek.com/FAQ/SW/FAQ14251
[FAQ14273]MT6735/MT6753/MT6580 ESD问题攻略——外部TE方式
https://onlinesso.mediatek.com/FAQ/SW/FAQ14273
[FAQ14880]LCM ESD Check 问题处理流程
https://onlinesso.mediatek.com/FAQ/SW/FAQ14880
[FAQ13728]MT6735通过读寄存器方式做ESD,客制化需求—-多个返回值
https://onlinesso.mediatek.com/FAQ/SW/FAQ13728

一.LCM ESD客制化方法
alps/kernel-3.18/drivers/misc/mediatek/lcm/xxx/xxx.c
alps/vendor/mediatek/proprietary/bootable/bootloader/lk/dev/lcm/xxx/xxx.c
static void lcm_get_params(LCM_PARAMS params)
{
……
/
Esd Check方法1 : Read from lcm */
params->dsi.esd_check_enable = 1;
params->dsi.customization_esd_check_enable = 1;
params->dsi.lcm_esd_check_table[0].cmd = 0x0A; //具体是哪个寄存器由FAE告知
params->dsi.lcm_esd_check_table[0].count = 1;
params->dsi.lcm_esd_check_table[0].para_list[0] = 0x1C; //读取寄存器的正确值也由FAE告知,寄存器的值要配置正确,否则会不断地esd recovery

/ Esd Check方法2 : EXT TE /
//params->dsi.esd_check_enable = 1;
//params->dsi.customization_esd_check_enable = 0;
}

二.MT6580 LCM ESD check流程分析
alps/kernel-3.18/drivers/misc/mediatek/video/mt6580/videox/primary_display.c

int primary_display_init(char *lcm_name, unsigned int lcm_fps)
{
……
primary_display_esd_check_task =
kthread_create(primary_display_esd_check_worker_kthread,
NULL, “display_esd_check”);
……
//这里根据LCM中配置的params->dsi.esd_check_enable是否等于1来判断是否开启primary_display_esd_check_worker_kthread线程
if (_need_do_esd_check())
wake_up_process(primary_display_esd_check_task);
……
}

unsigned int _need_do_esd_check(void)
{
int ret = 0;

#ifdef CONFIG_OF
if ((pgc->plcm->params->dsi.esd_check_enable == 1)
&& (islcmconnected == 1))
ret = 1;

#else
if (pgc->plcm->params->dsi.esd_check_enable == 1)
ret = 1;

#endif
return ret;
}

static int primary_display_esd_check_worker_kthread(void data)
{
……
//定义esd recovery重试的次数
int esd_try_cnt = 5;
……
//这里每2秒扫描一次
msleep(2000); /
esd check and pull clock lane every 2s */
……
//执行esd check判断是否要进行esd recovery,关于primary_display_esd_check函数的分析在下面会讲到
ret = primary_display_esd_check();
if (ret == 1) {
pr_debug(“[ESD]esd check fail, will do esd recovery\n”);
i = esd_try_cnt;
while (i–) {
DISPCHECK(“[ESD]esd recovery try:%d\n”, i);
//执行恢复动作,重新初始化显示屏参数,关于primary_display_esd_recovery函数的分析在下面有讲到
primary_display_esd_recovery();
//执行完恢复动作后,再次进行esd check,如果检测到的lcm寄存器的值正常,则不会再进行recovery的动作;否则会连续进行recovery和esd check,若在规定的5次内recovery均不成功,就会执行primary_display_esd_check_enable(0)关闭primary_display_esd_check_worker_kthread线程。
ret = primary_display_esd_check();
if (ret == 0) {
pr_debug
(“[ESD]esd recovery success\n”);
break;
}
pr_debug(“[ESD]after esd recovery, esd check still fail\n”);
if (i == 0) {
DISPERR(
“[ESD]after esd recovery %d times, esd check still fail, disable esd check\n”,
esd_try_cnt);
primary_display_esd_check_enable(0);
}
}
}
……
}

/ ESD CHECK FUNCTION /
/ return 1: esd check fail /
/ return 0: esd check pass /
int primary_display_esd_check(void)
{
……
/ Esd Check : EXT TE /
//使用TE的方式进行esd check的时候,需要在lcm的驱动中定义esd_check_enable为1,customization_esd_check_enable为0,lcm的初始化参数也要匹配TE的,主控这边的TE GPIO口需要配置正确
if (pgc->plcm->params->dsi.customization_esd_check_enable == 0) {
MMProfileLogEx(ddp_mmp_get_events()->esd_extte,
MMProfileFlagStart, 0, 0);
if (primary_display_is_video_mode()) {
primary_display_switch_esd_mode(1);
/ use cmdq to pull DSI clk lane/
if (primary_display_cmdq_enabled()) {
_primary_path_lock(func);

            /* 0.create esd check cmdq */
            cmdqRecCreate(CMDQ_SCENARIO_DISP_ESD_CHECK, &(pgc->cmdq_handle_config_esd));
            _primary_path_unlock(__func__);

            /* 1.reset*/
            cmdqRecReset(pgc->cmdq_handle_config_esd);

            /* wait stream eof first */
            ret = cmdqRecWait(pgc->cmdq_handle_config_esd, CMDQ_EVENT_DISP_RDMA0_EOF);
            cmdqRecWait(pgc->cmdq_handle_config_esd, CMDQ_EVENT_MUTEX0_STREAM_EOF);

            _primary_path_lock(__func__);
            /* 2.stop dsi vdo mode */
            dpmgr_path_build_cmdq(pgc->dpmgr_handle,
                        pgc->cmdq_handle_config_esd, CMDQ_STOP_VDO_MODE, 0);

            /* 3.pull DSI clock lane */
            DSI_sw_clk_trail_cmdq(0, pgc->cmdq_handle_config_esd);
            DSI_manual_enter_HS(pgc->cmdq_handle_config_esd);


            /* 4.start dsi vdo mode */
            dpmgr_path_build_cmdq(pgc->dpmgr_handle,
                        pgc->cmdq_handle_config_esd, CMDQ_START_VDO_MODE, 0);

            /* 5. trigger path */
            cmdqRecClearEventToken(pgc->cmdq_handle_config_esd, CMDQ_EVENT_MUTEX0_STREAM_EOF);

            dpmgr_path_trigger(pgc->dpmgr_handle, pgc->cmdq_handle_config_esd, CMDQ_ENABLE);

            _primary_path_unlock(__func__);
            cmdqRecFlush(pgc->cmdq_handle_config_esd);

            cmdqRecDestroy(pgc->cmdq_handle_config_esd);
            pgc->cmdq_handle_config_esd = NULL;
        }
        if (_need_register_eint()) {
            MMProfileLogEx(ddp_mmp_get_events()->esd_extte,
                       MMProfileFlagPulse, 1, 1);

            if (wait_event_interruptible_timeout
                (esd_ext_te_wq,
                 atomic_read(&esd_ext_te_event),
                 HZ / 2) > 0) {
                ret = 0;    /* esd check pass */
            } else {
                ret = 1;    /* esd check fail */
            }
            atomic_set(&esd_ext_te_event, 0);
        }
        primary_display_switch_esd_mode(0);
    } else {
        MMProfileLogEx(ddp_mmp_get_events()->esd_extte,
                   MMProfileFlagPulse, 0, 1);
        if (dpmgr_wait_event_timeout
            (pgc->dpmgr_handle, DISP_PATH_EVENT_IF_VSYNC,
             HZ / 2) > 0) {
            ret = 0;    /* esd check pass */
        } else {
            ret = 1;    /* esd check fail */
        }
    }
    MMProfileLogEx(ddp_mmp_get_events()->esd_extte,
               MMProfileFlagEnd, 0, ret);
    /* _primary_path_unlock(__func__); */
    goto done;
}
/* / Esd Check : Read from lcm */
//读LCM寄存器方式进行esd check和recovery
MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm, MMProfileFlagStart, 0,
           primary_display_cmdq_enabled());
if (primary_display_cmdq_enabled()) {
    _primary_path_lock(__func__);
    MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm,
               MMProfileFlagPulse, 0, 1);

    /* 0.create esd check cmdq */
    cmdqRecCreate(CMDQ_SCENARIO_DISP_ESD_CHECK,
              &(pgc->cmdq_handle_config_esd));
    dpmgr_path_build_cmdq(pgc->dpmgr_handle,
                  pgc->cmdq_handle_config_esd,
                  CMDQ_ESD_ALLC_SLOT, 0);
    MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm,
               MMProfileFlagPulse, 0, 2);
    DISPCHECK("[ESD]ESD config thread=%p\n", pgc->cmdq_handle_config_esd);
    _primary_path_unlock(__func__);

    /* 1.use cmdq to read from lcm */
    //发CMDQ_ESD_CHECK_READ的命令读取lcm的寄存器数据,_esd_check_config_handle_vdo函数在下面会讲到
    if (primary_display_is_video_mode())
        ret = _esd_check_config_handle_vdo();
    else
        ret = _esd_check_config_handle_cmd();

    MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm,
               MMProfileFlagPulse,
               primary_display_is_video_mode(), 3);
    if (ret == 1) {
        /* cmdq fail */
        if (_need_wait_esd_eof()) {
            /* Need set esd check eof synctoken to let trigger loop go. */
            cmdqCoreSetEvent(CMDQ_SYNC_TOKEN_ESD_EOF);
        }
        /* do dsi reset */
        dpmgr_path_build_cmdq(pgc->dpmgr_handle,
                      pgc->cmdq_handle_config_esd,
                      CMDQ_DSI_RESET, 0);
        goto destroy_cmdq;
    }

    DISPCHECK("[ESD]ESD config thread done~\n");

    /* 2.check data(*cpu check now) */
    //发CMDQ_ESD_CHECK_CMP的命令,判断读取到的lcm寄存器数据和lcm驱动中定义的lcm_esd_check_table[i].para_list[0]是否一致
    ret = dpmgr_path_build_cmdq(pgc->dpmgr_handle,
                    pgc->cmdq_handle_config_esd,
                    CMDQ_ESD_CHECK_CMP, 0);
    MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm,
               MMProfileFlagPulse, 0, 4);
    if (ret)
        ret = 1;    /* esd check fail */

destroy_cmdq:
dpmgr_path_build_cmdq(pgc->dpmgr_handle,
pgc->cmdq_handle_config_esd,
CMDQ_ESD_FREE_SLOT, 0);
/ 3.destroy esd config thread /
cmdqRecDestroy(pgc->cmdq_handle_config_esd);
pgc->cmdq_handle_config_esd = NULL;
/ _primary_path_unlock(func); /
}
……
return ret;
}

/ For Vdo Mode Read LCM Check /
/ Config cmdq_handle_config_esd /
int _esd_check_config_handle_vdo(void)
{
int ret = 0; / 0:success , 1:fail /

primary_display_esd_cust_bycmdq(1);

/* 1.reset */
cmdqRecReset(pgc->cmdq_handle_config_esd);

/* Lock which is used to avoid esd and suspend affect */
ret = cmdqRecWait(pgc->cmdq_handle_config_esd, CMDQ_EVENT_DISP_RDMA0_EOF);
cmdqRecWait(pgc->cmdq_handle_config_esd, CMDQ_EVENT_MUTEX0_STREAM_EOF);

_primary_path_lock(__func__);

/* 2.stop dsi vdo mode */
dpmgr_path_build_cmdq(pgc->dpmgr_handle, pgc->cmdq_handle_config_esd,
              CMDQ_STOP_VDO_MODE, 0);

/* 3.write instruction(read from lcm) */
//发CMDQ_ESD_CHECK_READ的命令读取lcm的寄存器数据
dpmgr_path_build_cmdq(pgc->dpmgr_handle, pgc->cmdq_handle_config_esd, CMDQ_ESD_CHECK_READ, 0);

/* pull DSI clock lane */
DSI_sw_clk_trail_cmdq(0, pgc->cmdq_handle_config_esd);
DSI_manual_enter_HS(pgc->cmdq_handle_config_esd);

/* 4.start dsi vdo mode */
dpmgr_path_build_cmdq(pgc->dpmgr_handle, pgc->cmdq_handle_config_esd, CMDQ_START_VDO_MODE, 0);

/* 5. trigger path */
dpmgr_path_trigger(pgc->dpmgr_handle, pgc->cmdq_handle_config_esd,
           CMDQ_ENABLE);

_primary_path_unlock(__func__);

/* 6.flush instruction */
dprec_logger_start(DPREC_LOGGER_ESD_CMDQ, 0, 0);
ret = cmdqRecFlush(pgc->cmdq_handle_config_esd);
dprec_logger_done(DPREC_LOGGER_ESD_CMDQ, 0, 0);

DISPCHECK("[ESD]_esd_check_config_handle_vdo ret=%d\n", ret);

if (ret)
    ret = 1;

primary_display_esd_cust_bycmdq(0);
return ret;

}

alps/kernel-3.18/drivers/misc/mediatek/video/mt6580/dispsys/ddp_dsi.c
int ddp_dsi_build_cmdq(DISP_MODULE_ENUM module, void cmdq_trigger_handle, CMDQ_STATE state)
{
……
else if (state == CMDQ_ESD_CHECK_READ) {
// 下发CMDQ_ESD_CHECK_READ命令的数据处理
/
enable dsi interrupt: RD_RDY/CMD_DONE (need do this here?) */
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG, DSI_REG[dsi_i]->DSI_INTEN,
RD_RDY, 1);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG, DSI_REG[dsi_i]->DSI_INTEN,
CMD_DONE, 1);

for (i = 0; i < 3; i++) {
    if (dsi_params->lcm_esd_check_table[i].cmd == 0)
        break;

    /* 0. send read lcm command(short packet) */
    t0.CONFG = 0x04;    /* /BTA */
    t0.Data0 = dsi_params->lcm_esd_check_table[i].cmd;
    /* / 0xB0 is used to distinguish DCS cmd or Gerneric cmd, is that Right??? */
    t0.Data_ID =
        (t0.Data0 <
         0xB0) ? DSI_DCS_READ_PACKET_ID : DSI_GERNERIC_READ_LONG_PACKET_ID;
    t0.Data1 = 0;

    /* write DSI CMDQ */
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_CMDQ_REG[dsi_i]->data[0],
             0x00013700);
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_CMDQ_REG[dsi_i]->data[1],
             AS_UINT32(&t0));
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_CMDQ_SIZE, 2);

    /* start DSI */
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_START, 0);
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_START, 1);

    /* 1. wait DSI RD_RDY(must clear, in case of cpu RD_RDY interrupt handler) */
    if (dsi_i == 0) {
        DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
                  0x00000001, 0x1);
        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_STATUS_REG,
                  DSI_REG[dsi_i]->DSI_INTSTA, RD_RDY, 0x0);
    }

#if 0
else { / DSI1 /

    DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
              0x00000001, 0x1);
    DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_STATUS_REG,
              DSI_REG[dsi_i]->DSI_INTSTA, RD_RDY, 0x0);
}

#endif
/ 2. save RX data /
if (hSlot) {
DSI_BACKUPREG32(cmdq_trigger_handle,
hSlot, i 4 + 0, &DSI_REG[0]->DSI_RX_DATA0);
DSI_BACKUPREG32(cmdq_trigger_handle,
hSlot, i
4 + 1, &DSI_REG[0]->DSI_RX_DATA1);
DSI_BACKUPREG32(cmdq_trigger_handle,
hSlot, i 4 + 2, &DSI_REG[0]->DSI_RX_DATA2);
DSI_BACKUPREG32(cmdq_trigger_handle,
hSlot, i
4 + 3, &DSI_REG[0]->DSI_RX_DATA3);
}

/* 3. write RX_RACK */
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_RACK_REG, DSI_REG[dsi_i]->DSI_RACK,
          DSI_RACK, 1);

/* 4. polling not busy(no need clear) */
if (dsi_i == 0) {
    DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
              0x80000000, 0);
}

#if 0
else { / DSI1 /

    DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
              0x80000000, 0);
}

#endif
/ loop: 0~4 /
}

    /* DSI_OUTREGBIT(cmdq_trigger_handle, DSI_INT_ENABLE_REG,DSI_REG[dsi_i]->DSI_INTEN,RD_RDY,0); */
} else if (state == CMDQ_ESD_CHECK_CMP) {
// 下发CMDQ_ESD_CHECK_READ命令的数据处理

    DISPCHECK("[DSI]enter cmp\n");
    /* cmp just once and only 1 return value */
    for (i = 0; i < 3; i++) {
        if (dsi_params->lcm_esd_check_table[i].cmd == 0)
            break;

        DISPCHECK("[DSI]enter cmp i=%d\n", i);

        /* read data */
        if (hSlot) {
            /* read from slot */
            cmdqBackupReadSlot(hSlot, i * 4 + 0, ((uint32_t *)&read_data0));
            cmdqBackupReadSlot(hSlot, i * 4 + 1, ((uint32_t *)&read_data1));
            cmdqBackupReadSlot(hSlot, i * 4 + 2, ((uint32_t *)&read_data2));
            cmdqBackupReadSlot(hSlot, i * 4 + 3, ((uint32_t *)&read_data3));
        } else {
            /* read from dsi , support only one cmd read */
            if (i == 0) {
                DSI_OUTREG32(NULL, &read_data0,
                        AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA0));
                DSI_OUTREG32(NULL, &read_data1,
                        AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA1));
                DSI_OUTREG32(NULL, &read_data2,
                        AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA2));
                DSI_OUTREG32(NULL, &read_data3,
                        AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA3));
            }
        }

        MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm, MMProfileFlagPulse,
                   AS_UINT32(&read_data0),
                   AS_UINT32(&(dsi_params->lcm_esd_check_table[i])));

        DISPDBG("[DSI]enter cmp read_data0 byte0=0x%x byte1=0x%x byte2=0x%x byte3=0x%x\n",
            read_data0.byte0,
            read_data0.byte1,
            read_data0.byte2,
            read_data0.byte3);
        DISPDBG
            ("[DSI]enter cmp check_table cmd=0x%x,count=0x%x,para_list[0]=0x%x,para_list[1]=0x%x\n",
             dsi_params->lcm_esd_check_table[i].cmd,
             dsi_params->lcm_esd_check_table[i].count,
             dsi_params->lcm_esd_check_table[i].para_list[0],
             dsi_params->lcm_esd_check_table[i].para_list[1]);
        DISPDBG("[DSI]enter cmp DSI+0x200=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x200));
        DISPDBG("[DSI]enter cmp DSI+0x204=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x204));
        DISPDBG("[DSI]enter cmp DSI+0x60=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x60));
        DISPDBG("[DSI]enter cmp DSI+0x74=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x74));
        DISPDBG("[DSI]enter cmp DSI+0x88=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x88));
        DISPDBG("[DSI]enter cmp DSI+0x0c=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x0c));

        /* 0x02: acknowledge & error report */
        /* 0x11: generic short read response(1 byte return) */
        /* 0x12: generic short read response(2 byte return) */
        /* 0x1a: generic long read response */
        /* 0x1c: dcs long read response */
        /* 0x21: dcs short read response(1 byte return) */
        /* 0x22: dcs short read response(2 byte return) */
        //根据读取到read_data0.byte0的值,来判断lcm发过来的是什么类型的数据包,进行相应的判断处理
        packet_type = read_data0.byte0;

        if (packet_type == 0x1A || packet_type == 0x1C) {
            recv_data_cnt = read_data0.byte1 + read_data0.byte2 * 16;
            if (recv_data_cnt > 2) {
                DISPCHECK
                ("Set receive data count from %d to 2 as ESD check supported max data count.\n",
                    recv_data_cnt);
                recv_data_cnt = 2;
            }
            if (recv_data_cnt > dsi_params->lcm_esd_check_table[i].count) {
                DISPCHECK
                ("Set receive data count from %d to %d as ESD check table specified.\n",
                        recv_data_cnt, dsi_params->lcm_esd_check_table[i].count);
                recv_data_cnt = dsi_params->lcm_esd_check_table[i].count;
            }
            DISPCHECK("DSI read long packet size: %d\n", recv_data_cnt);
            //比较这种lcm中配置的lcm_esd_check_table[i].para_list[0]值和read_data1.byte0的值是否一致
            result = memcmp((void *)&(dsi_params->lcm_esd_check_table[i].para_list[0]),
                (void *)&read_data1, recv_data_cnt);
        } else if (packet_type == 0x11 ||
               packet_type == 0x12 ||
               packet_type == 0x21 ||
               packet_type == 0x22) {
            /* short read response */
            if (packet_type == 0x11 || packet_type == 0x21)
                recv_data_cnt = 1;
            else
                recv_data_cnt = 2;

            if (recv_data_cnt > dsi_params->lcm_esd_check_table[i].count) {
                DISPCHECK
                ("Set receive data count from %d to %d as ESD check table specified.\n",
                    recv_data_cnt, dsi_params->lcm_esd_check_table[i].count);
                recv_data_cnt = dsi_params->lcm_esd_check_table[i].count;
            }
            DISPCHECK("DSI read short packet size: %d\n", recv_data_cnt);
            //比较这种lcm中配置的lcm_esd_check_table[i].para_list[0]值和read_data0.byte1的值是否一致
            result = memcmp((void *)&(dsi_params->lcm_esd_check_table[i].para_list[0]),
                    (void *)&read_data0.byte1, recv_data_cnt);
        } else if (packet_type == 0x02) {
            DISPCHECK("read return type is 0x02\n");
            result = 1;
        } else {
            DISPCHECK("read return type is non-recognite, type = 0x%x\n", packet_type);
            result = 1;
        }

        if (result == 0) {
            /* clear rx data */
            /* DSI_OUTREG32(NULL, &DSI_REG[dsi_i]->DSI_RX_DATA0,0); */
            // result等于0的话,esd是ok的,不会进行recovery的动作
            ret = 0; /* esd pass */
        } else {
            // result等于1的话,esd是fail的,会进行recovery的动作
            ret = 1; /* esd fail */
            break;
        }
    }

}

……
}

alps/kernel-3.18/drivers/misc/mediatek/video/mt6580/videox/primary_display.c
/ ESD RECOVERY /
int primary_display_esd_recovery(void)
{
……
//重新初始化显示屏参数
disp_lcm_init(pgc->plcm, 1);
……
}

alps/kernel-3.18/drivers/misc/mediatek/video/mt6580/videox/disp_lcm.c
int disp_lcm_init(disp_lcm_handle plcm, int force)
{
LCM_DRIVER
lcm_drv = NULL;

DISPFUNC();
if (_is_lcm_inited(plcm)) {
        lcm_drv = plcm->drv;

        if (lcm_drv->init_power) {
                if (!disp_lcm_is_inited(plcm) || force) {
                        DISPMSG("lcm init power()\n");
                        lcm_drv->init_power();
                }
        }

        if (lcm_drv->init) {
                if (!disp_lcm_is_inited(plcm) || force) {
                        DISPMSG("lcm init()\n");
                        //这里就是调用到lcm的static void lcm_init(void)函数
                        lcm_drv->init();
                }
        } else {
                DISPERR("FATAL ERROR, lcm_drv->init is null\n");
                return -1;
        }

        return 0;
}

DISPERR("plcm is null\n");
return -1;

}

三.读寄存器方式做ESD检测,目前只支持读取三个寄存器,各个寄存器只能够识别返回一个值。(只能够识别屏端返回的短包),如果需要识别返回多个值,可以做如下修改
(1)
alps/kernel-3.18/drivers/misc/mediatek/lcm/inc/lcm_drv.h

#define RT_MAX_NUM 10 //该值不可以修改,最大只支持10个返回值

#define ESD_CHECK_NUM 3 //该值表示目前最多可以读取的寄存器个数,尽量不要修改,修改成越大,esc check的负载越重,系统运行时更慢
typedef struct {
unsigned char cmd;
unsigned char count;
unsigned char para_list[RT_MAX_NUM];
} LCM_esd_check_item;
typedef struct {
……
LCM_esd_check_item lcm_esd_check_table[ESD_CHECK_NUM];
……
} LCM_DSI_PARAMS;

(2)
alps/kernel-3.18/drivers/misc/mediatek/lcm/xxx/xxx.c
alps/vendor/mediatek/proprietary/bootable/bootloader/lk/dev/lcm/xxx/xxx.c
static void lcm_get_params(LCM_PARAMS *params)
{
……
//配置三个lcm的寄存器地址,每个寄存器配置10个值,如下,寄存器的值要配置正确,否则会不断地esd recovery
params->dsi.esd_check_enable = 1;
params->dsi.customization_esd_check_enable = 1;

params->dsi.lcm_esd_check_table[0].cmd = 0xBa;
params->dsi.lcm_esd_check_table[0].count = 10; 
params->dsi.lcm_esd_check_table[0].para_list[0] = 0x32;
params->dsi.lcm_esd_check_table[0].para_list[1] = 0x81;
params->dsi.lcm_esd_check_table[0].para_list[2] = 0x05;
params->dsi.lcm_esd_check_table[0].para_list[3] = 0xF9;
params->dsi.lcm_esd_check_table[0].para_list[4] = 0x0e;
params->dsi.lcm_esd_check_table[0].para_list[5] = 0x0e;
params->dsi.lcm_esd_check_table[0].para_list[6] = 0x02;
params->dsi.lcm_esd_check_table[0].para_list[7] = 0x00;
params->dsi.lcm_esd_check_table[0].para_list[8] = 0x00;
params->dsi.lcm_esd_check_table[0].para_list[9] = 0x00;

params->dsi.lcm_esd_check_table[1].cmd = 0xc1;
params->dsi.lcm_esd_check_table[1].count = 10; 
params->dsi.lcm_esd_check_table[1].para_list[0] = 0x54;
params->dsi.lcm_esd_check_table[1].para_list[1] = 0x00;
params->dsi.lcm_esd_check_table[1].para_list[2] = 0x1E;
params->dsi.lcm_esd_check_table[1].para_list[3] = 0x1E;
params->dsi.lcm_esd_check_table[1].para_list[4] = 0x77;
params->dsi.lcm_esd_check_table[1].para_list[5] = 0xF1;
params->dsi.lcm_esd_check_table[1].para_list[6] = 0xFF;
params->dsi.lcm_esd_check_table[1].para_list[7] = 0xFF;
params->dsi.lcm_esd_check_table[1].para_list[8] = 0xCC;
params->dsi.lcm_esd_check_table[1].para_list[9] = 0xCC;

params->dsi.lcm_esd_check_table[2].cmd = 0xe9;
params->dsi.lcm_esd_check_table[2].count = 10; 
params->dsi.lcm_esd_check_table[2].para_list[0] = 0x02;
params->dsi.lcm_esd_check_table[2].para_list[1] = 0x00;
params->dsi.lcm_esd_check_table[2].para_list[2] = 0x10;
params->dsi.lcm_esd_check_table[2].para_list[3] = 0x05;
params->dsi.lcm_esd_check_table[2].para_list[4] = 0x16;
params->dsi.lcm_esd_check_table[2].para_list[5] = 0x0A;
params->dsi.lcm_esd_check_table[2].para_list[6] = 0xA0;
params->dsi.lcm_esd_check_table[2].para_list[7] = 0x12;
params->dsi.lcm_esd_check_table[2].para_list[8] = 0x31;
params->dsi.lcm_esd_check_table[2].para_list[9] = 0x23;

}

(3)
alps/kernel-3.18/drivers/misc/mediatek/video/mt6735/ddp_dsi.c
int ddp_dsi_build_cmdq(DISP_MODULE_ENUM module, void cmdq_trigger_handle, CMDQ_STATE state)
{
int ret = 0, result = 0;
int i = 0, j = 0;
int dsi_i = 0;
LCM_DSI_PARAMS
dsi_params = NULL;
DSI_T0_INS t0, t1;
struct DSI_RX_DATA_REG read_data0,read_data1,read_data2,read_data3;
unsigned char buffer[20];
uint32_t recv_data_cnt;
unsigned char packet_type;
unsigned int h = 0;

static cmdqBackupSlotHandle hSlot[4] = {0, 0, 0, 0};

if (DISP_MODULE_DSIDUAL == module)
    dsi_i = 0;
else
    dsi_i = DSI_MODULE_to_ID(module);

dsi_params = &_dsi_context[dsi_i].dsi_params;

if (cmdq_trigger_handle == NULL) {
    DISPMSG("cmdq_trigger_handle is NULL\n");
    return -1;
}

if (state == CMDQ_BEFORE_STREAM_SOF) {
    /* need waiting te */
    if (module == DISP_MODULE_DSI0) {
        if (dsi0_te_enable == 0)
            return 0;

#ifndef MTK_FB_CMDQ_DISABLE
ret =
cmdqRecClearEventToken(cmdq_trigger_handle, CMDQ_EVENT_DSI_TE);
ret = cmdqRecWait(cmdq_trigger_handle, CMDQ_EVENT_DSI_TE);

#endif
}

#if 0
else if (module == DISP_MODULE_DSI1) {
if (dsi1_te_enable == 0)
return 0;

    ret =
        cmdqRecClearEventToken(cmdq_trigger_handle,
                   CMDQ_EVENT_MDP_DSI1_TE_SOF);
    ret = cmdqRecWait(cmdq_trigger_handle, CMDQ_EVENT_MDP_DSI1_TE_SOF);
} else if (module == DISP_MODULE_DSIDUAL) {
    if (dsidual_te_enable == 0)
        return 0;

    /* TODO: dsi 8 lane do not use te???? */
    /* ret = cmdqRecWait(cmdq_trigger_handle, CMDQ_EVENT_MDP_DSI0_TE_SOF); */
}

#endif
else {
DISPERR(“wrong module: %s\n”, ddp_get_module_name(module));
return -1;
}
} else if (state == CMDQ_CHECK_IDLE_AFTER_STREAM_EOF) {
/ need waiting te /
if (module == DISP_MODULE_DSI0) {
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
0x80000000, 0);
}

#if 0
else if (module == DISP_MODULE_DSI1) {
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_INTSTA,
0x80000000, 0);
} else if (module == DISP_MODULE_DSIDUAL) {
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[0]->DSI_INTSTA,
0x80000000, 0);
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[1]->DSI_INTSTA,
0x80000000, 0);
}

#endif
else {
DISPERR(“wrong module: %s\n”, ddp_get_module_name(module));
return -1;
}
} else if (state == CMDQ_ESD_CHECK_READ) {
/ enable dsi interrupt: RD_RDY/CMD_DONE (need do this here?) /
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,
DSI_REG[dsi_i]->DSI_INTEN, RD_RDY, 1);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,
DSI_REG[dsi_i]->DSI_INTEN, CMD_DONE, 1);

for (i = 0; i < 3; i++) {
    if (dsi_params->lcm_esd_check_table[i].cmd == 0)
        break;

    /* 0. send read lcm command(short packet) */
    t0.CONFG = 0x04;    /* BTA */
    t0.Data0 = dsi_params->lcm_esd_check_table[i].cmd;
    /* / 0xB0 is used to distinguish DCS cmd or Gerneric cmd, is that Right??? */
    t0.Data_ID =
        (t0.Data0 <
         0xB0) ? DSI_DCS_READ_PACKET_ID :
        DSI_GERNERIC_READ_LONG_PACKET_ID;
    t0.Data1 = 0;

    t1.CONFG = 0x00;
    t1.Data0 = dsi_params->lcm_esd_check_table[i].count;
    t1.Data1 = 0x00;
    t1.Data_ID = 0x37;

    /* write DSI CMDQ */
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_CMDQ_REG[dsi_i]->data[0],
             AS_UINT32(&t1));
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_CMDQ_REG[dsi_i]->data[1],
             AS_UINT32(&t0));
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_CMDQ_SIZE,
             2);

    /* start DSI */
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_START, 0);
    DSI_OUTREG32(cmdq_trigger_handle, &DSI_REG[dsi_i]->DSI_START, 1);

    /* 1. wait DSI RD_RDY(must clear, in case of cpu RD_RDY interrupt handler) */
    if (dsi_i == 0) {    /* DSI0 */
        DSI_POLLREG32(cmdq_trigger_handle,
                  &DSI_REG[dsi_i]->DSI_INTSTA, 0x00000001, 0x1);
        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_STATUS_REG,
                  DSI_REG[dsi_i]->DSI_INTSTA, RD_RDY, 0);
    }

#if 0
else { / DSI1 /
DSI_POLLREG32(cmdq_trigger_handle,
&DSI_REG[dsi_i]->DSI_INTSTA, 0x00000001, 0x1);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_STATUS_REG,
DSI_REG[dsi_i]->DSI_INTSTA, RD_RDY, 0);
}

#endif
/ 2. save RX data /
if (hSlot[0] && hSlot[1] && hSlot[2] && hSlot[3]) {
DSI_BACKUPREG32(cmdq_trigger_handle, hSlot[0], i,
&DSI_REG[0]->DSI_RX_DATA0);
DSI_BACKUPREG32(cmdq_trigger_handle, hSlot[1], i,
&DSI_REG[0]->DSI_RX_DATA1);
DSI_BACKUPREG32(cmdq_trigger_handle, hSlot[2], i,
&DSI_REG[0]->DSI_RX_DATA2);
DSI_BACKUPREG32(cmdq_trigger_handle, hSlot[3], i,
&DSI_REG[0]->DSI_RX_DATA3);
}

/* 3. write RX_RACK */
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_RACK_REG,
          DSI_REG[dsi_i]->DSI_RACK, DSI_RACK, 1);

/* 4. polling not busy(no need clear) */
if (dsi_i == 0) {    /* DSI0 */
    DSI_POLLREG32(cmdq_trigger_handle,
              &DSI_REG[dsi_i]->DSI_INTSTA, 0x80000000, 0);
}

#if 0
else { / DSI1 /
DSI_POLLREG32(cmdq_trigger_handle,
&DSI_REG[dsi_i]->DSI_INTSTA, 0x80000000, 0);
}

#endif
/ loop: 0~4 /
}

    /* DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,DSI_REG[dsi_i]->DSI_INTEN,RD_RDY,0); */
} else if (state == CMDQ_ESD_CHECK_CMP) {

    DISPMSG("[DSI]enter cmp\n");
    /* cmp just once and only 1 return value */
    for (i = 0; i < 3; i++) {
        if (dsi_params->lcm_esd_check_table[i].cmd == 0)
            break;

        DISPMSG("[DSI]enter cmp i=%d\n", i);

        /* read data */
        if (hSlot[0] && hSlot[1] && hSlot[2] && hSlot[3]) {
            /* read from slot */
            cmdqBackupReadSlot(hSlot[0], i, ((uint32_t *)&read_data0));
            cmdqBackupReadSlot(hSlot[1], i, ((uint32_t *)&read_data1));
            cmdqBackupReadSlot(hSlot[2], i, ((uint32_t *)&read_data2));
            cmdqBackupReadSlot(hSlot[3], i, ((uint32_t *)&read_data3)); 
        } else {
            /* read from dsi , support only one cmd read */
            if (i == 0) {
                DSI_OUTREG32(NULL, &read_data0,AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA0));
                DSI_OUTREG32(NULL, &read_data1,AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA1));
                DSI_OUTREG32(NULL, &read_data2,AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA2));
                DSI_OUTREG32(NULL, &read_data3,AS_UINT32(&DSI_REG[dsi_i]->DSI_RX_DATA3));
            }
        }

        MMProfileLogEx(ddp_mmp_get_events()->esd_rdlcm, MMProfileFlagPulse,
                   AS_UINT32(&read_data0),
                   AS_UINT32(&(dsi_params->lcm_esd_check_table[i])));

        DISPDBG
            ("[DSI]enter cmp read_data0 byte0=0x%x byte1=0x%x byte2=0x%x byte3=0x%x\n",
             read_data0.byte0, read_data0.byte1, read_data0.byte2,
             read_data0.byte3);
        DISPDBG
            ("[DSI]cmp check_table cmd=0x%x,count=0x%x,para_list[0]=0x%x,para_list[1]=0x%x\n",
             dsi_params->lcm_esd_check_table[i].cmd,
             dsi_params->lcm_esd_check_table[i].count,
             dsi_params->lcm_esd_check_table[i].para_list[0],
             dsi_params->lcm_esd_check_table[i].para_list[1]);
        DISPDBG("[DSI]enter cmp DSI+0x200=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x200));
        DISPDBG("[DSI]enter cmp DSI+0x204=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x204));
        DISPDBG("[DSI]enter cmp DSI+0x60=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x60));
        DISPDBG("[DSI]enter cmp DSI+0x74=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x74));
        DISPDBG("[DSI]enter cmp DSI+0x88=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x88));
        DISPDBG("[DSI]enter cmp DSI+0x0c=0x%x\n",
            AS_UINT32(DDP_REG_BASE_DSI0 + 0x0c));

        /* 0x02: acknowledge & error report */
        /* 0x11: generic short read response(1 byte return) */
        /* 0x12: generic short read response(2 byte return) */
        /* 0x1a: generic long read response */
        /* 0x1c: dcs long read response */
        /* 0x21: dcs short read response(1 byte return) */
        /* 0x22: dcs short read response(2 byte return) */
        packet_type = read_data0.byte0;

        if (packet_type == 0x1A || packet_type == 0x1C) {
            recv_data_cnt = read_data0.byte1 + read_data0.byte2 * 16;
            DISPDBG("packet_type=0x%x,recv_data_cnt = %d\n", packet_type, recv_data_cnt);
            if(recv_data_cnt > RT_MAX_NUM)
            {
                DISPMSG("DSI read long packet data exceeds 10 bytes \n");
                recv_data_cnt = RT_MAX_NUM;
            } 

            if (recv_data_cnt > dsi_params->lcm_esd_check_table[i].count) {
                recv_data_cnt = dsi_params->lcm_esd_check_table[i].count;
            }

            if (recv_data_cnt <= 4) {
                memcpy((void *)buffer, (void *)&read_data1, recv_data_cnt);
            } else if (recv_data_cnt <= 8) {
                memcpy((void *)buffer, (void *)&read_data1, 4);
                memcpy((void *)(buffer + 4), (void *)&read_data2, recv_data_cnt - 4);
            } else {
                memcpy((void *)buffer, (void *)&read_data1, 4);
                memcpy((void *)(buffer + 4), (void *)&read_data2, 4);
                memcpy((void *)(buffer + 8), (void *)&read_data3, recv_data_cnt - 8);
            }

            for (j = 0; j < recv_data_cnt; j++) {
                DISPDBG("buffer[%d]=0x%x\n", j, buffer[j]);
                if (buffer[j] != dsi_params->lcm_esd_check_table[i].para_list[j]) {
                    result= 1;
                    DISPMSG("[ESD]CMP i %d return value 0x%x,para_list[%d]=0x%x\n", i,
                    buffer[j], j, dsi_params->lcm_esd_check_table[i].para_list[j]);
                    break;
                }
            }
        } else if (packet_type == 0x11 ||
        packet_type == 0x12 ||
        packet_type == 0x21 ||
        packet_type == 0x22) {
            /* short read response */
            if (packet_type == 0x11 || packet_type == 0x21)
                recv_data_cnt = 1;
            else
                recv_data_cnt = 2;

            if (recv_data_cnt > dsi_params->lcm_esd_check_table[i].count) {
                recv_data_cnt = dsi_params->lcm_esd_check_table[i].count;
            }

            memcpy((void *)buffer, (void *)&read_data0.byte1, recv_data_cnt);
            DISPDBG("packet_type=0x%x,recv_data_cnt = %d\n", packet_type, recv_data_cnt);

            for (j = 0; j < recv_data_cnt; j++) {
                DISPDBG("buffer[%d]=0x%x\n", j, buffer[j]);
                if (buffer[j] != dsi_params->lcm_esd_check_table[i].para_list[j]) {
                    result= 1;
                    DISPMSG("[ESD]CMP i %d return value 0x%x,para_list[%d]=0x%x\n", i,
                    buffer[j], j, dsi_params->lcm_esd_check_table[i].para_list[j]);
                    break;
                }
            }
        } else if (packet_type == 0x02) {
            DISPMSG("read return type is 0x02\n");
            result = 1;
        } else {
            DISPMSG("read return type is non-recognite, type = 0x%x\n", packet_type);
            result = 1;
        }

        if (result == 0) {
            /* clear rx data */
            /* DSI_OUTREG32(NULL, &DSI_REG[dsi_i]->DSI_RX_DATA0,0); */
            ret = 0; /* esd pass */
        } else {
            ret = 1; /* esd fail */
            break;
        }
    }

} else if (state == CMDQ_ESD_ALLC_SLOT) {
    /* create 3*4 slot */
    for(h = 0; h < 4; h++){
        cmdqBackupAllocateSlot(&hSlot[h], 3);
    }
} else if (state == CMDQ_ESD_FREE_SLOT) {
    if (hSlot[0] && hSlot[1] && hSlot[2] && hSlot[3]) {
        for(h = 0; h < 4; h++){
            cmdqBackupFreeSlot(hSlot[h]);
            hSlot[h] = 0;
        }
    }
} else if (state == CMDQ_STOP_VDO_MODE) {
    /* use cmdq to stop dsi vdo mode */
    /* -1. stop TE_RDY IRQ */
    DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,
              DSI_REG[i]->DSI_INTEN, TE_RDY, 0);

    /* 0. set dsi cmd mode */
    DSI_SetMode(module, cmdq_trigger_handle, CMD_MODE);

    /* 1. polling dsi not busy */
    i = DSI_MODULE_BEGIN(module);
    if (i == 0) {
        /* DSI0/DUAL */
        /* polling vm done */
        /* polling dsi busy */
        DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[i]->DSI_INTSTA,
                  0x80000000, 0);

#if 0
i = DSI_MODULE_END(module);
if (i == 1) { / DUAL /
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[i]->DSI_INTSTA,
0x80000000, 0);
}

#endif
}

#if 0
else { / DSI1 /
DSI_POLLREG32(cmdq_trigger_handle, &DSI_REG[i]->DSI_INTSTA,
0x80000000, 0);
}

#endif
/ 2.dual dsi need do reset DSI_DUAL_EN/DSI_START /
if (module == DISP_MODULE_DSIDUAL) {
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_COM_CTRL_REG,
DSI_REG[0]->DSI_COM_CTRL, DSI_DUAL_EN, 0);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_COM_CTRL_REG,
DSI_REG[1]->DSI_COM_CTRL, DSI_DUAL_EN, 0);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_START_REG,
DSI_REG[0]->DSI_START, DSI_START, 0);
DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_START_REG,
DSI_REG[1]->DSI_START, DSI_START, 0);
}
/ 3.disable HS /
/ DSI_clk_HS_mode(module, cmdq_trigger_handle, false); /

} else if (state == CMDQ_START_VDO_MODE) {

    /* 0. dual dsi set DSI_START/DSI_DUAL_EN */
    if (module == DISP_MODULE_DSIDUAL) {
        /* must set DSI_START to 0 before set dsi_dual_en, don't know why.2014.02.15 */
        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_START_REG,
                  DSI_REG[0]->DSI_START, DSI_START, 0);
        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_START_REG,
                  DSI_REG[1]->DSI_START, DSI_START, 0);

        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_COM_CTRL_REG,
                  DSI_REG[0]->DSI_COM_CTRL, DSI_DUAL_EN, 1);
        DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_COM_CTRL_REG,
                  DSI_REG[1]->DSI_COM_CTRL, DSI_DUAL_EN, 1);

    }
    /* 1. set dsi vdo mode */
    DSI_SetMode(module, cmdq_trigger_handle, dsi_params->mode);

    /* 2. enable HS */
    /* DSI_clk_HS_mode(module, cmdq_trigger_handle, true); */

    /* 3. enable mutex */
    /* ddp_mutex_enable(mutex_id_for_latest_trigger,0,cmdq_trigger_handle); */

    /* 4. start dsi */
    /* DSI_Start(module, cmdq_trigger_handle); */

} else if (state == CMDQ_DSI_RESET) {
    DISPMSG("CMDQ Timeout, Reset DSI\n");
    DSI_DumpRegisters(module, 1);
    DSI_Reset(module, NULL);
}

return ret;

}

四.案例分析
1.哪个案子的哪种显示屏:V166-357D jd9365
2.现象:有进行复位动作,但是屏无法恢复
3.分析:读0x0a寄存器,出现异常的情况为0x18或者0x08,且经过连续5次recovery之后异常没有消除,最终退出了recovery kthread屏也没有恢复
4.解决办法:
a.修改lcm_init和lcm_suspend时lcm rst脚的控制方法及延时的时间,保证每一次初始化过程更合理有效

b.增加初始化时下发0x11的次数确保下发成功

c. 在primary_display_esd_check_worker_kthread中增加复位延时的时间,确保有足够的时间能使0x0a恢复正常之后再进行下一次esd check

解决办法主要是c,另外中间的延时可以具体细调,不能延时过高影响用户体验,也不能延时太低再出现esd recovery fail。

6.Himax TP的ESD读寄存器只支持mipi长包,需要识别各个寄存器返回多个值,参考FAQ13728在ddp_dsi.c的ESD_CMP处添加修改。

Linux usb子系统(一):子系统架构

一、USB协议基础知识

前序:USB概念概述

  USB1.0版本速度1.5Mbps(低速USB) USB1.1版本速度12Mbps(全速USB) USB2.0版本速度480Mbps(高速USB)。

  USB 分为主从两大体系,一般而言, PC 中的 USB 系统就是作主,而一般的 USB 鼠标, U 盘则是典型的 USB 从系统。

  USB主控制器这一块,我们至少要开发出 USB 的主控制器与从控制器,鼠标是低速设备,所需的是最简单的一类从控制器。主控制器则复杂得多,因为太过于复杂了,所以就形成了一些标准。在一个复杂的系统中,标准的好处就是可以让开发者把精力集中在自己负责的一块中来,只需要向外界提供最标准的接口,而免于陷于技术的汪洋大海中。

  USB 主控制器主要有 1.1 时代的 OHCI 和 UHCI , 2.0 时代的 EHCI ,这些标准规定了主控制器的功能和接口(寄存器的序列及功能),对我们驱动工程师而言,这样的好处就是只要你的驱动符合标某一标准,你就能轻而易举的驱动所有这个标准的主控制器。要想把主控制器驱动起来,本来是一件很难的事情,估计全球的 IT 工程师没几个能有这样的水平,但有了标准,我们就可以轻松的占有这几个高水平的 IT 工程师的劳动成果。

  主控制器和驱动有了,我们还需要 USB 协议栈,这就是整个 USB 系统的软件部分的核心(有的资料中直接把其称为 USB 核心), USB 协议栈一方面向使用 USB 总线的设备驱动提供操作 USB 总线的 API ,另一方面则管理上层驱动传下来的的数据流,按 USB 主控制器的要求放在控制器驱动规定的位置, USB 主控制器会调度这些数据。

  我们这里用到了调度这个词, USB 主控制器的调度其实和火车的调度 CPU 的调度有相似之处,物理上的通路只有一条,但 USB 中规定的逻辑上的通路却有许多条,有时一个设备就会占用几条逻辑通道,而 USB 系统中又会有多个设备同时运行。这就好像是只有一条铁路线,但来来往往的火车却有许多, USB 主控制器的作用就是调度这些火车,而 USB 协议栈的作用则向上层的 USB 设备驱动提供不同的车次。

  有了以上的这些模块,才能为 USB 鼠标设计驱动,这一点上 ps/2 鼠标的驱动和 USB 鼠标的驱动结构基本一样,只不过我们的数据通路是 USB 总线。

  USB 系统甚至把设备驱动都给标准化了,只要是支持 USB 的主机,就可以支持任何一个厂商的 USB 鼠标,任何一个厂商的 U 盘,只要是被 USB 系统包函的设备,只要这些设备支持相应的标准,就无需重新设计驱动而直接使用。

  下是简单的列出了 USB 设备类型,理想的情况 USB 系统要对这些设备作完整的支持,设备也必须符合 USB 规范中的要求。

1 - audio :表示一个音频设 备。

2 - communication device :通讯设备,如电话, moden 等等。

3 - HID :人机交互设备,如键盘,鼠标等。

6 - image 图象设备,如扫描仪,摄像头等,有时数码相 机也可归到这一类。

7 -打印机类。如单向,双向打印机等。

8 - mass storage 海量存储类。所有带有一定存储功能的都可以归到这一类。如数码相机大多数都归这一类。

9 - hub 类。

11 - chip card/smart card 。

13 -- Content Security

14 -- Video ( Interface )

15 -- Personal Healthcare

220 -- Diagnostic Device

224 -- Wireless Controller ( Interface )

239 -- Miscellaneous

254 -- Application Specific ( Interface )

255 - vendor specific. 厂家的自定义类,主要用于一些特殊的设备。如接口转接卡等。

  随着 USB 技术的发展, USB 系统中的一些不足也逐渐被承认, OTG 就是这种情况下的主要产物。

  现在市面上有些设备(比如一些 MP4 )即能插上电脑当 U 盘使,也能被 U 盘插上读取 U 盘。这样的设备在 USB 系统中是作主还是作从呢?

  这就是 OTG(On-The-Go), 即可以作主也可以作从,传说中的雌雄同体。这主要是为嵌入式设备准备的,因为 USB 是一种主从系统,不能支持点对点平等的传输数据, OTG 正是在这种需求下产生的, OTG 不仅支持控制器的主从切换,在一定层度上,也支持相同设备之间的数据交换。

1、USB的传输线结构

一条USB的传输线分别由地线、电源线、D+、D-四条线构成,D+和D-是差分输入线(抗干扰),它使用的是3.3V的电压,而电源线和地线可向设备提供5V电压,最大电流为500MA。OTG 的做法就是增来一个 ID pin 来判断设备是接入设备的是主还是从。vbus 主要是供电, D+/D- 则是用来传输数据,就是我们前面所讲的主设备和从设备间唯一的一条铁路。

信号线名称

颜色

1

Vbus

2

D-

3

D+

绿

4

GNU

shell (金属壳)

屏敝层

2、USB可以热插拔的硬件原理

  USB主机是如何检测到设备的插入的呢?首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

3、USB主机控制器

  USB主机控制器属于南桥芯片的一部分,通过PCI总线和处理器通信。USB主机控制器分为UHCI(英特尔提出)、OHCI(康柏和微软提出)、 EHCI。其中OHCI驱动程序用来为非PC系统上以及带有SiS和ALi芯片组的PC主办上的USB芯片提供支持。UHCI驱动程序多用来为大多数其他PC主板(包括Intel和Via)上的USB芯片提供支持。ENCI兼容OHCI和UHCI。UHCI的硬件线路比OHCI简单,所以成本较低,但需要较复杂的驱动程序,CPU负荷稍重。主机控制器驱动程序完成的功能主要包括:解析和维护URB,根据不同的端点进行分类缓存URB;负责不同USB传输类型的调度工作;负责USB数据的实际传输工作;实现虚拟跟HUB的功能。

4、USB设备的构成

  USB设备的构成包括了配置,接口和端点。

  1. 设备通常具有一个或者更多个配置

  2. 配置经常具有一个或者更多个接口

  3. 接口通常具有一个或者更多个设置

  4. 接口没有或者具有一个以上的端点

  需要注意的是,驱动是绑定到USB接口上,而不是整个设备。

5、主控制怎么正确访问各种不同的USB设备

  每一个USB设备接入PC时,USB总线驱动程序都会使用默认的地址0(仅未分配地址的设备可以使用)跟USB设备通信,然后给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)就可以了。

  USB总线驱动程序获取USB设置信息。USB设备里都会有一个叫 EEPROM的东东,它就是用来存储设备本身信息的。它与Flash虽说都是要电擦除的,但它可以按字节擦除,Flash只能一次擦除一个 block。

6、usb-firmware简易框架

  usb firmware主要工作是满足usb 协议所定义的标准请求(usb协议第9章第4节),不同的firmware因为硬件不同而操作有所不同,但目的都是完成主控制器对设备的标准请求,大致框图如下:

7、USB传输事务

  USB通信最基本的形式是通过一个名为端点(endpoint)的东西。它是真实存在的。

  端点只能往一个方向传送数据(端点0除外,端点0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT。除了端点0,低速设备只能有2个端点,高速设备也只能有15个IN端点和15个OUT端点。

  主机和端点之间的数据传输是通过管道。

  端点只有在device上才有,协议说端点代表在主机和设备端点之间移动数据的能力。

  USB通信都是由host端发起的。

  首先明确一点USB协议规定所有的数据传输都必须由主机发起。所以这个传输的一般格式:令牌包(表明传输的类型),数据包(实际传输的数据),握手包(数据的正确性)。首先是由主机控制器发出令牌包,然后主机/设备发送数据包,甚至可以没有,最后设备/主机发送握手包,这么一个过程就叫做一个USB传输事务。一个USB传输事务就实现了一次从主机和设备间的通讯。USB的事务有:OUT、IN、SETUP事务。

  令牌包:可分为OUT包、IN包、SetUp包和帧起始包,OUT包就是说明接下来的数据包的方向时从主机到设备。

  数据包:里面包含的就是我们实际要传输的东东了 。

  握手包:发送方发送了数据,接受方收没收到是不是该吱个声呀。

  一个数据包里面包含有很多的域,里面包含了很多信息,一般有同步的域,数据包的核心信息的域,数据校验的域。

  令牌包:SYNC + PID + ADDR + ENDP + CRC5 :(同步) + (IN/OUT/SetUp) + (设备地址)+(设备端点) + (校验)

  数据包:分为DATA0包和DATA1包,当USB发送数据的时候,当一次发送的数据长度大于相应端点的容量时,就需要把数据包分为好几个包,分批发送,DATA0包和DATA1包交替发送,即如果第一个数据包是 DATA0,那第二个数据包就是DATA1。

      SYNC + PID + DATA0/1 + CRC5:(同步) + (DATA0/1) + (数据) + (校验)。

      但也有例外情况,在同步传输中(四类传输类型中之一),所有的数据包都是为DATA0,格式如下: SYNC + PID + 0~1023字节 + CRC16:(同步) + (DATA0) + (数据) + (校验)。

握手包:SYNC+PID:(同步)+(HandShake)

8、USB协议的四种传输类型

  因为usb支持的设备实在是太多,而且不同的设备对于传输数据各有各的要求和这就导致了我们需要不同的传输方式。USB支持4种传输方式:控制传输;批量传输;中断传输;实(等)时传输。

  控制传输:首先发送 Setup 传输事务,然后IN/OUT传输事务,最后是 STATUS transaction,向主机汇报前面SETUP 和 IN/OUT阶段的结果。控制传输主要用于向设备发送配置信息、获取设备信息、发送命令道设备,或者获取设备的状态报告。控制传输一般发送的数据量较小,当USB设备插入时,USB核心使用端点0对设备进行配置,另外,端口0与其他端点不一样,端点0可以双向传输。

  批量传输:由OUT事务和IN事务构成,用于大容量数据传输,没有固定的传输速率,也不占用带宽,当总线忙时,USB会优先进行其他类型的数据传输,而暂时停止批量转输。批量传输通常用在数据量大、对数据实时性要求不高的场合,例如USB打印机、扫描仪、大容量存储设备、U盘等。

  中断传输:由OUT事务和IN事务构成,中断传输就是中断端点以一个固定的速度来传输较少的数据,USB键盘和鼠标就是使用这个传输方式。这里说的中断和硬件上下文中的中断不一样,它不是设备主动发送一个中断请求,而是主机控制器在保证不大于某个时间间隔内安排一次传输。中断传输对时间要求比较严格,所以可以用中断传输来不断地检测某个设备,当条件满足后再使用批量传输传输大量的数据。

  等时传输:由OUT事务和IN事务构成,有两个特殊地方,第一,在同步传输的IN和OUT事务中是没有握手阶段;第二,在数据包阶段所有的数据包都为DATA0 。等时传输同样可以传输大批量数据,但是对数据是否到达没有保证,它对实时性的要求很高,例如音频、视频等设备(USB摄像头,USB话筒)。

  这4种传输方式由4个事务组成:

  IN事务:IN事务为host输入服务,当host需要从设备获得数据的时候,就需要IN事务。

  OUT事务:OUT事务为host输出服务,当host需要输出数据到设备的时候,就需要OUT事务。

  SETUP事务:SETUP事务为host控制服务,当host希望传输一些USB规范的默认操作的时候就需要使用setup事务。

  SOF事务:这个用于帧同步。

  然后这4种事务又由3类包(token包,handshake包,data包)组成,每类又分几种:

  in包:in包用于指明当前的事务为in类型的。

  out包: out包用于指明当前事务为out类型的。

  setup包: setup包指明当前事务为setup类型的。

  sof包: sof包指明当前事务为setup类型的。

  ack包:ack握手包指明当前的事务的数据包传输是成功的。

  nak包:nak握手包指明当前设备忙,不能处理数据包,请主机稍后再次发送。

  stall包:stall握手包指明当前设备不能接受或者传输数据,表示一个严重的错误。

  data0包:该数据包的类型为0。

  data1包:该数据包的类型为1。

  下图是一个USB鼠标插入Linux系统时完整的枚举过程,一共发生了11次传输,每次传输包括几个事务,每个事务又包括几个包,每个包包括几个域。

  这里有一个概念需要注意,这里的中断传输与硬件中断那个中断是不一样的,这个中断传输实际是靠USB host control轮询usb device来实现的,而USB host control对于CPU则是基于中断的机制。

  拿USB鼠标为例,USB host control对USB鼠标不断请求,这个请求的间隔是很短的,在USB spec Table 9-13端点描述符中的bInterval域中指定的,当鼠标发生过了事件之后,鼠标会发送数据回host,这时USB host control中断通知CPU,于是usb_mouse_irq被调用,在usb_mouse_irq里,就可以读取鼠标发回来的数据,当读完之后,驱动再次调用usb_submit_urb发出请求,就这么一直重复下去,一个usb鼠标的驱动也就完成了。

  下面是USB鼠标中断传输图,可以看到USB host control向usb device发送了IN包,没有数据的时候device回复的是NAK,有数据的时候才向host control发送DATA包。

9、USB设备被识别的过程

  当USB设备插上主机时,主机就通过一系列的动作来对设备进行枚举配置。

  1、接入态(Attached):设备接入主机后,主机通过检测信号线上的电平变化来发现设备的接入;

  2、供电态(Powered):就是给设备供电,分为设备接入时的默认供电值,配置阶段后的供电值(按数据中要求的最大值,可通过编程设置)

  3、缺省态(Default):USB在被配置之前,通过缺省地址0与主机进行通信;

  4、地址态(Address):经过了配置,USB设备被复位后,就可以按主机分配给它的唯一地址来与主机通信,这种状态就是地址态;

  5、配置态(Configured):通过各种标准的USB请求命令来获取设备的各种信息,并对设备的某此信息进行改变或设置。

  6、挂起态(Suspended):总线供电设备在3ms内没有总线动作,即USB总线处于空闲状态的话,该设备就要自动进入挂起状态,在进入挂起状态后,总的电流功耗不超过280UA。

10、标准的USB设备请求命令

  USB设备请求命令是在控制传输的第一个阶段:setup事务传输的数据传输阶段发送给设备的。

  标准USB设备请求命令共有11个,大小都是8个字节,具有相同的结构,由5 个字段构成。通过标准USB准设备请求,我们可以获取存储在设备EEPROM里面的信息;知道设备有哪些的设置或功能;获得设备的运行状态;改变设备的配置等。

  标准USB准设备请求 = bmRequestType(1) + bRequest(2) + wvalue(2) + wIndex(2) + wLength(2)

  bmRequestType:

    [7 bit]= 0主机到设备; 1设备到主机

    [6-5 bit]= 00标准请求命令; 01类请求命令; 10用户定义命令; 11保留

    [4-0 bit]= 00000 接收者为设备; 00001 接收者为接口; 00010 接收者为端点; 00011 接收者为其他接收者; 其他 其他值保留

  bRequest:

    0) 0 GET_STATUS:用来返回特定接收者的状态

    1) 1 CLEAR_FEATURE:用来清除或禁止接收者的某些特性

    2) 3 SET_FEATURE:用来启用或激活命令接收者的某些特性

    3) 5 SET_ADDRESS:用来给设备分配地址

    4) 6 GET_DEscriptOR:用于主机获取设备的特定描述符

    5) 7 SET_DEscriptOR:修改设备中有关的描述符,或者增加新的描述符

    6) 8 GET_CONFIGURATION:用于主机获取设备当前设备的配置值、

    7) 9 SET_CONFIGURATION:用于主机指示设备采用的要求的配置

    8) 10 GET_INTERFACE:用于获取当前某个接口描述符编号

    9) 11 SET_INTERFACE:用于主机要求设备用某个描述符来描述接口

    10) 12 SYNCH_FRAME:用于设备设置和报告一个端点的同步

  wvalue: 这个字段是 request 的参数,request 不同,wValue就不同。

  wIndex:wIndex,也是request 的参数,bRequestType指明 request 针对的是设备上的某个接口或端点的时候,wIndex 就用来指明是哪个接口或端点。

  wLength:控制传输中 DATA transaction 阶段的长度。

二、Linux USB系统架构

这个是USB系统的拓扑图,4个部分构成:USB主机控制器,根集线器,集线器,设备。其中Root Hub与USB主机控制器是绑定在一起的。

  在机箱的尾部面板上,物理上存在一,二或四个USB端口。端口可以用来连接一个普通设备或者一个hub,hub是一个USB设备,可以用来扩展连接USB设备的端口数量。最大连接USB设备数量是减去连在总线上的hub数量(如果有50个hub,那么最多77(=127-50)个设备能够连接),剩下的就是能够连接USB设备的数量。Hub总是高速的,如果一个hub是自供电的,那么任何设备都能够附着到上面。但是如果hub是总线供电的,那么仅仅低供电(最大100mA)设备能够附着到上面,一个总线供电的hub不应该连接到另一个总线供电的hub-你应该在总线供电和自供电间交替.

  通常情况下主机控制器的物理端口由一个虚拟的root hub脸管理。这个hub是有主机控制器(host controller)的设备驱动虚拟的,用来统一管理总线拓扑,因此USB子系统的驱动能够用同样的方法管理每个端口。

  USB通信都是由host端发起的。USB设备驱动程序分配并初始化一个URB发给USB Core,USB Core改一改,发给USB主机控制器驱动,USB主机控制器驱动把它解析成包,在总线上进行传送。

  USB Core是由内核实现的,其实也就是把host control driver里的功能更集中的向上抽象了一层,它是用来对最上层的USB设备驱动屏蔽掉host control的不同。

  USB通信最基本的形式是通过一个名为端点(endpoint)的东西。它是真实存在的。端点只能往一个方向传送数据(端点0除外,端点0使用message管道,它既可以IN又可以OUT),或者IN,或者OUT(前面已经介绍过)。除了端点0,低速设备只能有2个端点,高速设备也只能有15个IN端点和15个OUT端点。主机和端点之间的数据传输是通过管道。端点只有在device上才有,协议说端点代表在主机和设备端点之间移动数据的能力。

  Linux系统下的usb部分分为四个部门或者叫做四大家族,他们是host控制器驱动、hub驱动、usb core、设备类驱动,他们共同配合着完成了对usb设备的访问操作。

枚举和设备描述符

  每当一个USB设备附着到总线上,它将会被USB子系统枚举.也就是分配唯一的设备号(1-127)然后读取设备描述符.描述符是一个包含关于设备的信息和属性的数据结构.USB标准定义了一个描述符层次结构,下图所示:

标准描述符

  设备描述符: 描述USB设备的大概信息,其中包括适用于设备的全局信息,所有设备的配置。一个USB设备只有一个设备描述符。

  配置描述符: 描述了特定的设备配置信息。一个USB设备可以有一或多个配置描述符。每个配置有一个或多个接口(interface),并且每个接口有零或多个端点(endpoint)。一个端点在一个单独的配置下,是不和其他的接口共享的,但是一个单独的接口对于同一个端点能够有几种可选的配置。端点可以没有限制的在一部分不同的配置下的接口间共享。配置仅仅能够通过标准的控制传输set_configuration来激活。不同的配置能够用来全局配置信息,例如供电消耗。

  接口描述符: 描述了一个配置内的特定接口。一个配置提供一个或多个接口,每个接口带有零个或多个端点描述符描述了在配置内的唯一配置。一个可以包含可选的配置的接口使得配置好的端点和/或他们的特性能够多种多样。默认的接口设置总是设置为零。可替换的设置能够在标准控制传输的set_interface来选择一个。例如一个多功能设备带有话筒的摄像头,可以有三种可用的配置来改变分配在总线上的带宽。

Camera activated
Microphone activated
Camera and microphone activated
  端点描述符: 包含主机用来决定每个端点带宽的信息。一个端点象征一个USB设备的逻辑数据源或接收端(logic data source or sink)。端点零是用来所有的控制传输并且该端点没有设备描述符。USB spec交替使用pipe和endpoint术语。

  字符串描述符: 是可选项,提供了unicode编码的额外的可读信息。他们可以是厂商和设备名称或序列号。

设备类型

  标准的设备和接口描述符包含有关分类的内容:class, sub-class和protocol。这些字段主机可以用来设备或接口和驱动联系。依赖于分类说明是如何指定的?对于class字段和接口描述符的合法字段是由USB Device Working Group来定义的。

  在Class Specification中将设备或接口分组归类并指定特性,这样就使得主机开发软件能够基于这个类别进行管理多种多样的实现。这样的主机软件通过设备中的描述信息将操作方法绑定到指定的设备。一个类别规格作为所有的该类别的设备或接口的最小操作框架服务。(PS:也就是说,所有该类别的设备或接口,都是以类别规格定义为接口框架。)

人机接口设备

  HID分类,主要是包含人们控制计算机系统的设备。典型的HID分类设备包含:

  键盘和鼠标设备例如:标准的鼠标设备,追踪球,游戏手柄。

  前端面板控制 例如:旋钮,开关,按键,滚动器。

  可能在电话设备,远端控制VCR,游戏或模拟设备上存在控制器。

再了解一下USB驱动框架:

  USB总线和USB设备使用软件进行抽象描述起来是非常复杂的,一方面是协议使然,一方面也是因为它们使用太广泛了,抽象时考虑很太多情况。幸运的是,内核开发者们抽象出来的内核USB 子系统把很多复杂性都隐藏了。

针对上面这幅图,为了理解什么是USB子系统,我们要做以下说明:
  a) USB 驱动都是夸kernel子系统的,因为最终USB设备是要通过BLCOCK 或CHAR设备的方式呈现给我们的,所以USB Driver之上还有一层。
  b) USB driver利用USB Core提供的API来简单优雅的完成驱动工作,这里USB Core抽象了复杂的USB协议。
  c) 主机控制器驱动位于USB软件的最下层,提供主机控制器硬件的抽象,隐藏硬件的细节,在主机控制器之下是物理的USB及所有与之连接的USB设备。主机控制器驱动只和USB Core进行关联,USB Core将用户的请求映射到相关的主机控制器驱动,从而使用户无需去访问主机控制器。
  d) USB Core和USB主机控制器驱动就构成了我们的USB子系统,USB Core负责实现一些核心的功能,例如协议之类,提供一个用于访问和控制USB硬件的接口,使设备驱动不用去考虑系统当前使用哪种主机控制器。自从有了USB子系统,写USB驱动的时候,只需要调用USB Core export的接口,就几乎能完成所有工作。
  e) USB总线将USB设备和USB驱动关联起来。

USB子系统初始化

  usb初始化函数定义在内核源码(2.6.37)drivers/usb/core/usb.c:

复制代码
/*

  • Init
    */
    static int __init usb_init(void)
    {
    int retval;
    if (nousb) {

    pr_info("%s: USB support disabled\n", usbcore_name);
    return 0;
    

    }

    retval = usb_debugfs_init();
    if (retval)

    goto out;
    

    retval = bus_register(&usb_bus_type);
    if (retval)

    goto bus_register_failed;
    

    retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
    if (retval)

    goto bus_notifier_failed;
    

    retval = usb_major_init();
    if (retval)

    goto major_init_failed;
    

    retval = usb_register(&usbfs_driver);
    if (retval)

    goto driver_register_failed;
    

    retval = usb_devio_init();
    if (retval)

    goto usb_devio_init_failed;
    

    retval = usbfs_init();
    if (retval)

    goto fs_init_failed;
    

    retval = usb_hub_init();
    if (retval)

    goto hub_init_failed;
    

    retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
    if (!retval)

    goto out;
    

    usb_hub_cleanup();
    hub_init_failed:
    usbfs_cleanup();
    fs_init_failed:
    usb_devio_cleanup();
    usb_devio_init_failed:
    usb_deregister(&usbfs_driver);
    driver_register_failed:
    usb_major_cleanup();
    major_init_failed:
    bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
    bus_notifier_failed:
    bus_unregister(&usb_bus_type);
    bus_register_failed:
    usb_debugfs_cleanup();
    out:
    return retval;
    }

subsys_initcall(usb_init);
复制代码
usb_debugfs_init():

  DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。类似的虚拟文件系统还有procfs和sysfs等,这几种虚拟文件系统都并不实际存储在硬盘上,而是Linux内核运行起来后,执行mount -t debugfs none /media/mmcblk0p2/ 才建立起来。在/media/mmcblk0p2/目录下创建usb目录并在下面创建devices文件。

  当我们执行cat devices会调用usbfs_devices_fops->read(usb_device_read)函数去搜寻usb_bus_list链表下的usb设备信息,也就是所有总线下的设备。

bus_register:

  是将usb总线注册到系统中,总线可是linux设备模型中的领导者,不管是多大的领导,也是领导,如PCI、USB、I2C,即使他们在物理上有从属关系,但是在模型的世界里,都是总线,拥有一样的待遇,所以任何一个子系统只要管理自己的设备和驱动,就需要向内核注册一个总线,注册报到。

bus_register_notifier:

  大多数内核子系统都是相互独立的,因此某个子系统可能对其它子系统产生的事件感兴趣。为了满足这个需求,也即是让某个子系统在发生某个事件时通知其它的子系统,Linux内核提供了通知链的机制。通知链表只能够在内核的子系统之间使用,而不能够在内核与用户空间之间进行事件的通知。

  通知链表是一个函数链表,链表上的每一个节点都注册了一个函数。当某个事情发生时,链表上所有节点对应的函数就会被执行。所以对于通知链表来说有一个通知方与一个接收方。在通知这个事件时所运行的函数由被通知方决定,实际上也即是被通知方注册了某个函数,在发生某个事件时这些函数就得到执行。其实和系统调用signal的思想差不多。

  bus_register->BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier),已经初始化了usb_bus_type->p->bus_notifier通过blocking_notifier_chain_register函数注册到通知链表。

  那什么时候usb总线收到通知呢?

  当总线发现新的设备调用device_add->blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev)

  当总线卸载设备时调用device_del->blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DEL_DEVICE, dev);

  则调用usb_bus_nb的回调成员函数notifier_call(usb_bus_notify),函数定义如下:

复制代码
/*

  • Notifications of device and interface registration
    /
    static int usb_bus_notify(struct notifier_block
    nb, unsigned long action,

    void *data)
    

    {
    struct device *dev = data;

    switch (action) {
    case BUS_NOTIFY_ADD_DEVICE:

    if (dev->type == &usb_device_type)//usb 设备   
        (void) usb_create_sysfs_dev_files(to_usb_device(dev)); //创建descriptors文件
    else if (dev->type == &usb_if_device_type) //usb接口
        (void) usb_create_sysfs_intf_files(
                to_usb_interface(dev));//创建interface文件
    break;
    

    case BUS_NOTIFY_DEL_DEVICE:

    if (dev->type == &usb_device_type)//usb设备
        usb_remove_sysfs_dev_files(to_usb_device(dev));//删除descriptors文件
    else if (dev->type == &usb_if_device_type)//usb接口
        usb_remove_sysfs_intf_files(to_usb_interface(dev));//删除interface文件
    break;
    

    }
    return 0;
    }
    复制代码
    usb_major_init:注册字符设备,主设备号180。

    usb_register(&usbfs_driver):

复制代码
struct usb_driver usbfs_driver = {
.name = “usbfs”,
.probe = driver_probe,
.disconnect = driver_disconnect,
.suspend = driver_suspend,
.resume = driver_resume,
};
复制代码
  usb_register->usb_register_driver():

复制代码
/**

  • usb_register_driver - register a USB interface driver
  • @new_driver: USB operations for the interface driver
  • @owner: module owner of this driver.
  • @mod_name: module name string
    *
  • Registers a USB interface driver with the USB core. The list of
  • unattached interfaces will be rescanned whenever a new driver is
  • added, allowing the new driver to attach to any recognized interfaces.
  • Returns a negative error code on failure and 0 on success.
    *
  • NOTE: if you want your driver to use the USB major number, you must call
  • usb_register_dev() to enable that functionality. This function no longer
  • takes care of that.
    /
    int usb_register_driver(struct usb_driver
    new_driver, struct module *owner,

    const char *mod_name)
    

    {
    int retval = 0;

    if (usb_disabled())

    return -ENODEV;
    

    new_driver->drvwrap.for_devices = 0;
    new_driver->drvwrap.driver.name = (char *) new_driver->name;
    new_driver->drvwrap.driver.bus = &usb_bus_type;
    new_driver->drvwrap.driver.probe = usb_probe_interface;
    new_driver->drvwrap.driver.remove = usb_unbind_interface;
    new_driver->drvwrap.driver.owner = owner;
    new_driver->drvwrap.driver.mod_name = mod_name;
    spin_lock_init(&new_driver->dynids.lock);
    INIT_LIST_HEAD(&new_driver->dynids.list);

    retval = driver_register(&new_driver->drvwrap.driver);
    if (retval)

    goto out;
    

    usbfs_update_special();

    retval = usb_create_newid_file(new_driver);
    if (retval)

    goto out_newid;
    

    retval = usb_create_removeid_file(new_driver);
    if (retval)

    goto out_removeid;
    

    pr_info(“%s: registered new interface driver %s\n”,

    usbcore_name, new_driver->name);
    

out:
return retval;

out_removeid:
usb_remove_newid_file(new_driver);
out_newid:
driver_unregister(&new_driver->drvwrap.driver);

printk(KERN_ERR "%s: error %d registering interface "
        "    driver %s\n",
        usbcore_name, retval, new_driver->name);
goto out;

}
EXPORT_SYMBOL_GPL(usb_register_driver);
复制代码
其余功能如下:

  1> driver_register实现。后面会详细分析。

  2> usbfs_update_special(): 跟usb文件系统相关,看下面的usbfs_init分析。

  3> usb_create_newid_file(): 创建newid属性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根据传入的ID值,增加一个新的动态usb设备到驱动(这里是usbfs),引起驱动重新探测所有的设备。

  4> usb_create_removeid_file():创建removeid属性文件,在/sys/bus/usb/drivers/usbfs/下面可以看到此文件。根据传入的ID值,删除驱动(这里是usbfs)里的一个usb设备。

  5> 输出信息:usbcore: registered new interface driver usbfs

现在分析driver_register功能:

  1> 首先判断,些驱动所属bus的subsys_private结构有没有初始化。如果没有,报bug信息。

  2> 判断需要注册的driver和driver所属的bus是否都有probe, remove, shutdown函数。如有,打印kernel warning信息。

  3> 判断此driver已经在driver所属的bus上面注册过了。如果注册过了,打印错误信息,并返回。

  4> 调用bus_add_driver来注册driver。

  5> 调用driver_add_groups来添加组属性。

最后对bus_add_driver进行分析。

复制代码
/**

  • bus_add_driver - Add a driver to the bus.
  • @drv: driver.
    /
    int bus_add_driver(struct device_driver
    drv)
    {
    struct bus_type bus;
    struct driver_private
    priv;
    int error = 0;

    bus = bus_get(drv->bus);
    if (!bus)

    return -EINVAL;
    

    pr_debug(“bus: ‘%s’: add driver %s\n”, bus->name, drv->name);

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv) {

    error = -ENOMEM;
    goto out_put_bus;
    

    }
    klist_init(&priv->klist_devices, NULL, NULL);
    priv->driver = drv;
    drv->p = priv;
    priv->kobj.kset = bus->p->drivers_kset;
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,

    "%s", drv->name);
    

    if (error)

    goto out_unregister;
    

    if (drv->bus->p->drivers_autoprobe) {

    error = driver_attach(drv);
    if (error)
        goto out_unregister;
    

    }
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent);
    if (error) {

    printk(KERN_ERR "%s: uevent attr (%s) failed\n",
        __func__, drv->name);
    

    }
    error = driver_add_attrs(bus, drv);
    if (error) {

    /* How the hell do we get out of this pickle? Give up */
    printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
        __func__, drv->name);
    

    }

    if (!drv->suppress_bind_attrs) {

    error = add_bind_files(drv);
    if (error) {
        /* Ditto */
        printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
            __func__, drv->name);
    }
    

    }

    kobject_uevent(&priv->kobj, KOBJ_ADD);
    return 0;

out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
复制代码
其功能是向bus中添加一个driver。

  1> bus_get():bus的计数加1;

  2> kzalloc,分配driver_private内存空间。

  3> 初始化此driver的klist_devices链表。

  4> kobject_init_and_add():在/sys/bus/usb/drivers/下面创建usbfs文件夹。

  5> 如果总线支持drivers_autoprobe,调用driver_attach。(USB 总线支持)

  6> driver_create_file: 在/sys/bus/usb/drivers/usbfs下面创建uevent属性文件。

  7> driver_add_attrs():将总线的属性也加到/sys/bus/usb/drivers/usbfs

  8> add_bind_files():在/sys/bus/usb/drivers/usbfs创建bind和unbind属性文件。

  9> kobject_uevent():发送一个KOBJ_ADD的事件。

在/sys/bus/usb/drivers/usbfs下面的文件:

bind module new_id remove_id uevent unbind

usb_devio_init:注册字符设备,主设备189。

usbfs_init:

复制代码
int __init usbfs_init(void)
{
int retval;

retval = register_filesystem(&usb_fs_type);
if (retval)
    return retval;

usb_register_notify(&usbfs_nb);

/* create mount point for usbfs */
usbdir = proc_mkdir("bus/usb", NULL);

return 0;

}
复制代码
函数功能:

  1> register_filesystem注册usbfs文件系统,当应用程序执行mount命令的时候,挂载文件系统到相应的目录。

  2> usb_register_notify函数注册到内核通知链表,当收到其他子系统通知,调用notifier_call回调函数usbfs_notify:

复制代码
static int usbfs_notify(struct notifier_block self, unsigned long action, void dev)
{
switch (action) {
case USB_DEVICE_ADD:
usbfs_add_device(dev);//在bus号创建的目录下,根据设备号创建设备文件
break;
case USB_DEVICE_REMOVE:
usbfs_remove_device(dev);//删除bus号创建的目录下的设备文件
break;
case USB_BUS_ADD:
usbfs_add_bus(dev);//根据bus号创建目录
break;
case USB_BUS_REMOVE:
usbfs_remove_bus(dev);//删除bus号创建的目录
}

usbfs_update_special();//更新文件系统节点
usbfs_conn_disc_event();

return NOTIFY_OK;

}
复制代码
  static BLOCKING_NOTIFIER_HEAD(usb_notifier_list);usb_notifier_list通知链表初始化

  usb_register_notify->blocking_notifier_chain_register(&usb_notifier_list, nb):向usb_notifier_list通知链表注册

  blocking_notifier_call_chain(&usb_notifier_list, USB_DEVICE_ADD, udev):通知有usb设备增加
  blocking_notifier_call_chain(&usb_notifier_list,USB_DEVICE_REMOVE, udev):通知有usb设备移除
  blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_ADD, ubus):通知有usb总线增加
  blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_REMOVE, ubus):通知有usb总线移除

  3> proc_mkdir在/proc/bus/目录下创建usb目录。

usb_register_device_driver:

在了解usb_generic_driver驱动前,先分析usb总线的match函数:

复制代码
static int usb_device_match(struct device dev, struct device_driver drv)
{
/ devices and interfaces are handled separately /
if (is_usb_device(dev)) {

    /* interface drivers never match devices */
    if (!is_usb_device_driver(drv))
        return 0;

    /* TODO: Add real matching code */
    return 1;

} else if (is_usb_interface(dev)) {
    struct usb_interface *intf;
    struct usb_driver *usb_drv;
    const struct usb_device_id *id;

    /* device drivers never match interfaces */
    if (is_usb_device_driver(drv))
        return 0;

    intf = to_usb_interface(dev);
    usb_drv = to_usb_driver(drv);

    id = usb_match_id(intf, usb_drv->id_table);
    if (id)
        return 1;

    id = usb_match_dynamic_id(intf, usb_drv);
    if (id)
        return 1;
}

return 0;

}

  函数中我们分成两类判断:

  is_usb_device(),根据设备类型dev->type == &usb_device_type 来判断是否是usb设备,然后在通过for_devices(usb_register_device_driver函数注册的时候设置为1) 判断驱动是否是usb设备设备驱动,如果成功,则设备和设备驱动匹配,调用相应的驱动的probe函数(因为usb总线没有probe成员函数)。

  is_usb_interface(),根据设备类型dev->type == &usb_if_device_type 来判断是否是接口,然后在通过for_devices(usb_register函数注册的时候设置为0) 判断驱动是否是接口驱动,如果是接口驱动(所以调用usb_register都是注册的接口驱动,因为一个设备可以有多个接口,每个接口必须独立驱动),接着usb_match_id这个函数就是用来判断这个接口是否在id table中得到了match,一旦得到,就进入了具体接口驱动的probe函数了。。

  到这里我们不禁要思索驱动找到了注册的地方,那设备来自哪里?这里也有两个函数要分析:

  usb_alloc_dev():dev->dev.type = &usb_device_type,这里就表示了是usb设备,这个函数主要有两个地方调用。一个就是usb_init->usb_hub_init->hub_thread->hub_events->hub_port_connect_change,这个会在下面进行详细的分析;另外一个musb_probe->musb_init_controller->usb_add_hcd,DM8168芯片注册主控器的时候用到(或者其他芯片主控器注册)。

  usb_set_configuration(): intf->dev.type = &usb_if_device_type,这里就表示了是接口。

  这里我们知道usb_register 和 usb_register_device_driver,一个是设备驱动的注册,一个是接口驱动的注册,match的时候通过for_devices来区分。接口指的就是一种具体的功能。

上面我们提过每种类型的总线都有一套自己的驱动函数,看来在usb的世界里更特殊一些,usb总线下的设备驱动有一套,接口驱动也有一套:usb_probe_interface。

不管是设备还是接口都是挂在总线上的,一个总线只有一个match函数,usb_device_match。

  在这个usb的match函数里,首先是对usb设备的match,设备的match很简单的,只要是个usb设备就认为match了,因为现在进来的usb设备统统都认为是usb_generic_driver的,都和他match。上面我们提到过这个,所有的usb设备首先都会经过筛选这一关,处理之后,才有重生的机会。接口就不一样了,如果进来的dev不是设备,就认为是个接口,然后判断drv是否为接口驱动,如果是,那么就继续判断,这个判断机制就是usb特有的了:Id。每个接口驱动注册的时候都会有一个id 的,加到了id table表中。

  看了上面分析,usb match函数中涉及到的设备和接口驱动两条判断路线,在usb的世界里,真正的驱动是针对接口的,针对设备的其实是刚开始没有配置之前,一个通用的usb设备驱动,用来处理所有的usb设备,将其进入配置态,获取该配置下的各种接口,并将接口作为一种特殊的usb设备(接口设备)添加到设备模型中。

  下面我们分析usb_generic_driver:

struct usb_device_driver usb_generic_driver = {
.name = “usb”,
.probe = generic_probe,
.disconnect = generic_disconnect,

#ifdef CONFIG_PM
.suspend = generic_suspend,
.resume = generic_resume,

#endif
.supports_autosuspend = 1,
};

  当USB设备(只有设备先被注册之后才会分析接口,才会注册接口) 被探测并被注册到系统后(用device_add),会调用usb_bus_type.mach()(只要是usb设备,都会跟usb_generic_driver匹配上),之后会调用usb_probe_device(),从而引发usb_generic_driver的 probe()调用,也就是generic_probe函数。

  下面将会对generic_probe函数进行分析:

static int generic_probe(struct usb_device *udev)
{
int err, c;

if (udev->authorized == 0)
    dev_err(&udev->dev, "Device is not authorized for usage\n");
else {
    c = usb_choose_configuration(udev);
    if (c >= 0) {
        err = usb_set_configuration(udev, c);
        if (err) {
            dev_err(&udev->dev, "can't set config #%d, error %d\n",
                c, err);
            /* This need not be fatal.  The user can try to
             * set other configurations. */
        }
    }
}
usb_notify_add_device(udev);
return 0;

}

  usb_generic_driver中的generic_probe函数,这个函数是一个usb设备的第一个匹配的driver。Generic通用,只要是个usb设备就得先跟他来一段,usb设备驱动界的老大。他的probe干啥了呢?很简单!找个合适的配置,配置一下。从此usb设备就进入配置的时代了。(前期的工作谁做的呢,到这都已经设置完地址了,当然是hub了,hub发现设备后,会进行前期的枚举过程,获得配置,最终调用device_add将该usb设备添加到总线上。这个过程可以专门来一大段,是hub的主要工作,所以需要把hub单独作为一个家族来对待,人家可是走在第一线的默默无闻的工作者,默默的将设备枚举完成后,将这个设备添加到usb总线上,多伟大)。

  注意:设备setconfig时参数只能为0或者合理的配置值,0就代表不配置,仍然是寻址态。不过有些设备就是拿配置0作为配置值得。

  usb_choose_configuration从设备可能的众多配置(udev->descriptor.bNumConfigurations)选择一个合适的配置(struct usb_host_config),并返回该配置的索引值。

//为usb device选择一个合适的配置
int usb_choose_configuration(struct usb_device udev)
{
int i;
int num_configs;
int insufficient_power = 0;
struct usb_host_config
c, *best;

best = NULL;
//udev->config,其实是一个数组,存放设备的配置.usb_dev->config[m]-> interface[n]表示第m个配置的第n个接口的intercace结构.(m,n不是配置序号和接口序号).
c = udev->config;
//config项数
num_configs = udev->descriptor.bNumConfigurations;
//遍历所有配置项
for (i = 0; i < num_configs; (i++, c++)) {
    struct usb_interface_descriptor *desc = NULL;

    //配置项的接口数目
    //取配置项的第一个接口
    if (c->desc.bNumInterfaces > 0)
        desc = &c->intf_cache[0]->altsetting->desc;

    ... ...

    //电源不足.配置描述符中的电力是所需电力的1/2
    if (c->desc.bMaxPower * 2 > udev->bus_mA) {
        insufficient_power++;
        continue;
    }

     //非标准Ethernet-over-USB协议

   if (i == 0 && num_configs > 1 && desc &&
     (is_rndis(desc) || is_activesync(desc))){

      … …

     }
//选择一个不是USB_CLASS_VENDOR_SPEC的配置
     else if (udev->descriptor.bDeviceClass !=
USB_CLASS_VENDOR_SPEC &&
(!desc || desc->bInterfaceClass !=
USB_CLASS_VENDOR_SPEC)) {
best = c;
break;
}

    /*如果所有剩下的配置是特殊的vendor,选择第一个*/
    else if (!best)
        best = c;
}

... ...

//如果选择好了配置,返回配置的序号,否则,返回-1
if (best) {
    i = best->desc.bConfigurationValue;
    dev_info(&udev->dev,
        "configuration #%d chosen from %d choice%s\n",
        i, num_configs, plural(num_configs));
} else {
    i = -1;
    dev_warn(&udev->dev,
        "no configuration chosen from %d choice%s\n",
        num_configs, plural(num_configs));
}

return i;

}

  例如:我机器上的的 usb 驱动加载时,输出:usb 1-1: configuration #1 chosen from 3 choices

  表示:此设备有3个配置,而驱动最终选择了索引号为1的配置,至于选择策略是怎样的,请看usb_choose_configuration()函数。 

  generic_probe函数中的usb_set_configuration函数里有很重要的动作,不是简单的设置个配置,当我们选择了某一个配置后,需要将这个配置的所有接口取出来,初始化接口作为驱动对应的一种”设备”的参数,如总线类型、设备类型等,调用device_add将该接口设备添加到设备模型中。

int usb_set_configuration(struct usb_device *dev, int configuration)
{
… …

if (cp && configuration == 0)
    dev_warn(&dev->dev, "config 0 descriptor??\n");

  /首先,根据选择好的配置号找到相应的配置,在这里要注意了, dev->config[]数组中的配置并不是按照配置的序号来存放的,而是按照遍历到顺序来排序的.因为有些设备在发送配置描述符的时候,并不是按照配置序号来发送的,例如,配置2可能在第一次GET_CONFIGURATION就被发送了,而配置1可能是在第二次GET_CONFIGURATION才能发送.
   取得配置描述信息之后,要对它进行有效性判断,注意一下本段代码的最后几行代码:usb2.0 spec上规定,0号配置是无效配置,但是可能有些厂商的设备并末按照这一约定,所以在linux中,遇到这种情况只是打印出警告信息,然后尝试使用这一配置.
/
n = nintf = 0;
if (cp) {
//接口总数
nintf = cp->desc.bNumInterfaces;
//在这里, 注要是为new_interfaces分配空间,要这意的是, new_interfaces是一个二级指针,它的最终指向是struct usb_interface结构.特别的,如果总电流数要小于配置所需电流,则打印出警告消息.实际上,这种情况在usb_choose_configuration()中已经进行了过滤.
new_interfaces = kmalloc(nintf sizeof(new_interfaces),
GFP_KERNEL);
… …

    for (; n < nintf; ++n) {
        new_interfaces[n] = kzalloc(
                sizeof(struct usb_interface),
                GFP_KERNEL);
         ... ...
    }

    //如果总电源小于所需电流,打印警告信息
    i = dev->bus_mA - cp->desc.bMaxPower * 2;
    ... ...
}

 
//要对设备进行配置了,先唤醒它
ret = usb_autoresume_device(dev);
if (ret)
goto free_interfaces;

 //不是处于ADDRESS状态,先清除设备的状态
if (dev->state != USB_STATE_ADDRESS)
    usb_disable_device(dev, 1); /* Skip ep0 */

//确定我们有足够带宽提供这个配置
ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
... ...

//发送控制消息,选取配置
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
              USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
              NULL, 0, USB_CTRL_SET_TIMEOUT);
 ... ...
}

//dev->actconfig存放的是当前设备选取的配置
dev->actconfig = cp;
 ... ...
//将状态设为CONFIGURED
usb_set_device_state(dev, USB_STATE_CONFIGURED);
/*接下来,就要对设备进行配置了,首先,将设备唤醒.只有在ADDRESS状态才能转入到CONFIG状态.(SUSPEND状态除外). 所以,如果设备当前不是处于ADDRESS状态,就需要将设备的状态初始化
接着,发送SET_CONFIGURATION的Control消息给设备,用来选择配置最后,将dev->actconfig指向选定的配置,将设备状态设为CONFIG*/

 //遍历所有的接口
for (i = 0; i < nintf; ++i) {
    struct usb_interface_cache *intfc;
    struct usb_interface *intf;
    struct usb_host_interface *alt;

  /之前初始化的new_interfaces在这里终于要派上用场了.初始化各接口,从上面的初始化过程中,我们可以看出:
    Intf->altsetting,表示接口的各种设置
    Intf->num_altsetting:表示接口的设置数目
    Intf->intf_assoc:接口的关联接口(定义于minor usb 2.0 spec)
    Intf->cur_altsetting:接口的当前设置.
/
cp->interface[i] = intf = new_interfaces[i];
intfc = cp->intf_cache[i];
intf->altsetting = intfc->altsetting;
intf->num_altsetting = intfc->num_altsetting;
//是否关联的接口描述符,定义在minor usb 2.0 spec中
intf->intf_assoc = find_iad(dev, cp, i);
kref_get(&intfc->ref);

//选择0号设置
alt = usb_altnum_to_altsetting(intf, 0);

//如果0号设置不存在,选排在第一个设置
if (!alt)
    alt = &intf->altsetting[0];

//当前的配置
intf->cur_altsetting = alt;

     //用来启用接口,也就是启用接口中的每一个endpoint.
usb_enable_interface(dev, intf);
//注意这个地方对intf内嵌的struct devcie结构赋值,它的type被赋值为了usb_if_device_type.bus还是usb_bus_type.可能你已经反应过来了,要和这个device匹配的设备是interface的驱动.
intf->dev.parent = &dev->dev;
intf->dev.driver = NULL;
intf->dev.bus = &usb_bus_type;
intf->dev.type = &usb_if_device_type;
intf->dev.dma_mask = dev->dev.dma_mask;
device_initialize(&intf->dev);//device 初始化
mark_quiesced(intf);
     /
      device的命名:
      dev指的是这个接口所属的usb_dev,结合我们之前在UHCI中关于usb设备命名方式的描述.可得出它的命令方式如下:
      USB总线号-设备路径:配置号.接口号.
      例如,在我的虚拟机上:/sys/bus/usb/devices
      1-0:1.0 usb1
      可以得知,系统只有一个usb control.
      1-0:1.0:表示,第一个usb control下的root hub的1号配置的0号接口.
/
sprintf(&intf->dev.bus_id[0], “%d-%s:%d.%d”,
dev->bus->busnum, dev->devpath,
configuration, alt->desc.bInterfaceNumber);
}
kfree(new_interfaces);

if (cp->string == NULL)
    cp->string = usb_cache_string(dev, cp->desc.iConfiguration);

//注册每一个接口?
for (i = 0; i < nintf; ++i) {
    struct usb_interface *intf = cp->interface[i];

    dev_dbg(&dev->dev,
        "adding %s (config #%d, interface %d)\n",
        intf->dev.bus_id, configuration,
        intf->cur_altsetting->desc.bInterfaceNumber);
    ret = device_add(&intf->dev);//增加device
    if (ret != 0) {
        dev_err(&dev->dev, "device_add(%s) --> %d\n",
            intf->dev.bus_id, ret);
        continue;
    }
    usb_create_sysfs_intf_files(intf);
}

//使设备suspend
usb_autosuspend_device(dev);

return 0;

}

  最后,注册intf内嵌的device结构.设备配置完成了,为了省电,可以将设备置为SUSPEND状态.

  到此为止usb_generic_driver凭借自己的博爱的胸襟将所有设备的各个接口添加到了linux的设备模型中。

  usb设备首先以设备的身份与usb_generic_driver匹配,成功之后,会分裂出接口,当对接口调用device_add()后,会引起接口和接口驱动的匹配,这个匹配还是用usb_bus_type.mach()函数。因为接口的device->bus=& usb_bus_type, 这跟usb设备是一样的,所以,都会调用到usb_bus_type.mach(),但设备和接口的处理流程是不一样的(前面已经分析过)。

usb_hub_init:

int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) {
printk(KERN_ERR “%s: can’t register hub driver\n”,
usbcore_name);
return -1;
}

khubd_task = kthread_run(hub_thread, NULL, "khubd");
if (!IS_ERR(khubd_task))
    return 0;

/* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);

return -1;

}

这个函数主要有两个功能:

  在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化。

对于usb_register()可以看作是usb设备中的接口驱动,而usb_register_device_driver()是一个单纯的USB设备驱动。

  在usb_hub_init函数中完成了注册hub驱动,并且利用函数kthread_run创建一个内核线程。该线程用来管理监视hub的状态,所有的情况都通过该线程来报告。

  当加载主控器的时候,在自身的platform驱动的probe函数里,调用usb_add_hcd->register_root_hub向usb总线注册root hub设备, usb总线match成功后,由usb_generic_driver驱动的probe函数,配置interface设备,然后向usb总线注册interface, usb总线再一次match, 不过这次是匹配了interface,通过ID值和hub驱动配置,因此调用hub驱动的probe函数(hub_probe),hub_probe函数中调用hub_configure函数来配置hub,在这个函数中主要是利用函数usb_alloc_urb函数来分配一个urb,利用usb_fill_int_urb来初始化这个urb结构,包括hub的中断服务程序hub_irq的,查询的周期等。

  每当有设备连接到USB接口时,USB总线在查询hub状态信息的时候会触发hub的中断服务程序hub_irq,在该函数中利用kick_khubd将hub结构通过event_list添加到khubd的队列hub_event_list,然后唤醒khubd。进入hub_events函数,该函数用来处理khubd事件队列,从khubd的hub_event_list中的每个usb_hub数据结构。该函数中首先判断hub是否出错,然后通过一个for循环来检测每个端口的状态信息。利用usb_port_status获取端口信息,如果发生变化就调用hub_port_connect_change函数来配置端口等。 

static void hub_events(void)
{
  … …
while (1) {

//如果hub_event_list为空,退出
spin_lock_irq(&hub_event_lock);
if (list_empty(&hub_event_list)) {
    spin_unlock_irq(&hub_event_lock);
    break;
}
//取hub_event_list中的后一个元素,并将其断链
tmp = hub_event_list.next;
list_del_init(tmp);

     //根据tmp获取hub
hub = list_entry(tmp, struct usb_hub, event_list);
//增加hub计数
kref_get(&hub->kref);
     //解锁
spin_unlock_irq(&hub_event_lock);

hdev = hub->hdev;
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev);

     … …
usb_lock_device(hdev);
//如果hub断开了,继续hub_event_list中的下一个
if (unlikely(hub->disconnected))
goto loop;

//设备没有连接上
if (hdev->state == USB_STATE_NOTATTACHED) {
    hub->error = -ENODEV;
    //将下面的子设备全部disable
    hub_pre_reset(intf);
    goto loop;
}

/* 自动恢复 */
ret = usb_autopm_get_interface(intf);
if (ret) {
    dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
    goto loop;
}

//hub 暂停
if (hub->quiescing)
    goto loop_autopm;

//hub 有错误发生?
if (hub->error) {
    dev_dbg (hub_dev, "resetting for error %d\n",
        hub->error);

    ret = usb_reset_composite_device(hdev, intf);
    if (ret) {
        dev_dbg (hub_dev,
            "error resetting hub: %d\n", ret);
        goto loop_autopm;
    }

    hub->nerrors = 0;
    hub->error = 0;
}

     /首先,从hub_event_list摘下第一个元素,根据我们之前在接口驱动probe过程的kick_khubd()函数分析中,有将hub-> event_list添加到hub_event_list.因此,就可以顺藤摸瓜找到hub,再根据hub结构,找到接口结构和所属的usb 设备结构.
然后,进行第一个重要的判断.如果hub被断开了,则,断开hub下面所连接的所有端口,这是在hub_pre_reset()中完成的.
最后,进行第二个重要的判断,如果hub发生了错误,则reset它下面的所有端口,这是在usb_reset_composite_device()中完成的.
/

//在这里,它遍历hub上的每一个端口,如果端口的连接会生了改变(connect_change等于1)的情况,就会调用hub_port_connect_change()
for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {

       //检测端口是否忙
if (test_bit(i, hub->busy_bits))
continue;
//change_bits会在hub 第一次初始化时被赋值。而event_bits则在hub_irq中改变
connect_change = test_bit(i, hub->change_bits);
//如果都没有改变,继续测试下一个端口。
if (!test_and_clear_bit(i, hub->event_bits) &&
!connect_change && !hub->activating)
continue;

//Get_Port_Status:取得端口状态.
//会取得port的改变值和状态值
    ret = hub_port_status(hub, i,
            &portstatus, &portchange);
    if (ret < 0)
        continue;

       //在struct usb_dev中,有一个struct usb_device *children[USB_MAXCHILDREN]的成员,它是表示对应端口序号上所连接的usb设备.
//如果对应端口没有在设备树上,且端口显示已经连接上
//将connect_change置为1
if (hub->activating && !hdev->children[i-1] &&
(portstatus &
USB_PORT_STAT_CONNECTION))
connect_change = 1;
//端口的连接状态发生了改变.需要发送Clear_Feature
if (portchange & USB_PORT_STAT_C_CONNECTION) {
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}

//端口的状态从enable 变为了disable
if (portchange & USB_PORT_STAT_C_ENABLE) {
    if (!connect_change)
        dev_dbg (hub_dev,
            "port %d enable change, "
            "status %08x\n",
            i, portstatus);
    clear_port_feature(hdev, i,
        USB_PORT_FEAT_C_ENABLE);

     //端口已经被停止了,且端口已经被连在设备树中.
     //需要重启一下此端口
    if (!(portstatus & USB_PORT_STAT_ENABLE)
        && !connect_change
        && hdev->children[i-1]) {
        dev_err (hub_dev,
            "port %i "
            "disabled by hub (EMI?), "
            "re-enabling...\n",
            i);
        connect_change = 1;
    }
}

//Resume完成   
if (portchange & USB_PORT_STAT_C_SUSPEND) {
    clear_port_feature(hdev, i,
        USB_PORT_FEAT_C_SUSPEND);
    //如果端口连接了设备,就将设备唤醒
    if (hdev->children[i-1]) {
        ret = remote_wakeup(hdev->
                children[i-1]);
        if (ret < 0)
            connect_change = 1;
    }
    //如果端口没有连接设备,就将端口禁用
    else {
        ret = -ENODEV;
        hub_port_disable(hub, i, 1);
    }
    dev_dbg (hub_dev,
        "resume on port %d, status %d\n",
        i, ret);
}

//有过流保护,需要对hub power on
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
    dev_err (hub_dev,
        "over-current change on port %d\n",
        i);
    clear_port_feature(hdev, i,
        USB_PORT_FEAT_C_OVER_CURRENT);
    hub_power_on(hub);
}

//Reset状态已经完成了
if (portchange & USB_PORT_STAT_C_RESET) {
    dev_dbg (hub_dev,
        "reset change on port %d\n",
        i);
    clear_port_feature(hdev, i,
        USB_PORT_FEAT_C_RESET);
}

      
/什么情况下, hub_port_connect_change才会被设为1.
     1:端口在hub->change_bits中被置位.搜索整个代码树,发生在设置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手动将端口禁用,会将对应位置1.
2:hub上没有这个设备树上没有这个端口上的设备.但显示端口已经连上了设备
3:hub这个端口上的连接发生了改变,从端口有设备连接变为无设备连接,或者从无设备连接变为有设备连接.
4:hub的端口变为了disable,此时这个端口上连接了设备,但被显示该端口已经变禁用,需要将connect_change设为1.
5:端口状态从SUSPEND变成了RESUME,远程唤醒端口上的设备失败,就需要将connect_change设为1.
另外hub_port_connect_change()函数我们放在后面再来讨论
/
if (connect_change)
hub_port_connect_change(hub, i,
portstatus, portchange);
}

 //对HUB的处理
//如果hub状态末变化,不需要做任何处理
if (test_and_clear_bit(0, hub->event_bits) == 0)
    ;   /* do nothing */
//Get_hub_status 失败?
else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
    dev_err (hub_dev, "get_hub_status failed\n");
else {
    //这里是对应hub 状态发生了改变,且Get_hub_status正常返回的情况
    //如果hub的本地电源供电发生了改变
    if (hubchange & HUB_CHANGE_LOCAL_POWER) {
        dev_dbg (hub_dev, "power change\n");
        clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
        //如果是本地电源供电
        if (hubstatus & HUB_STATUS_LOCAL_POWER)
            /* FIXME: Is this always true? */
            hub->limited_power = 1;
        //如果本电源不供电
        else
            hub->limited_power = 0;
    }
    //如果hub 发生过电源保护,需要对hub power on
    if (hubchange & HUB_CHANGE_OVERCURRENT) {
        dev_dbg (hub_dev, "overcurrent change\n");
        msleep(500);    /* Cool down */
        clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
                   hub_power_on(hub);
    }
}

hub->activating = 0;

/* If this is a root hub, tell the HCD it's okay to
 * re-enable port-change interrupts now. */
if (!hdev->parent && !hub->busy_bits[0])
    usb_enable_root_hub_irq(hdev->bus);

loop_autopm:
/ Allow autosuspend if we’re not going to run again /
if (list_empty(&hub->event_list))
usb_autopm_enable(intf);
loop:
usb_unlock_device(hdev);
kref_put(&hub->kref, hub_release);
} / end while (1) /
}

  hub_port_connect_change()函数分析: 

static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
{
… …

//hub led
if (hub->has_indicators) {
    set_port_led(hub, port1, HUB_LED_AUTO);
    hub->indicator[port1-1] = INDICATOR_AUTO;
}

//忽略掉CONFIG_USB_OTG的处理

#ifdef CONFIG_USB_OTG
/ during HNP, don’t repeat the debounce /
if (hdev->bus->is_b_host)
portchange &= ~(USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE);

#endif

//尝试唤醒一个存在的设备
udev = hdev->children[port1-1];
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
        udev->state != USB_STATE_NOTATTACHED) {
    usb_lock_device(udev);
    if (portstatus & USB_PORT_STAT_ENABLE) {
        status = 0;        /* Nothing to do */

#ifdef CONFIG_USB_SUSPEND
} else if (udev->state == USB_STATE_SUSPENDED &&
udev->persist_enabled) {
/* For a suspended device, treat this as a

 * remote wakeup event.
 */
status = usb_remote_wakeup(udev);

#endif

    } else {
        status = -ENODEV;    /* Don't resuscitate */
    }
    usb_unlock_device(udev);

    if (status == 0) {
        clear_bit(port1, hub->change_bits);
        return;
    }
}

//如果对应端口已经有设备连接,先将其断开
if (udev)
    usb_disconnect(&hdev->children[port1-1]);

//接下来,将hub->change_bits的对应位清掉,该位是在函数hub_port_logical_disconnect()中被置的,在这里将其清除,免得下次在进入hub_events()的时候,再次检测到这个位发生改变.    
clear_bit(port1, hub->change_bits);

//如果发生物理断开或者连接状态改变,我们可能忘记移除设备
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
        (portchange & USB_PORT_STAT_C_CONNECTION))
    clear_bit(port1, hub->removed_bits);

//连接发生改变
//连接反弹的处理,实际上就是除抖动
if (portchange & (USB_PORT_STAT_C_CONNECTION |
            USB_PORT_STAT_C_ENABLE)) {
    //如果该端口的连接发生改变(从有连接到无接接,或者从无连接到有连接),就有一个除抖动的过程,usb2.0 spec上规定,除抖动的时间为100ms.
    //在函数里,定义的测试时间是1500ms.如果在这个时间内,端口还末处于稳定状态,就会返回-ETIMEDOUT
    //如果已经处于稳定状态了,就会返回稳定状态下的portstatus
    status = hub_port_debounce(hub, port1);
    if (status < 0) {
        if (printk_ratelimit())
            dev_err(hub_dev, "connect-debounce failed, "
                    "port %d disabled\n", port1);
        portstatus &= ~USB_PORT_STAT_CONNECTION;
    } else {
        portstatus = status;
    }
}
//如果接口上没有连接了,可以直接退出了
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
        test_bit(port1, hub->removed_bits)) {

    /*经过去抖后,端口稳定的处于断开连接状态.说明端口已经没有设备了.然后,再判断hub是否有电源开关((wHubCharacteristics & HUB_CHAR_LPSM) < 2),portstatus 的 USB_PORT_FEAT_POWER位是否被设置,如果没有被设置,则说明该端口断电了.
      如果hub有电源开关,且端口没有上电,则需要发送POWER的Set_Feature来为之上电*/
    if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
            && !(portstatus & USB_PORT_STAT_POWER))
        set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);

     //如果端口依然处理enable状态,就会跳转到标号done处,就端口disalbe.
if (portstatus & USB_PORT_STAT_ENABLE)
goto done;
return;
}

/*如果端口隐定处于连接状态,那就需要连接端口下的设备了.首先看到的是一个for循环,是用来配置设备的两种方式.我们知道,在配置设备的时候,首先要去取设备的描述符,这个过程是在ep0上完成的.而这个ep0支持的最大传输出数据又是在设备描述符的bMaxPacketSize0中所      定义的.因此就对应有两种处理方式:
  第一种是传输8个字节,取得描述符的前面一部份,从而就可以取得bMaxPacketSize0.此后再reset设备,再根据这个bMaxPacketSize0的长度去取它的设备描述符.
  第二种是一次传输64字节,取得设备描述符的bMaxPacketSize0字段*/
for (i = 0; i < SET_CONFIG_TRIES; i++) {

   //为探测到的usb设备(包括普通hub,u盘等)分配并初始化udev
// 在为root hub分配struct usb_dev的时候,它的第一个参数,也就是它的父结点是为NULL.
/我们来观察一下它在sysfs中的命名方式
在没有插入U盘之前:/sys/bus/usb/devices
1-0:1.0 usb1
插入U盘之后:
1-0:1.0 1-1 1-1:1.0 usb1
增加的两个目是:
1-1和1-1:1.0
1-1:1.0 :只有这样的目录,表示该U盘只有一个接口,当前选取的是第0号设置项.
/
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
dev_err (hub_dev,
“couldn’t allocate port %d usb_device\n”,
port1);
goto done;
}
//置为USB_STATE_POWERED状态
usb_set_device_state(udev, USB_STATE_POWERED);
udev->bus_mA = hub->mA_per_port;
udev->level = hdev->level + 1;
udev->wusb = hub_is_wusb(hub);

/*
 * USB 3.0 devices are reset automatically before the connect
 * port status change appears, and the root hub port status
 * shows the correct speed.  We also get port change
 * notifications for USB 3.0 devices from the USB 3.0 portion of
 * an external USB 3.0 hub, but this isn't handled correctly yet
 * FIXME.
 */
if (!(hcd->driver->flags & HCD_USB3))
    udev->speed = USB_SPEED_UNKNOWN;
else if ((hdev->parent == NULL) &&
        (portstatus & USB_PORT_STAT_SUPER_SPEED))
    udev->speed = USB_SPEED_SUPER;
else
    udev->speed = USB_SPEED_UNKNOWN;

/*为设备指定一个地址,是到所属的usb bus的bus->devmap中找到没有使用的那一位,先进行两次新的策略(i=0和=1时),如果不行就再进行两次旧的策略(i=2和i=3时).所有这一切只有一个目的,就是为了获得设备的描述符,
设置了udev->tt、udev->ttport和udev->ep    0.desc.wMaxPacketSize,设置udev->status= USB_STATE_ADDRESS。*/
choose_address(udev);
if (udev->devnum <= 0) {
    status = -ENOTCONN;    /* Don't retry */
    goto loop;
}

//hub_port_init()对这个usb_dev结构进行一系的初始化,在这个函数中会处理:Get_Description,Set_address.等操作
status = hub_port_init(hub, udev, port1, i);
if (status < 0)
    goto loop;

usb_detect_quirks(udev);
if (udev->quirks & USB_QUIRK_DELAY_INIT)
    msleep(1000);

/* consecutive bus-powered hubs aren't reliable; they can
 * violate the voltage drop budget.  if the new child has
 * a "powered" LED, users should notice we didn't enable it
 * (without reading syslog), even without per-port LEDs
 * on the parent.
 */
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
        && udev->bus_mA <= 100) {
    u16    devstat;

    status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
            &devstat);
    if (status < 2) {
        dev_dbg(&udev->dev, "get status %d ?\n", status);
        goto loop_disable;
    }
    le16_to_cpus(&devstat);
    if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
        dev_err(&udev->dev,
            "can't connect bus-powered hub "
            "to this port\n");
        if (hub->has_indicators) {
            hub->indicator[port1-1] =
                INDICATOR_AMBER_BLINK;
            schedule_delayed_work (&hub->leds, 0);
        }
        status = -ENOTCONN;    /* Don't retry */
        goto loop_disable;
    }
}

/* check for devices running slower than they could */
if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
        && udev->speed == USB_SPEED_FULL
        && highspeed_hubs != 0)
    check_highspeed (hub, udev, port1);

/* Store the parent's children[] pointer.  At this point
 * udev becomes globally accessible, although presumably
 * no one will look at it until hdev is unlocked.
 */
status = 0;

// 将分配的struct usb_dev结构跟他的父结构关联起来,也就是说添加到它的父结构的usb_dev-> children[]数组.
spin_lock_irq(&device_state_lock);
if (hdev->state == USB_STATE_NOTATTACHED)
    status = -ENOTCONN;
else
    hdev->children[port1-1] = udev;
spin_unlock_irq(&device_state_lock);

if (!status) {
  /*usb_configure_device(): 得到设备的描述符(包括设备描述符、配置描述符、接口描述符等),分析以上描述符信息,提取出配置、接口等,并赋值给udev结构里相应的字段。

     device_add() :将usb设备注册到系统里,这个动作将触发驱动的匹配,由于这是个usb设备,所以万能usb驱动usb_generic_driver会匹配上,从而generic_probe会得到执行,从上面可以看出来,这一次hub_events()调用是由于主控制器初始化调用了
hub_probe,从而引发hub_events调用。那root hub初始化完成以后hub_events会如何触发呢?答案是通过中断!而这个中断的服务函数就是hub_irq,也即是说,凡是真正的有端口变化事件发生,hub_irq就会被调用,而hub_irq()最终会调用kick_khubd(), 触发hub的event_list,于是再次调用hub_events().*/
status = usb_new_device(udev);
if (status) {
spin_lock_irq(&device_state_lock);
hdev->children[port1-1] = NULL;
spin_unlock_irq(&device_state_lock);
}
}

if (status)
    goto loop_disable;

status = hub_power_remaining(hub);
if (status)
    dev_dbg(hub_dev, "%dmA power budget left\n", status);

return;

loop_disable:
hub_port_disable(hub, port1, 1);
loop:
usb_ep0_reinit(udev);
release_address(udev);
hub_free_dev(udev);
usb_put_dev(udev);
if ((status == -ENOTCONN) || (status == -ENOTSUPP))
break;
}
if (hub->hdev->parent ||
!hcd->driver->port_handed_over ||
!(hcd->driver->port_handed_over)(hcd, port1))
dev_err(hub_dev, “unable to enumerate USB device on port %d\n”,
port1);
// Done标号是对应上述处理失败的处理,它禁用掉该端口(因为该端口没有连接设备或者是端口上的设备配置失败),如果是root hub,且USB控制器器驱动中又定义了relinquish_port.调用它.
done:
hub_port_disable(hub, port1, 1);
if (hcd->driver->relinquish_port && !hub->hdev->parent)
hcd->driver->relinquish_port(hcd, port1);
}

参考了很多大神的分析,非常感谢!

USB Audio Class (UAC) 分析

一个UAC设备插入到Ubuntu 14.04电脑上dmesg中打印的信息如下:

[ 2367.490491] usb 3-3.2: new full-speed USB device number 9 using xhci_hcd
[ 2367.580010] usb 3-3.2: New USB device found, idVendor=0d8c, idProduct=0132
[ 2367.580018] usb 3-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 2367.580023] usb 3-3.2: Product: USB PnP Audio Device
[ 2367.580027] usb 3-3.2: Manufacturer: C-Media Electronics Inc.
[ 2367.581679] input: C-Media Electronics Inc. USB PnP Audio Device as /devices/pci0000:00/0000:00:14.0/usb3/3-3/3-3.2/3-3.2:1.2/0003:0D8C:0132.0004/input/input18
[ 2367.581999] hid-generic 0003:0D8C:0132.0004: input,hidraw3: USB HID v1.11 Device [C-Media Electronics Inc. USB PnP Audio Device] on usb-0000:00:14.0-3.2/input2
[ 2367.913280] usbcore: registered new interface driver snd-usb-audio

这里可以看到其驱动程序为snd-usb-audio,依据这个关键词在内核中查找到如下内容:

tony@tony-E431:~/linux-3.4.y$ grep “registered new interface driver” ./ -rn
Binary file ./vmlinux.o matches
Binary file ./.tmp_vmlinux1 matches
Binary file ./.tmp_vmlinux2 matches
Binary file ./drivers/built-in.o matches
Binary file ./drivers/usb/built-in.o matches
Binary file ./drivers/usb/core/built-in.o matches
./drivers/usb/core/driver.c:865: pr_info(“%s: registered new interface driver %s\n”,
Binary file ./drivers/usb/core/driver.o matches
Binary file ./drivers/usb/core/usbcore.o matches
Binary file ./arch/arm/boot/Image matches
Binary file ./vmlinux matches
tony@tony-E431:~/linux-3.4.y$

以及

// file: linux-3.4.y/sound/usb/card.c
/*

  • entry point for linux usb interface
    */

static struct usb_driver usb_audio_driver = {
.name = “snd-usb-audio”,
.probe = usb_audio_probe,
.disconnect = usb_audio_disconnect,
.suspend = usb_audio_suspend,
.resume = usb_audio_resume,
.id_table = usb_audio_ids,
.supports_autosuspend = 1,
};

根据probe方法snd_usb_audio_probe调用了snd_usb_apply_boot_quirk和snd_card_register,查到了

// linux-3.4.y/sound/usb/card.c
/*

  • new 2.5 USB kernel API
    /
    static int usb_audio_probe(struct usb_interface
    intf,
    const struct usb_device_id *id)
    
    {
    struct snd_usb_audio *chip;
    chip = snd_usb_audio_probe(interface_to_usbdev(intf), intf, id);
    if (chip) {
    usb_set_intfdata(intf, chip);
    return 0;
    
    } else
    return -EIO;
    
    }

// linux-3.4.y/sound/usb/quirks.c
int snd_usb_apply_boot_quirk(struct usb_device dev,
struct usb_interface
intf,
const struct snd_usb_audio_quirk *quirk)
{
u32 id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));

switch (id) {
case USB_ID(0x041e, 0x3000):
    /* SB Extigy needs special boot-up sequence */
    /* if more models come, this will go to the quirk list. */
    return snd_usb_extigy_boot_quirk(dev, intf);

case USB_ID(0x041e, 0x3020):
    /* SB Audigy 2 NX needs its own boot-up magic, too */
    return snd_usb_audigy2nx_boot_quirk(dev);

case USB_ID(0x10f5, 0x0200):
    /* C-Media CM106 / Turtle Beach Audio Advantage Roadie */
    return snd_usb_cm106_boot_quirk(dev);

case USB_ID(0x0d8c, 0x0102):
    /* C-Media CM6206 / CM106-Like Sound Device */
case USB_ID(0x0ccd, 0x00b1): /* Terratec Aureon 7.1 USB */
    return snd_usb_cm6206_boot_quirk(dev);

case USB_ID(0x133e, 0x0815):
    /* Access Music VirusTI Desktop */
    return snd_usb_accessmusic_boot_quirk(dev);

case USB_ID(0x17cc, 0x1000): /* Komplete Audio 6 */
case USB_ID(0x17cc, 0x1010): /* Traktor Audio 6 */
case USB_ID(0x17cc, 0x1020): /* Traktor Audio 10 */
    return snd_usb_nativeinstruments_boot_quirk(dev);
case USB_ID(0x0763, 0x2012):  /* M-Audio Fast Track Pro USB */
    return snd_usb_fasttrackpro_boot_quirk(dev);
}

return 0;

}

snd_usb_audio_probe
/*

  • probe the active usb device
    *
  • note that this can be called multiple times per a device, when it
  • includes multiple audio control interfaces.
    *
  • thus we check the usb device pointer and creates the card instance
  • only at the first time. the successive calls of this function will
  • append the pcm interface to the corresponding card.
    /
    static struct snd_usb_audio

    snd_usb_audio_probe(struct usb_device *dev,

    struct usb_interface *intf,
    const struct usb_device_id *usb_id)
    

    {
    const struct snd_usb_audio_quirk quirk = (const struct snd_usb_audio_quirk )usb_id->driver_info;
    int i, err;
    struct snd_usb_audio chip;
    struct usb_host_interface
    alts;
    int ifnum;
    u32 id;

    alts = &intf->altsetting[0];
    ifnum = get_iface_desc(alts)->bInterfaceNumber;
    id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),

    le16_to_cpu(dev->descriptor.idProduct));
    

    if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)

    goto __err_val;
    

    if (snd_usb_apply_boot_quirk(dev, intf, quirk) < 0)

    goto __err_val;
    

    /*

    • found a config. now register to ALSA
      */

      / check whether it’s already registered /
      chip = NULL;
      mutex_lock(&register_mutex);
      for (i = 0; i < SNDRV_CARDS; i++) {
      if (usb_chip[i] && usb_chip[i]->dev == dev) {

      if (usb_chip[i]->shutdown) {
          snd_printk(KERN_ERR "USB device is in the shutdown state, cannot create a card instance\n");
          goto __error;
      }
      chip = usb_chip[i];
      chip->probing = 1;
      break;
      

      }
      }
      if (! chip) {
      /* it’s a fresh one.

      • now look for an empty slot and create a new card instance
        */
        for (i = 0; i < SNDRV_CARDS; i++)
        if (enable[i] && ! usb_chip[i] &&
        (vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
        (pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
        if (snd_usb_audio_create(dev, i, quirk, &chip) < 0) {
            goto __error;
        }
        snd_card_set_dev(chip->card, &intf->dev);
        chip->pm_intf = intf;
        break;
        
        }
        if (!chip) {
        printk(KERN_ERR “no available usb audio device\n”);
        goto __error;
        }
        }

      /*

    • For devices with more than one control interface, we assume the
    • first contains the audio controls. We might need a more specific
    • check here in the future.
      */
      if (!chip->ctrl_intf)
      chip->ctrl_intf = alts;

      chip->txfr_quirk = 0;
      err = 1; / continue /
      if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
      / need some special handlings /
      if ((err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk)) < 0)

      goto __error;
      

      }

      if (err > 0) {
      / create normal USB audio interfaces /
      if (snd_usb_create_streams(chip, ifnum) < 0 ||

      snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) {
      goto __error;
      

      }
      }

      / we are allowed to call snd_card_register() many times /
      if (snd_card_register(chip->card) < 0) {
      goto __error;
      }

      usb_chip[chip->index] = chip;
      chip->num_interfaces++;
      chip->probing = 0;
      mutex_unlock(&register_mutex);
      return chip;

    __error:
    if (chip) {

    if (!chip->num_interfaces)
        snd_card_free(chip->card);
    chip->probing = 0;
    

    }
    mutex_unlock(&register_mutex);
    __err_val:
    return NULL;
    }

Bus 003 Device 019: ID 0d8c:0132 C-Media Electronics, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 16
idVendor 0x0d8c C-Media Electronics, Inc.
idProduct 0x0132
bcdDevice 1.00
iManufacturer 1 C-Media Electronics Inc.
iProduct 2 USB PnP Audio Device
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 221
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 1 Control Device
bInterfaceProtocol 0
iInterface 0
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 1 (HEADER)
bcdADC 1.00
wTotalLength 47
bInCollection 1
baInterfaceNr( 0) 1
AudioControl Interface Descriptor:
bLength 12
bDescriptorType 36
bDescriptorSubtype 2 (INPUT_TERMINAL)
bTerminalID 2
wTerminalType 0x0201 Microphone
bAssocTerminal 0
bNrChannels 2
wChannelConfig 0x0003
Left Front (L)
Right Front (R)
iChannelNames 0
iTerminal 0
AudioControl Interface Descriptor:
bLength 9
bDescriptorType 36
bDescriptorSubtype 3 (OUTPUT_TERMINAL)
bTerminalID 7
wTerminalType 0x0101 USB Streaming
bAssocTerminal 0
bSourceID 8
iTerminal 0
AudioControl Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 5 (SELECTOR_UNIT)
bUnitID 8
bNrInPins 1
baSource( 0) 10
iSelector 0
AudioControl Interface Descriptor:
bLength 10
bDescriptorType 36
bDescriptorSubtype 6 (FEATURE_UNIT)
bUnitID 10
bSourceID 2
bControlSize 1
bmaControls( 0) 0x01
Mute Control
bmaControls( 1) 0x02
Volume Control
bmaControls( 2) 0x02
Volume Control
iFeature 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 7
bDelay 1 frames
wFormatTag 1 PCM
AudioStreaming Interface Descriptor:
bLength 29
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 1
bSubframeSize 2
bBitResolution 16
bSamFreqType 7 Discrete
tSamFreq[ 0] 8000
tSamFreq[ 1] 11025
tSamFreq[ 2] 16000
tSamFreq[ 3] 22050
tSamFreq[ 4] 32000
tSamFreq[ 5] 44100
tSamFreq[ 6] 48000
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 5
Transfer Type Isochronous
Synch Type Asynchronous
Usage Type Data
wMaxPacketSize 0x0064 1x 100 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioControl Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x01
Sampling Frequency
bLockDelayUnits 0 Undefined
wLockDelay 0 Undefined
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 2
bNumEndpoints 1
bInterfaceClass 1 Audio
bInterfaceSubClass 2 Streaming
bInterfaceProtocol 0
iInterface 0
AudioStreaming Interface Descriptor:
bLength 7
bDescriptorType 36
bDescriptorSubtype 1 (AS_GENERAL)
bTerminalLink 7
bDelay 1 frames
wFormatTag 1 PCM
AudioStreaming Interface Descriptor:
bLength 29
bDescriptorType 36
bDescriptorSubtype 2 (FORMAT_TYPE)
bFormatType 1 (FORMAT_TYPE_I)
bNrChannels 2
bSubframeSize 2
bBitResolution 16
bSamFreqType 7 Discrete
tSamFreq[ 0] 8000
tSamFreq[ 1] 11025
tSamFreq[ 2] 16000
tSamFreq[ 3] 22050
tSamFreq[ 4] 32000
tSamFreq[ 5] 44100
tSamFreq[ 6] 48000
Endpoint Descriptor:
bLength 9
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 5
Transfer Type Isochronous
Synch Type Asynchronous
Usage Type Data
wMaxPacketSize 0x00c8 1x 200 bytes
bInterval 1
bRefresh 0
bSynchAddress 0
AudioControl Endpoint Descriptor:
bLength 7
bDescriptorType 37
bDescriptorSubtype 1 (EP_GENERAL)
bmAttributes 0x01
Sampling Frequency
bLockDelayUnits 0 Undefined
wLockDelay 0 Undefined
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 26
Report Descriptors:
UNAVAILABLE
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x87 EP 7 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 1
Device Status: 0x0000
(Bus Powered)

总结:
UAC设备的参数是通过USB描述符确定的。比如声道是bNrChannels,位深是bBitResolution,采样率是bSamFreqType。截取其中一段说明:

bNrChannels 1
bSubframeSize 2
bBitResolution 16
bSamFreqType 7 Discrete
tSamFreq[ 0] 8000
tSamFreq[ 1] 11025
tSamFreq[ 2] 16000
tSamFreq[ 3] 22050
tSamFreq[ 4] 32000
tSamFreq[ 5] 44100
tSamFreq[ 6] 48000

声道1,位深16,波特率8000~48000。

-博客地址: Born2013.github.io

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment