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变量所指的内存大小

 

posted @ 2018-09-24 16:25  习惯就好233  阅读(227)  评论(0编辑  收藏  举报