• LDD3 第13章 USB驱动程序


    通用串行总线(USB)是主机和外围设备之间的一种连接。最新USB规范修订增加了理论上高达480Mbps的高速连接。

    从拓扑上看,USB子系统并不是以总线的方式来布置的,它是一颗由几个点对点的连接构建而成的树。

    USB是四线缆:地线、电源线、两根信号线

    • USB主控制器负责询问每一个USB设备是否有数据需要发送。
    • 一个USB设备在没有主控制器要求的情况下是不能发送数据的。
    • 方便搭建即插即用的系统。
    • 只担当设备和主控制器之间通信通道的角色,对它所发送的数据没有任何特殊的内容和结构上的要求。
    • USB协议规范定义了一套任何特定类型的设备都可以遵循的标准。
    • 不同特定类型称为类(class),包括存储设备、键盘、鼠标、游戏杆、网络设备和调制解调器,和其他需要驱动的设备。

    Linux内核支持两种USB驱动程序:

    • 宿主(host)系统上的驱动程序
    • 设备(device)上的驱动程序
    • 宿主系统的USB驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制该设备如何作为一个USB设备和主机通信。

    一、USB设备基础

    USB设备非常复杂,官网www.usb.org中有详细描述

    Linux内部有USB核心(USB core)的子系统来处理大部分的复杂性。 

    端点

    USB通信最基本的形式是通过一个名为端点的东西。 USB端点只能往一个方向传输数据,可以看做单向管道。

    USB端点四种不同类型,具有不同的传输数据的方式:

    • 控制:控制端点用来控制对USB设备不同部分的访问。
    • 中断:每当USB宿主要求设备传输数据时,中断端点就以一个固定的速率来传输少量的数据。
    • 批量:批量端点传输大批量的数据。
    • 等时:可以传送大批量的数据,但数据是否到达是没有保证的。

    内核中使用struct usb_host_endpoint来描述USB端点:

    struct usb_host_endpoint {
        struct usb_endpoint_descriptor        desc;
        struct usb_ss_ep_comp_descriptor    ss_ep_comp;
        struct list_head        urb_list;
        void                *hcpriv;
        struct ep_device        *ep_dev;    /* For sysfs info */
    
        unsigned char *extra;   /* Extra descriptors */
        int extralen;
        int enabled;
    };
    struct usb_host_endpoint

    结构中包含struct usb_endpoint_descriptor包含了真正的端点信息,所有的USB特定数据。这些数据时设备自己定义的。

    /* USB_DT_ENDPOINT: Endpoint descriptor */
    struct usb_endpoint_descriptor {
        __u8  bLength;
        __u8  bDescriptorType;
    
        __u8  bEndpointAddress;       /* 这是特定端点USB地址,端点方向 */  
        __u8  bmAttributes;              /* 这是端点的类型 */
        __le16 wMaxPacketSize;        /* 端点一次可以处理的最大字节 */
        __u8  bInterval;                   /* 如果端点是中断类型 */
    
        /* NOTE:  these two are _only_ in audio endpoints. */
        /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
        __u8  bRefresh;
        __u8  bSynchAddress;
    } __attribute__ ((packed));
    struct usb_endpoint_descriptor

    接口

    USB端点被捆绑为接口。USB接口值处理一种USB逻辑连接,一些USB设备具有多个接口,Linux需要不同的驱动程序来处理。

    USB接口可以有其他的设置,这些是和接口的参数不同的选择。

    内核使用struct usb_interface结构体来描述USB接口,USB核心把该结构体传递给USB驱动程序。

    struct usb_interface {
        /* array of alternate settings for this interface,
         * stored in no particular order */
        struct usb_host_interface *altsetting;
    
        struct usb_host_interface *cur_altsetting;    /* the currently
                         * active alternate setting */
        unsigned num_altsetting;    /* number of alternate settings */
    
        /* If there is an interface association descriptor then it will list
         * the associated interfaces */
        struct usb_interface_assoc_descriptor *intf_assoc;
    
        int minor;            /* minor number this interface is
                         * bound to */
        enum usb_interface_condition condition;        /* state of binding */
        unsigned sysfs_files_created:1;    /* the sysfs attributes exist */
        unsigned ep_devs_created:1;    /* endpoint "devices" exist */
        unsigned unregistering:1;    /* unregistration is in progress */
        unsigned needs_remote_wakeup:1;    /* driver requires remote wakeup */
        unsigned needs_altsetting0:1;    /* switch to altsetting 0 is pending */
        unsigned needs_binding:1;    /* needs delayed unbind/rebind */
        unsigned reset_running:1;
        unsigned resetting_device:1;    /* true: bandwidth alloc after reset */
    
        struct device dev;        /* interface specific device info */
        struct device *usb_dev;
        atomic_t pm_usage_cnt;        /* usage counter for autosuspend */
        struct work_struct reset_ws;    /* for resets in atomic context */
    };
    /* struct usb_host_interface *altsetting 一个接口结构体数组,包含了所有可能用于该接口的可选设置 */
    /* unsigned num_altsetting:altsetting指针所指的可选设置的数量 */
    /*  struct usb_host_interface *cur_altsetting:指向altsetting数组内部的指针,表示该接口的当前活动设置 */
    /* int minor:如果捆绑到该接口的USB驱动程序使用USB主设备号 */
    /* struct usb_interface 结构体中还有其他的字段,不过USB驱动程序不需要考虑它们 */
    struct usb_interface

    配置

    USB接口本身被捆绑为配置,一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。

    Linux使用struct usb_host_config结构体来描述USB配置,使用struct usb_device结构体来描述整个USB设备。

    struct usb_host_config {
        struct usb_config_descriptor    desc;
    
        char *string;        /* iConfiguration string, if present */
    
        /* List of any Interface Association Descriptors in this
         * configuration. */
        struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];
    
        /* the interfaces associated with this configuration,
         * stored in no particular order */
        struct usb_interface *interface[USB_MAXINTERFACES];
    
        /* Interface information available even when this is not the
         * active configuration */
        struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
    
        unsigned char *extra;   /* Extra descriptors */
        int extralen;
    };
    
    struct usb_device {
        int        devnum;
        char        devpath[16];
        u32        route;
        enum usb_device_state    state;
        enum usb_device_speed    speed;
    
        struct usb_tt    *tt;
        int        ttport;
    
        unsigned int toggle[2];
    
        struct usb_device *parent;
        struct usb_bus *bus;
        struct usb_host_endpoint ep0;
    
        struct device dev;
    
        struct usb_device_descriptor descriptor;
        struct usb_host_config *config;
    
        struct usb_host_config *actconfig;
        struct usb_host_endpoint *ep_in[16];
        struct usb_host_endpoint *ep_out[16];
    
        char **rawdescriptors;
    
        unsigned short bus_mA;
        u8 portnum;
        u8 level;
    
        unsigned can_submit:1;
        unsigned persist_enabled:1;
        unsigned have_langid:1;
        unsigned authorized:1;
        unsigned authenticated:1;
        unsigned wusb:1;
        int string_langid;
    
        /* static strings from the device */
        char *product;
        char *manufacturer;
        char *serial;
    
        struct list_head filelist;
    #ifdef CONFIG_USB_DEVICE_CLASS
        struct device *usb_classdev;
    #endif
    #ifdef CONFIG_USB_DEVICEFS
        struct dentry *usbfs_dentry;
    #endif
    
        int maxchild;
        struct usb_device *children[USB_MAXCHILDREN];
    
        u32 quirks;
        atomic_t urbnum;
    
        unsigned long active_duration;
    
    #ifdef CONFIG_PM
        unsigned long connect_time;
    #ifdef CONFIG_SMM6260_MODEM
        int autosuspend_delay;
    #endif
        unsigned do_remote_wakeup:1;
        unsigned reset_resume:1;
    #endif
        struct wusb_dev *wusb_dev;
        int slot_id;
    };
    struct usb_host_config和struct usb_device

    USB设备驱动程序通常需要把一个给定的struct usb_interface结构体数据转换为struct usb_device结构体,USB核心在很多函数调用汇总都需要该结构体。

    interface_to_usbdev用于该转换功能的函数。

    简而言之,USB之间的逻辑单元之间的关系可以如下描述:

    • 设备通常具有一个或更多的配置
    • 配置经常具有一个或更多的接口
    • 接口通常具有一个或更多的设置
    • 接口没有或具有一个以上的端点

    USB和sysfs

    USB sysfs设备命名方案为:

    根集线器 - 集线器端口号 : 配置 . 接口

    对于一个两层的树,其设备名类似于:

    根集线器 - 集线器端口号 - 集线器端口号 : 配置 . 接口

    sysfs并没有展示USB设备所有的不同部分,它只限于接口级别。不过可以通过/proc/bus/usb目录中,从/proc/bus/usb/device文件,

    可以知道系统中存在的所有USB设备的可选配置。

    USB urb

    Linux内核中的USB代码通过一个称为Urb的东西和所有的USB设备通信。

    struct urb结构体来描述这个请求块,可以在include/linux/usb.h文件中找到。

    urb被用来以一种异步的方式往/从特定的USB设备上的特定USB端点发送/接收数据。

    创建和销毁urb

    struct urb 接口体不能在驱动程序中或者另一个结构体中静态地创建,因为这样会破坏USB核心对urb所使用的引用计数机制。

    struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
    iso_packets:是该urb应该包含的等时数据包的数量,如果不是等时的,设置为0
    mem_flags:传递给用于从内核分配内存的kmalloc函数
    返回:urb的指针,NULL则是错误
    
    void usb_free_urb(struct urb *urb);
    释放urb指针

    中断urb

    正确地初始化即将被发送到USB设备的中断端点的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);
    struct urb *urb:指向需要初始化的urb指针
    struct usb_device *dev:该urb所发送的目标USB设备
    unsigned int pipe:该urb所发送的目标USB设备的特定端点
    void *transger_buffer:用于保存外发数据或者接受数据的缓冲区的指针
    int buffer_length:transfer_buffer指针所指向的缓冲区的大小
    usb_complete_t complete:指向当该urb结束之后调用的结束处理例程的指针
    void *context:指向一个小数据块,将被添加到urb结构体中以便进行结束处理例程后面的查找
    int interval:该urb应该被调度的间隔

    批量urb

    void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
                    usngined int pipe, void *transfer_buffer,
                    int buffer_length, usb_complete_t complete,
                    void *context);

    控制urb

    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);
    参数和usb_fill_bulk_urb完全一样,除了一个新参数
    unsgined char *setup_packet:指向即将被发送到端点的设置数据包的数据

    等时urb

    等时urb没有中断、控制和批量urb类似的初始化函数,需要手动的进行初始化

    urb->dev = dev;
    urb->context = uvd;
    urb->pipe = urb_rcvisopipe(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;
    for(j=0;j<FRAMES_PER_DESC;j++) {
        urb->iso_frame_desc[j].offset = j;
        urb->iso_frame_desc[j].length = 1;
    }
    等时urb初始化

    提交urb

    int usb_submit_urb(struct urb *urb, int mem_flags);
    urb:指向即将被发送到设备的urb指针
    mem_flags:传递给kmalloc的同一个参数
        GFP_ATOMIC:
        GFP_NOIO:在所有存储类型的设备的错误处理路径中也应该使用它
        GFP_KERNEL:该值应该在前述类别之外的所有情况中使用

    结束urb:结束回调处理例程

    取消urb

    int usb_kill_urb(struct urb *urb);
    int usb_unlink_urb(struct urb *urb);

    二、编写USB驱动程序

    对于PCI设备,有许多用来初始化该结构体的宏:

    USB_DEVICE(vendor, product)
    创建一个struct usb_device_id结构体,仅指定制造商和产品ID匹配
    USB_DEVICE_VER(vendor, product, lo, hi)
    创建一个struct usb_device_id结构体,指定制造商和产品ID值相匹配
    USB_DEVICE_INFO(class, subclass, protocol)
    创建一个struct usb_device_id结构体,仅和USB设备的指定类型相匹配
    USB_INTERFACE_INFO(class, subclass, protocol)
    创建一个struct usb_device_id结构体,仅和USB接口的指定类型相匹配
    
    struct usb_device_id表将被定义为:
    /* 该驱动程序支持的设备列表 */
    static struct usb_device_id skel_table [ ] = {
        { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
        {  }
    };
    MODULE_DEVICE_TABLE(usb, skel_table);

    注册USB驱动程序

    创建一个有效的struct usb_driver结构体只需要初始化五个字段:

    static struct usb_driver skel_driver = {
        .owner = THIS_MODULE,
        .name = "skeleton",
        .id_table = skel_table,
        .probe = skel_probe,
        .disconnect = skel_disconnect,
    };

    接口函数原型:

    int (*probe)(struct usb_interface *intf, const struct usb_device_id *id)
    探测函数
    void (*disconnect)(struct usb_interface *intf)
    断开函数
    int (*ioctl)(struct usb_interface *intf, unsigned int code, void *buf)
    ioctl函数
    int (*suspend)(struct usb_interface *intf, u32 state)
    挂起函数
    int (*resume)(struct usb_interface *intf)
    恢复函数
    函数接口

    探测和断开的细节

    /* 设置端点信息 */
    /* 只使用第一个批量IN和批量OUT端点 */
    iface_desc = interface->cur_altsetting;
    for(i=0;i<iface_desc->desc.bNumEndpoints;++i) {
        endpoint = &iface_desc->endpoint[i].desc;
        if(!dev->bulk_in_endpointAddr && 
            (endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                == USB_ENDPOINT_XFER_BULK)) {
            /* 发现一个批量IN类型的端点 */
            buffer_size = endpoint->wMaxPacketSize;
            dev->bulk_in_size = buffer_size;
            dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
            dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
            if(!dev->bulk_in_buffer) {
                err("Could not allocate bulk_in_buffer");
                goto error;
            }
        }
        if(!dev->bulk_out_endpointAddr &&
            !(endpoint->bEndpointAddress & USB_DIR_IN) &&
            ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                == USB_ENDPOINT_XFER_BULK)) {
            /* 发现一个批量OUT类型的端点 */
            dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
    }
    if(!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
        err("Could not find both bulk-in and bulk-out endpoints");
        goto error;
    }
    探测IN和OUT端点

    提交和控制urb

    /* 分配urb来把数据传输给设备 */
    urb = usb_alloc_urb(0, GFP_KERNEL);
    if(!urb) {
        retval = -ENOMEM;
        goto error;
    }
    /* 创建DMA缓冲区来以最高效的方式发送数据到设备 */
    buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
    if(!buf) {
        retval = -ENOMEM;
        goto error;
    }
    if(copy_from_user(buf, user_buffer, count)) {
        retval = -EFAULT;
        goto error;
    }
    /* 数据被复制到局部缓冲区中,urb必须可以提交给USB核心之前被正确的初始化 */
    usb_fill_bulk_urb(urb, dev->udev,
        usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
        buf, count, skel_write_bulk_callback, dev);
    urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
    /* urb正确分配,数据被正确复制,urb正确初始化,然后可以提交给USB核心以传输到设备 */
    /* 把数据从批量端口发出 */
    retval = usb_submit_urb(urb, GFP_KERNEL);
    if(retval) {
        err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
        goto error;
    }
    /* urb被成功地传输到USB设备之后,urb毁掉函数将被USB核心调用 */
    static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
    {
        /* sync/async解连接故障不是错误 */
        if(urb->status &&
            !(urb->status == -ENOENT ||
              urb->status == -ECONNRESET ||
              urb->status == -ESHUTDOWN)) {
                  dbg("%s - nonzero write bulk status received: %d",
                    __FUNCTION__, urb->status);
              }
    
              /* 释放已分配的缓冲区 */
              usb_buffer_free(urb->dev, urb->transfer_buffer_length,
                urb->transfer_buffer, urb->transfer_dma);
    }
    urb的使用流程

    不使用urb的USB传输

    有时候USB驱动程序只是要发送或者接受一些简单的USB数据,而不想把上面的流程走一遍,有两个函数提供了更简单接口和函数的使用。

    usb_bulk_msg

    int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
        void *data, int len, int *actual_length,
        int timeout);
    struct usb_device *usb_dev:发送的目标USB指针
    unsigned int pipe:目标USB的特定端点
    void *data:如果是OUT端点,指向即将发送到设备的数据的指针
    int len:data参数所指缓冲区的大小
    int *actual_length:指向保存实际传输字节数的位置的指针
    int timeout:等待的超时时间

    函数调用例子:

    /* 进行阻塞的批量读以设别获取数据 */
    retval = usb_bulk_msg(dev->udev,
        usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
        dev->bulk_in_buffer,
        min(dev->bulk_in_size, count),
        &count, HZ*10);
    /* 如果读取成功,复制数据到用户空间 */
    if(!retval) {
        if(copy_to_user(buffer, dev->bulk_in_buffer, count))
            retval = -EFAULT;
        else
            retval = count;
    }
    使用例子

    usb_control_msg

    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);
    struct usb_device *dev:控制消息所发送的目标USB设备的指针
    unsigned int pipe:该控制消息所发送的目标USB设备的特定端点
    __u8 request:控制消息的USB请求值
    __u8 requesttype:控制消息的USB请求类型值
    __u16 value:USB消息值
    __u16 index:USB消息索引值
    void *data:一个OUT端点,指向即将发送到设备的数据指针
    __u16 size:data参数所指缓冲区的大小
    int timeout:等待的超时时间

    其他USB数据函数

    int usb_get_descriptor(struct usb_device *dev, unsigned char type,
        unsigned char index, void *buf, int size);
    struct usb_device *usb_dev:指向想要获取描述符的目标USB设备的指针
    unsigned char type:描述符的类型
    unsigned char index:应该从设备获取的描述符的编号
    void *buf:指向复制描述符到其中的缓冲区的指针
    int size:buf变量所指的内存大小
  • 相关阅读:
    Hive的安装和使用
    Redis 慢查询日志
    GO语言-数组
    ZooKeeper-3.3.4集群安装配置
    GO语言-基础语法:循环
    GO语言-基础语法:条件判断
    GO语言-基础语法:变量定义
    nginx限制下载速度
    Centos7下Etcd集群搭建
    浅谈spj
  • 原文地址:https://www.cnblogs.com/ch122633/p/9695838.html
Copyright © 2020-2023  润新知