006USB设备驱动

一、 USB驱动程序之概念介绍(第十七课/第一节)

现象:
在WINDOWS下,把USB设备接到PC机上:

  1. 右下角会弹出"发现什么USB新设备"。如发现"android phone"。
  2. 跳出一个对话框,提示安装驱动程序。

问1:既然还没有"驱动程序",为何能知道是"android phone"?

    答1:Windows里已经有了USB的总线驱动程序,接入USB设备后,是"总线驱动程序"知道你是"android phone"。提示你安装的是"设备驱动程序"。
    USB总线驱动程序负责:识别USB设备,给USB设备找到对应的驱动程序。

问2:USB设备种类非常多,为什么一接入电脑,就能识别出来?

    答2:PC和USB设备都得遵守一些规范。比如:USB设备接入电脑后,PC机就会发出"你是什么?"   USB设备就必须回答"我是xxx"。
    USB总线驱动程序会发出某些命令想获取设备信息(描述符),USB设备必须返回(描述符)给PC机。

问3:PC机上接有非常多的USB设备,怎么分辨它们?

    答3:USB接口只有四根线:5V,GND,D-,D+
    每一个USB设备接入PC机时,USB总线驱动程序都会给它分配一个编号,接在USB总线上的每一个USB设备都有自己的编号(地址),PC机想访问某个USB设备时,发出的命令都含有对应的编号(地址)。

问4:USB设备刚接入PC机时,还没有编号,那么PC机怎么把"分配的编号"告诉它?

    答4:新接入的USB设备的默认编号是0,在未分配新编号前,PC机使用0编号和它通信。

问5:为什么一接入USB设备,PC机就能发现它?

    答5:PC机的USB口内部,D-和D+都接有15K的下拉电阻,未接USB设备时为低电平;USB设备的USB口内部,D-或D+接有1.5K的上拉电阻。当USB设备一接入PC机,就会把PC的USB口的D-或D+拉高,从硬件的角度通知PC机有新设备接入


其他概念:

    一、 USB是主从结构的:
    所有的USB传输,都是从USB主机这方发起,USB设备没有"主动"通知USB主机的能力。
    例:USB鼠标滑动一下立刻产生数据,但是它没有能力通知PC机来读数据,只能被动的等待PC机来读取。

    二、 USB的传输类型:
    a.控制传输:可靠,实时,比如:USB设备的识别过程
    b.批量传输:可靠,时间没有保证,比如:U盘(从PC机上删除U盘时有时快有时慢)
    c.中断传输:可靠,实时,比如:USB鼠标。(只是借助了中断的概念,实质是用USB主机查询方式来实现实时性,不断读USB鼠标。USB设备并不能发起中断,USB设备并没有发起通知给USB主机的能力。)
    d.实时传输:不可靠,实时,比如:USB摄像头。(数据的不可靠可以接受,如果要可靠,可能因为某一帧发送错误时会因可靠机制来重传,就会带来滞后感。)

    三、 USB传输的对象:端点(endpoint)
    我们说"读U盘"、"写U盘",可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据;除了端点0外,每一个端点只支持一个方向的数据传输。端点0用于控制传输,既能输入也能输出。

    四、 每一个端点都有(只有一个)传输类型,传输方向

    五、 术语里、程序里说的输入(IN)、输出(OUT)都是基于USB主机的立场说的。比如鼠标的数据是从鼠标传到PC机,对应的端点称为"输入端点"。(对于USB主机来说这是输入)。

    六、 USB总线驱动程序的作用:
    a. 识别USB设备。
    b. 查找并安装对应的设备驱动程序。
    c. 提供USB读写函数。(只是提供读写函数,数据的含义不知道。就像"送信人"一样,不了解数据含义。USB设备驱动程序知道数据含义。)

USB驱动程序框架:

二、 USB驱动程序之USB总线驱动程序(第十七课/第二节)

USB主机控制器有三种规范:1.UHCI;2.OHCI;3.EHCI

    UHCI:intel   适用于(USB1.1)低速(1.5Mbps)/(USB2.0)全速(12Mbps)     (当说到USB2.0还得分全速和高速)
    OHCI:microsoft 适用低速/全速
    EHICI:        适用于高速(480Mbps)

intel做芯片的,用UHCI这套规范做出来的主机控制器硬件功能强大,软件实现的就简单点;相反,microsoft做软件的,用OHCI这套规范做出来的硬件会差一点,但是软件会复杂点。

把USB设备(手机)接到开发板上,查看输出信息:

拔掉

这里的(full speed)会不可靠,速度的种类可能是"%s",所以在内核驱动目录下搜索"USB device using"

HUB 的概念:

每一个USB主机控制器都自带了一个USB HUB,HUB上接有USB设备,可以认为HUB是一个特殊的USB设备
在搜索到的(hub.c)文件中找到打印的这条语句

查看该语句在哪个函数里

hub端口初始化,看看这个函数在哪儿被调用

端口连接变化,这个函数又在哪儿被调用

hub事件函数又是在哪儿被调用

hub线程,它是一个内核线程,平时是休眠的,当有事情发生会被唤醒,那么被谁来唤醒呢

搜索一下谁来从这个队列把它唤醒

这个(kick_khubd)又是被谁调用

注意:这个中断是主机控制器里注册的中断,不是USB设备的中断
当接上USB设备,就会使得D+或者D-有低电平变成高电平,硬件上就感知到了有USB设备接入,主机控制器里就会产生中断。
在中断函数里面一路执行下来,最终就会执行到(hub_port_init),在函数里面就会打印出发现新的USB设备

根据USB驱动总线的作用,来看看是怎么告诉USB设备的

从(hub_port_connet_change)函数开始分析

分配一个usb_device结构体

这个结构体的总线类型是"usb_bus_type"

紧接着分配编号(地址)

然后进入(hub_port_init)函数,设置地址并获取描述符

设置地址

获取描述符
描述符的信息可以在(include\linux\usb\Ch9.h)看到,Ch9表示USB规范协议的第九章
设备描述符:每一个硬件都有USB设备描述符,我可以发出某些命令得到这些USB设备的设备描述符。一个设备可能会有多种配置


配置描述符


接口描述符:逻辑上的设备
每个配置下面可能有多个接口,也可能只有一个。比如:一个USB声卡,它硬件上只有一个,但逻辑上可能会有两个功能(录音、播放)。这样就分成了两个逻辑设备,用两个"接口描述符"分别描述逻辑设备"录音"和"播放"。


端点描述符


每一个硬件都有一个设备描述符,一个设备下面可能有多个配置,一个配置下面可能有多个接口,接口就是逻辑设备,我们写驱动程序时就是给接口(逻辑设备)写的,所以一个USB硬件可能会多个驱动程序。一个接口里面有多个端点,端点描述符会表示一次性会传输多大的数据,它的端点编号,它的方向,它的传输类型这些信息。
我们把这些描述符信息通通读出来之后就可对这个USB设备了如指掌了,就可以根据这些信息找到驱动程序了

问:为什么只获得8个字节呢?

答:因为还不知道你这个端点0一次性传输多少数据。

再次获得设备描述符

到这里(hub_port_init)函数就结束了。再返回上一级函数(hub_port_connet_change),看看还有哪些工作,调用(usb_new_devie)函数
在(usb_get_configuration)函数里把所有的描述符都读出来,并解析他们


最后跳到(usb_new_device)函数里执行(device_add)

(device_add)函数会到总线设备驱动模型上添加USB设备。把设备放到总线的dev链表,从总线的driver链表里取出driver用总线的match函数通过id_table一一比较,若能匹配成功,则调用driver中的"probe"函数。
总结一下这个过程:

    当接上一个USB设备时,就会产生由硬件产生一个中断(hub_irq),在中断里会分配一个编号(choose_address),然后把这个地址告诉USB设备(hub_set_address),接着发出各种命令获取USB设备的"设备描述符"。再然后注册一个device(device_add),这个被注册的device会被放到USB总线(usb_bus_type)的"设备链表",然后从总线的"驱动链表"中取出"usb_driver",把usb_interface和usb_driver的id_table比较,若匹配成功则调用"usb_driver"结构中的probe函数。

三、 USB驱动程序之USB设备驱动程序1简单编写(第十七课/第三节)


USB总线驱动程序,在我们接入USB设备的时候会帮我们构造一个新的 usb_dev,注册到(usb_bus_type)中去,左边这部分内核已经帮我们做好,我们要做的是右边这一块,构造一个 usb_driver 结构体,然后注册到(usb_bus_type)里。在 usb_driver 结构体里有 id_table,表示它能支持哪些设备,当(usb_bus_type)结构中match函数匹配成功之后就会调用 usb_driver 结构中probe函数;拔掉USB设备时就会调用 usb_driver 结构中的disconnet函数。
(usb_bus_type)总线驱动模型只是提供了这样一种框架,可以把左右两边挂钩起来。
目标:
USB鼠标用作按键:(相当于输入子系统)
左键 -- L
右键 -- S
中建 -- Enter
在probe函数做的事情:

    1. 分配一个input_dev结构体。
    2. 设置,使其产生哪类事件,该类事件的哪些事件。
    3. 注册
    4.硬件相关操作:
    写按键或触摸屏驱动程序时是注册某中断,在按键的中断里读引脚确定是按下还是松开然后读按键值。
    在USB鼠标里,是使用USB总线驱动程序提供的读写函数来收发数据。

怎么写USB设备驱动程序?
1. 分配/设置 usb_driver 结构体
.id_table 表示能支持哪些设备
.probe 若id_table能够支持就调用probe函数
.disconnet 拔掉USB设备时调用这个函数
2. 注册

先参考别人的代码:(driver\hid\usbhid\usbmouse.c)
入口函数里面注册一个(usb_driver结构体)

假设能够匹配,调用probe函数




以前的触摸屏驱动是从2440adc控制器里面得到数据,现在的USB设备驱动程序想得到数据,是发USB包。通过(USB主机控制器)来得到那些数据
现在来写出框架:

1th、 打印简单的语句

一、 id_table:

    ".match_flags":表示要匹配设备描述符中的哪一项。"USB_DEVICE_ID_MATCH_INT_INFO":INT是接口的意思,INT_INFO就是指接口的信息;接口的信息就在接口描述符里。
    ".bInterfaceClass":接口描述符里的类
    ".bInterfaceSubClass":接口描述符的子类
    ".bInterfaceProtocol":接口描述符的协议

只要这个设备的接口描述符里面的类是HID类;子类是BOOT;协议是MOUSE,那么这个id_table就能够支持。

也可以支持厂家ID,设备ID

二、 填充probe函数

一个USB硬件设备可能有多个逻辑设备,这些逻辑设备就是用"usb_interface"结构体表示的。如一块声卡有录音和播放两个逻辑接口,就需要两个驱动程序。
三、 disconnect函数

测试1th:
第一步:make menuconfig 去掉原来的USB鼠标驱动,不然一接上USB鼠标内核就会找到原来的驱动,新注册的驱动就无法加载。


第二步:编译没有USB的内核

第三步:使用没有USB的内核启动


第四步:挂接文件系统,加载驱动程序


第五步:插入,拔出鼠标

2th、 打印厂家ID和设备ID

当我们接入USB设备之后,USB总线驱动程序已经把这些设备描述符信息通通读取出来了。


打印描述符信息

测试2th:
重新加载驱动

插入,拔出鼠标

电脑上的USB鼠标描述符信息

3th、 打印USB鼠标的原始数据

继续分析实例(usbmouse.c)的probe函数


问:怎么知道是否是输入类型端点?
答:每一个端点的端点描述符都有一个属性,表明它的方向。
继续写代码,在自己的代码里面就不用做判断了,就认为它是一个鼠标
按照套路把前三步做完

以前的硬件操作就是去注册中断引脚然后去读取寄存器,现在需要用底层的USB主机控制器提供的读写函数来收发USB数据
A、数据传输三要素:源、目的、长度。
1. 源:USB设备的某个端点。



这个宏(usb_rcvintpipe)就包含了USB设备的地址和哪一个端点(端点地址)
自己的猜想:这一个int类型的pipe变量包含了端点类型、设备地址、端点地址、端点方向,是因为把这4字节拆分为了4部分

    PIPE_INTERRUPT:中断类型端点
    USB_DIR_IN:端点的方向
    devnum:设备地址(USB设备编号)
    endpoint:端点地址(bEndpointAddress)

2. 目的:从USB设备读数据,读到一个缓冲区。所以要分配一个缓冲区,不能用(kalloc),用(usb_buffer_alloc)来分配,返回一个(void*)的虚拟地址

3. 长度:端点描述符中的最大包大小

B、使用三要素
1. 分配一个urb(usb request block)

2. 使用三要素来设置urb和设置某些标记



USB主机控制器得到数据后,要往某个内存去写(写的内容是查询到USB设备的数据),但是这个主机控制器没那么聪明,需要我们告诉这个主机控制器某个内存的物理地址。
USB设备没有能力产生中断,它是通过USB主机控制器不断的查询,查询到数据后是由USB主机控制器向CUP发出中断申请的。
3. 使用urb;提交 urb

自己猜想:在实例程序中未看到该函数,猜想是注册input_dev的时候会调用该结构里的open函数
C、完成中断函数(USB设备没有中断能力,这个中断请求是USB主机控制器发出的)

D、disconnect函数

测试3th:
重新加载驱动

接上USB鼠标

操作鼠标观察数据

    第一个字节:数据"01"里的"1"是bit0,表示左键按下;"02"里的"2"是bit1,表示右键按下;"04"里的"4"是bit2,表示中键按下;"03"里的"3"是bit0和bit1,表示左右键同时按下。
    第二个字节:X方向,正值表示往右移,负值表示往左移。
    第三个字节:Y方向,往上移是负数,往下移是正数。
    第四个字节:滑轮,往前滑是正数,往后滑是负数。

USB鼠标驱动程序-3th

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
/*
 *参考 F:\编程之路\编程之路(Linux)\link_to_term1_and_term2\kernels\linux-2.6.22.6\drivers\hid\usbhid\usbmouse.c
 */
static struct input_dev* uk_dev;
static char* usb_buffer_vir;
static dma_addr_t usb_buffer_pys;
static struct urb* usb_urb;
static int len;

static struct usb_device_id usbmouse_id_table [] = {
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
        USB_INTERFACE_PROTOCOL_MOUSE) },
    { } /* Terminating entry */
};

/* 当USB主机控制器查询到信息后,保存到buffer然后产生中断 */
static void usbmouse_irq(struct urb *urb)
{
    int i;
    static int cnt = 0;
    printk("usb_buffer_vir data cnt: %d\n", cnt++);
    for(i=0; i<len; i++)
    {
        printk("%02x", usb_buffer_vir[i]);
    }
    printk("\n");
    /* 重新提交urb */
    usb_submit_urb(usb_urb, GFP_KERNEL);
}
static int usbmouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_device *dev = interface_to_usbdev(intf);
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int pipe;

    interface = intf->cur_altsetting;
    endpoint = &interface->endpoint[0].desc;

    /* 1.分配一个input_dev结构体 */
    uk_dev = input_allocate_device();
    /* 2. 设置 */
    /* 2.1 设置能产生哪类事件:按键类、重复类 */
    set_bit(EV_KEY, uk_dev->evbit);
    set_bit(EV_REP, uk_dev->evbit);
    /* 2.2 设置产生该类的哪些事件:L、S、回车 */
    set_bit(KEY_L, uk_dev->keybit);
    set_bit(KEY_S, uk_dev->keybit);
    set_bit(KEY_ENTER, uk_dev->keybit);
    /* 3. 注册 */
    input_register_device(uk_dev);
    /* 4. 硬件相关操作 */
    /* 数据传输三要素:源、目的、长度 */
    /* 4.1 源*/
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
    /* 4.2 长度 */
    len = endpoint->wMaxPacketSize;
    /* 4.3 目的:分配一个缓冲区 */
    usb_buffer_vir = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buffer_pys);
    /* 使用三要素 */
    /* 1. 分配 usb   request block */
    usb_urb = usb_alloc_urb(0, GFP_KERNEL);
    /* 2. 使用三要素来设置urb */
    usb_fill_int_urb(usb_urb, dev, pipe, usb_buffer_vir, len, usbmouse_irq, NULL, endpoint->bInterval);
    usb_urb->transfer_dma = usb_buffer_pys;
    usb_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    /* 3. 提交urb */
    usb_submit_urb(usb_urb, GFP_KERNEL);
    return 0;
}

static void usbmouse_disconnect(struct usb_interface *intf)
{
    struct usb_device *dev = interface_to_usbdev(intf);

    printk("usbmouse is disconnet!\n");
    usb_kill_urb(usb_urb);
    usb_free_urb(usb_urb);
    usb_buffer_free(dev, len, usb_buffer_vir, usb_buffer_pys);
    input_unregister_device(uk_dev);
    input_free_device(uk_dev);
}

static struct usb_driver usbmouse_as_key_driver = {
    .name        = "usbmouse_as_key",
    .probe        = usbmouse_probe,
    .disconnect    = usbmouse_disconnect,
    .id_table    = usbmouse_id_table,
};

static int usbmouse_as_key_init(void)
{
    usb_register(&usbmouse_as_key_driver);

    return 0;
}

static void usbmouse_as_key_exit(void)
{
    usb_deregister(&usbmouse_as_key_driver);
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");
4th、 用USB鼠标实现左键L,右键S,中键ENTER的功能

通过上述实验,可以知道对于实现按键操作,只需要对usb_buffer_vir的第一个数组作分析即可。

测试4th:
不用拔插,可以只重新加载新驱动

cat /dev/tty1然后按鼠标左右键

hexdump /dev/event0

总结:


根据总线驱动设备模型,这个(usb_bus_type)里面有个".match"函数,左边是由USB总线驱动程序帮我们来发现USB新设备,它会注册一个USB设备并且会从右边的driver链表里面通过总线里面的".match"函数比较左边(usb_interface)结构中的接口与右边(usb_driver)结构中的"id_table",若能吻合,则调用(usb_driver)中的".probe"函数。(usb_bus_type)提供了这套机制。
以前的数据是从中断或者读寄存器得到,现在USB驱动程序中的数据从USB总线提供的读写函数得到。一旦有数据就会保存数据,然后由主机控制器发出中断请求
数据传输三要素:源(端点地址)、目的(分配的缓冲区)、长度(端点描述符里最大包大小),然后构造一个"usb_urb = usb_alloc_urb(0, GFP_KERNEL)",紧接着把三要素填充到"usb_urb",最后要想使用就得提交urb。

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

猜你喜欢

转载自www.cnblogs.com/luosir520/p/11457311.html