71 linux usb设备驱动

usb接口有大,小口之分
usb有主机(host,大口)/设备(otg, 小口)工作模式

usb主机可以主动去操作设备, 主机接口由4根线(vcc, gnd, d+, d-)
usb设备被主机操作, 有5根线(vcc, gnd, d+, d-, id), id线可用于区分设备接口

// otg接口即可作主机角色,也可以作设备的角色

这里写图片描述

这里写图片描述

usb接口可以像树型一样的进行扩展, 可用usb hub芯片(FE1.1S, GL850G)将一个usb的硬件接口分成4个硬件接口。

这里写图片描述

usb设备会在d+, d-这两根上设一个上拉, 当接到usb主机时,usb主机的d+/d-的电平会发生变化

usb的接口协议版本:
usb1.0
usb1.1
usb2.0
usb3.0

usb是高度标准化的接口, usb控制器与usb设备都会有一套标准规范, 以便两者可以互相兼容.
usb设备一般情况下都会有固件, 协同usb控制器的操作.所以有可能在usb设备接入后,需要设备驱动传输固件到设备里执行
/

usb主机控制器(host)和usb设备控制器(otg)的驱动通常由soc厂商负责驱动好, 我们负责的是设备驱动

usb core里面制定好usb主机控制器的驱动接口的实现, 和设备驱动调用控制器驱动的接口
usb主机控制器的版本:
uhci: usb1.x
ohci: usb1.1 (低速1.5Mbps, 全速12Mbps)
ehci: usb2.0 (480Mbps)
xhci: usb3.0
/
查看当前系统下有哪些usb设备及相关的信息:
可用lsusb命令查看, lsusb -vt

// 输出的内容里: bus表示控制器, port表示接在控制上的设备

当一个USB设备没有相应的设备驱动,接上系统时也会知道具体是什么设备,原因:
usb设备里会存放有idVendor, idProduct等信息, 当接入系统时, 内核里的usb子系统会调用usb主机控制器驱动把usb设备的信息读出来, 再根据idVendor, idProduct信息来查找并匹配设备驱动

//在usb设备驱动模型里,不再需要我们描述硬件相关资源的设备. usb子系统会在usb设备接入时自动创建相关的设备信息.

//
/// usb otg驱动 /// 使用设备控制器让我们整个板子做一个USB设备的角色
选择otg驱动:

    make menuconfig
    Device Drivers  --->
        [*] USB support  ---> 
            <*>   USB Gadget Support  --->
                <M>   USB Gadget Drivers //选择这步后,才可以选择下面的选项

                <M>     Mass Storage Gadget //通过开发板的设备接口接上PC,会出现u盘(也就是开发板作U盘的角色)
                <M>     Serial Gadget       //开发板作uart的角色
                <M>     Printer Gadget      //作打印机角色
保存退出,重编内核并使用新内核镜像启动。
在内核源码的根目录:  
    make modules  
    make modules_install INSTALL_MOD_PATH=/板子网络文件系统的根目录

板子上操作:
    cd /lib/modules/3.4.112/kernel/drivers/usb/gadget/
    此目录下有: g_mass_storage.ko  g_serial.ko g_webcam.ko
    可用modinfo查看驱动模块的参数。

    接上板子的usb的otg线,让板子mmcblk0p7作u盘
    modprobe g_mass_storage file=/dev/mmcblk0p7  removable=1 
    echo 2 > /sys/devices/platform/sunxi_usb_udc/otg_role  //让板子的otg接口作设备用.

usb主机接口的设备驱动
在linux内核里通常会有一个结构体来描述一个硬件设备的资源, pci和usb的设备不需我们来描述, usb子系统会在usb设备接入时自动创建对应的结构体信息, 所以我们只要实现设备的驱动就可以了

//usb的设备驱动由usb_driver对象来描述

struct usb_driver {
    const char *name;
    int (*probe) (struct usb_interface *intf,
              const struct usb_device_id *id);
    void (*disconnect) (struct usb_interface *intf); //相当于前面的remove函数
    const struct usb_device_id *id_table; //匹配用
        ...
};

//usb设备驱动与usb设备的匹配有三种选择:
struct usb_device_id {
    __u16       match_flags; //用于记录用哪种方法匹配

    __u16       idVendor; //指定按usb设备的id来匹配
    __u16       idProduct;
    ...

    __u8        bDeviceClass; //按设备类型来匹配
    __u8        bDeviceSubClass;
    __u8        bDeviceProtocol;

    __u8        bInterfaceClass; //按接口类型来匹配
    __u8        bInterfaceSubClass;
    __u8        bInterfaceProtocol;
        ...
};


struct usb_device 对象表示一个usb设备

一个USB设备里由配置,接口, 端点组成, 一个接口(interface)需要一个usb设备驱动

(endpoint)端点: 用于主机与设备间通信通道。 一个端点只能一个方向。 输入/输出端点

(interface)接口: 一个接口表示USB设备的一种功能, 如一个USB设备有多种功能,则会有多个接口。 端点属于某个接口

(config)配置: 用于设置在某一时刻时USB设备所用的功能,接口需要配置来实现功能。

设备通常有一个或多个配置.
配置常常有一个或多个接口
接口常常有一个或多个设置.
接口有零或多个端点.

///
usb的端点分成4种:
control: 通常用作配置设备或者读取设备信息

INTERRUPT: 中断端点传送小量的数据, 以固定的速率在每次 USB 主请求设备数据时. usb鼠标, 键盘

BULK: 块端点传送大量的数据.普遍在打印机, 存储器, 和网络设备上    

ISOCHRONOUS: 同步端点也传送大量数据, 但是这个数据常常不被保证它完成. 这些端点用在可以
        处理数据丢失的设备中, 并且更多依赖于保持持续的数据流. 实时数据收集, 例如音频和视频设备, 一直都使用这些端点

不同类型的设备使用的端点类型也可能不同,但每个USB设备都会有control端点.有些USB设备只有control端点

USB 端点在内核中使用结构 struct usb_host_endpoint 来描述. 这个结构包含真实的端
点信息在另一个结构中, 称为 struct usb_endpoint_descriptor
struct usb_endpoint_descriptor {
    ....
    __u8  bEndpointAddress; //端点地址, 位掩码USB_DIR_OUT 和 USB_DIR_IN 可用来和这个成员比对
    __u8  bmAttributes; //这是端点的类型. 位掩码 USB_ENDPOINT_XFERTYPE_MASK 应当用来和这个值比对,来决定这个端点是否是 USB_ENDPOINT_XFER_ISOC, USB_ENDPOINT_XFER_BULK, 或者是类型 USB_ENDPOINT_XFER_INT

    __le16 wMaxPacketSize; //以字节计的这个端点可一次处理的最大大小
    __u8  bInterval; //如果这个端点是中断类型的, 这个值是为这个端点设置的间隔, 这个值以毫秒表示
    ...
} __attribute__ ((packed));

/////////////////////
USB 接口在内核中使用 struct usb_interface 结构来描述

struct usb_interface {
    struct usb_host_interface *altsetting; //包含所有预备设置的接口结构的数组
    struct usb_host_interface *cur_altsetting;  //指向数组 altsetting 的一个指针, 表示这个接口当前的激活的设置
    unsigned num_altsetting;  //由 altsetting 指针指向数组的元素个数

    int minor;    
    ...
};

///////////
struct usb_host_interface {
    struct usb_interface_descriptor desc; // desc.bNumEndpoints 表示端点个数
    struct usb_host_endpoint *endpoint; //端点数组首地址, endpoint[i].desc可取到struct usb_endpoint_descriptor对象的信息
};





linux 描述 USB 配置使用结构 struct usb_host_config 和整个 USB 设备使用结构
struct usb_device. USB 设备驱动通常不会需要读写这些结构的任何值, 因此它们在这
里没有详细定义

一个 USB 设备驱动通常不得不转换数据从给定的 struct usb_interface 结构到 struct
usb_device 结构, USB 核心需要给很多的函数调用. 为此, 提供有函数
interface_to_usbdev


实验获取usb设备的信息:

去掉内核的usb鼠标,键盘,蓝牙鼠标等的驱动
make menuconfig
 Device Drivers  --->
    [ ] HID Devices  --->
保存后,重编内核,并使用新镜像启动系统。

test.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>

int myprobe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_host_interface *hintf = intf->cur_altsetting;
    int n, i;
    struct usb_host_endpoint *endp;

    printk("num_alt = %d\n", intf->num_altsetting); 
    n = hintf->desc.bNumEndpoints;
    endp = hintf->endpoint;
    printk("endpoints = %d\n", n);

    for (i = 0; i < n; i++)
    {   
            printk("endpoint address: %x\n", endp[i].desc.bEndpointAddress);
            printk("direction : ");
            if (endp[i].desc.bEndpointAddress & USB_DIR_IN)
                printk("input\n");
            else
                printk("output\n");

            printk("type : ");
            switch (endp[i].desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
            {
            case USB_ENDPOINT_XFER_ISOC:
                printk("isoc \n");
                break;
            case USB_ENDPOINT_XFER_BULK:
                printk("bulk \n");
                break;
            case USB_ENDPOINT_XFER_INT:
                printk("int interval = %d\n", endp[i].desc.bInterval);
                break;
            case USB_ENDPOINT_XFER_CONTROL:
                printk("control \n");
                break;
            }

            printk("maxpacketSize = %d\n", endp[i].desc.wMaxPacketSize);
    }

    printk("in %s, %d\n", __func__, __LINE__);
    return 0;
}

void mydisconnect(struct usb_interface *intf)
{
    printk("in %s, %d\n", __func__, __LINE__);
}

struct usb_device_id ids[] = {
    {USB_DEVICE(0x046d, 0xc05a)},  // idVendor=046d, idProduct=c05a  mouse
    {USB_DEVICE(0x413c, 0x2107)},  // idVendor=413c, idProduct=2107  keyboard
    {USB_DEVICE(0x0951, 0x1666)},  // idVendor=0951, idProduct=1666  u_disk
    {},
};

struct usb_driver myusb = {
    .name = "myusb",
    .probe = myprobe,
    .disconnect = mydisconnect,

    .id_table = ids,
};


module_usb_driver(myusb);
MODULE_LICENSE("GPL");

///处理usb设备的数据/
linux 内核中的 USB 代码和所有的 USB 设备通讯使用称为 urb 的东西( USB request block)
也就是说所有USB设备要通过USB控制器收发数据需使用urb

1)被一个 USB 设备驱动创建.
2)安排给一个特定 USB 设备的特定端点.
3)提交给 USB 核心, 被 USB 设备驱动.
4)提交给特定设备的被 USB 核心指定的 USB 主机控制器驱动, . // usb_submit_urb(urb, GFP_ATOMIC);
5)被 USB 主机控制器处理, 它做一个 USB 传送到设备.
6)当 urb 完成, USB 主机控制器驱动通知 USB 设备驱动.

struct urb {
    ...
    struct usb_device *dev;     //指向这个 urb 要发送到的 struct usb_device 的指针
    unsigned int pipe;      //用于与设备端点通信的通道。不同类型的端点需要用不同的函数来的创建:
                // usb_sndctrlpipe(...),  usb_rcvctrlpipe, usb_sndbulkpipe, usb_rcvbulkpipe, usb_sndintpipe, usb_rcvintpipe
    ...
    void *transfer_buffer;  //用于存放发送数据到设备(对一个 OUT urb)或者从设备中获取数据(对于一个 IN
urb)的缓冲区地址
    u32 transfer_buffer_length; //数据缓冲区的大小
    u32 actual_length;      //当urb完成后, 在数据缓冲区里实际存放数据的大小
        ...
    int interval;         //中断端点设备的轮询间隔时间
    ...
    void *context;        //存放设备数据的缓冲区, 像dev_set_drvdata(...)这样用的数据缓冲区
    usb_complete_t complete;    //urb传输完成后, 触发的处理函数的地址。函数原型void (*complete)(struct urb *urb).
    ...
};

创建和销毁 urb的函数:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags); //当不是同步端点时,iso_packets为0. mem_flags通常设为GFP_KERNEL.
void usb_free_urb(struct urb *urb);

urb结构体成员较多,内核提供一些函数以方便初始化urb:

//中断端点:
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, 
               unsigned int pipe, void *transfer_buffer,
               int buffer_length, usb_complete_t complete,
                void *context, int interval);

//块端点:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
        unsigned int pipe, void *transfer_buffer,
        int buffer_length, usb_complete_t complete,
        void *context);

//控制端点:
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
        unsigned int pipe, unsigned char *setup_packet,
        void *transfer_buffer, int buffer_length,
        usb_complete_t complete, void *context);

//同步端点没有直接调用函数,只能一个成员一个成员来初始化:
    urb->dev = dev;
    urb->context = uvd;
    urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
    urb->interval = 1;
    urb->transfer_flags = URB_ISO_ASAP;
    urb->transfer_buffer = cam->sts_buf[i];
    urb->complete = konicawc_isoc_irq;
    urb->number_of_packets = FRAMES_PER_DESC;
    urb->transfer_buffer_length = FRAMES_PER_DESC;

提交urb, 即让usb控制来传输数据, 当完成后就调用urb里的complete函数.

int usb_submit_urb(struct urb *urb, int mem_flags); // mem_flags通常设为GFP_ATOMIC

//
实验,获取中断端点设备的数据:
test.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/slab.h>

struct urb *myurb;

void mycomplete(struct urb *urb)
{
    int i;
    unsigned char *data = urb->context; 


    for (i = 0; i < urb->actual_length; i++)
    {
        printk("%02x ", data[i]);
    }
    printk("\n");
    usb_submit_urb(myurb, GFP_ATOMIC);
}

int myprobe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_host_interface *hintf = intf->cur_altsetting;
    int n, i;
    struct usb_host_endpoint *endp;
    unsigned int pipe;
    struct usb_device *dev;
    unsigned char *buffer;

    printk("num_alt = %d\n", intf->num_altsetting); 
    n = hintf->desc.bNumEndpoints;
    endp = hintf->endpoint;
    printk("endpoints = %d\n", n);
//
    myurb = usb_alloc_urb(0, GFP_KERNEL);

    dev = interface_to_usbdev(intf);
    pipe = usb_rcvintpipe(dev, endp[0].desc.bEndpointAddress);
    buffer = kzalloc(endp[0].desc.wMaxPacketSize, GFP_KERNEL);

    usb_fill_int_urb(myurb, dev, pipe, buffer, endp[0].desc.wMaxPacketSize, 
            mycomplete, buffer, endp[0].desc.bInterval);

    printk("in %s, %d\n", __func__, __LINE__);
    usb_submit_urb(myurb, GFP_ATOMIC);
    return 0;
}

void mydisconnect(struct usb_interface *intf)
{
    usb_free_urb(myurb);
    printk("in %s, %d\n", __func__, __LINE__);
}

struct usb_device_id ids[] = {
    {USB_DEVICE(0x046d, 0xc05a)},  // idVendor=046d, idProduct=c05a  mouse
    {USB_DEVICE(0x413c, 0x2107)},  // idVendor=413c, idProduct=2107  keyboard
    {USB_DEVICE(0x0951, 0x1666)},  // idVendor=0951, idProduct=1666  u_disk
    {},
};

struct usb_driver myusb = {
    .name = "myusb",
    .probe = myprobe,
    .disconnect = mydisconnect,

    .id_table = ids,
};


module_usb_driver(myusb);
MODULE_LICENSE("GPL");


另:通常情况下,设备驱动里都是通过urb来获取设备的数据。但有些说法,说不用urb,而通过一些函数来完成工作。

//下面两个函数用于简单的传输数据, 函数内部其实还是创建urb, 提交urb.
usb_bulk_msg 创建一个 USB 块 urb 并且发送它到特定的设备, 接着在返回到调用者之
前等待完成. 它定义为:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
    void *data, int len, int *actual_length,
    int timeout);


usb_control_msg 函数就像 usb_bulk_msg 函数, 除了它允许一个驱动发送和结束 USB
控制信息:
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8
    requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值