linux设备驱动之USB数据传输分析 一

发布时间:2024-07-18  

三:传输过程的实现
说到传输过程,我们必须要从URB开始说起,这个结构的就好比是网络子系统中的skb,好比是I/O中的bio.USB系统的信息传输就是打成URB结构,然后再过行传送的.
URB的全称叫USB request block.下面从它的接口说起.
3.1:URB的相关接口
1:URB的创建
URB的创建是由usb_alloc_urb()完成的.这个函数会完成URB内存的分配和基本成员的初始化工作.代码如下:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
{
   struct urb *urb;

    urb = kmalloc(sizeof(struct urb) +
        iso_packets * sizeof(struct usb_iso_packet_descriptor),
        mem_flags);
    if (!urb) {
        err("alloc_urb: kmalloc failed");
        return NULL;
    }
    usb_init_urb(urb);
    return urb;
}
这个函数有两个参数,一个是iso_packets.仅仅用于ISO传输.表示ISO数据包个数,如果用于其它类型的传输,此参数为0.另一个是mem_flags.是分配内存的参数.
Usb_init_urb()如下:
void usb_init_urb(struct urb *urb)
{
    if (urb) {
        memset(urb, 0, sizeof(*urb));
        kref_init(&urb->kref);
        INIT_LIST_HEAD(&urb->anchor_list);
    }
}
由此可以看到,它的初始化只是初始化了引用计数和ahchor_list链表.这个链表在URB被锁定的时候会用到.

2:URB的初始化
USB2.0 spec中定义了四种传输,为别为ISO,INTER,BULK,CONTORL.linux kernel为INTER,BULK,CONTORL的URB初始化提供了一些API,ISO的传输只能够手动去初始化.这些API如下:
static inline 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_fn,
                    void *context)
static inline 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_fn,
                     void *context)
static inline 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_fn,
                    void *context,
                    int interval)
分别用来填充CONTORL,BULK,INT类型的URB.
观察他们的函数原型,发现有很多相的的参数.先对这些参数做一下解释:
Urb:是要初始化的urb
Dev:表示消息要被发送到的USB设备
Pipe:表示消息被发送到的端点
transfer_buffer:表示发送数据的缓冲区
length:就是transfer_buffer所表示的缓冲区大小
context:完成处理函数的上下文
complete_fn:传输完了之后要调用的函数.
usb_fill_control_urb()的setup_packet:即将被发送到端点的设备数据包
usb_fill_int_urb()中的interval:这个urb应该被调度的间隔.
函数的实际都是差不多的.以usb_fill_control_urb()为例:
static inline 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_fn,
                    void *context)
{
    urb->dev = dev;
    urb->pipe = pipe;
    urb->setup_packet = setup_packet;
    urb->transfer_buffer = transfer_buffer;
    urb->transfer_buffer_length = buffer_length;
    urb->complete = complete_fn;
    urb->context = context;
}
如上所示,只是将函数的参数赋值给了URB相关的成员而已.
另外,关于ISO的URB初始化虽然没有可以调用的API,但它的初始化也很简单,对应就是填充几个成员而已.
另外,对于pipe的参数.有一系列辅助的宏.如下示:
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint)   
    ((PIPE_CONTROL 
#define usb_rcvctrlpipe(dev,endpoint)   
    ((PIPE_CONTROL 
#define usb_sndisocpipe(dev,endpoint)   
    ((PIPE_ISOCHRONOUS 
#define usb_rcvisocpipe(dev,endpoint)   
    ((PIPE_ISOCHRONOUS 
#define usb_sndbulkpipe(dev,endpoint)   
    ((PIPE_BULK 
#define usb_rcvbulkpipe(dev,endpoint)   
    ((PIPE_BULK 
#define usb_sndintpipe(dev,endpoint)   
    ((PIPE_INTERRUPT 
#define usb_rcvintpipe(dev,endpoint)   
    ((PIPE_INTERRUPT 
这个宏都是根据usb2.0 spec的规范来设计的.

3:提交URB
提交urb的接口是usb_submit_urb().代码如下:
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
    int             xfertype, max;
    struct usb_device       *dev;
    struct usb_host_endpoint    *ep;
    int             is_out;

    if (!urb || urb->hcpriv || !urb->complete)
        return -EINVAL;
    dev = urb->dev;
    if ((!dev) || (dev->state 
        return -ENODEV;

    /* For now, get the endpoint from the pipe.  Eventually drivers
     * will be required to set urb->ep directly and we will eliminate
     * urb->pipe.
     */

    //取得要传输的端口.对端地址是由方向+dev address+port number组成的
    ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out)
            [usb_pipeendpoint(urb->pipe)];
    if (!ep)
        return -ENOENT;

    urb->ep = ep;
    urb->status = -EINPROGRESS;
    urb->actual_length = 0;

    /* Lots of sanity checks, so HCDs can rely on clean data
     * and don't need to duplicate tests
     */
     //取得ep的传输类型
    xfertype = usb_endpoint_type(&ep->desc);
    //如果是控制传输.端点0默认是控制传输
    if (xfertype == USB_ENDPOINT_XFER_CONTROL) {
        //控制传输的urb如果没有setup_packet是非法的
        struct usb_ctrlrequest *setup =
                (struct usb_ctrlrequest *) urb->setup_packet;

        if (!setup)
            return -ENOEXEC;
        //判断是否是out方向的传输
        is_out = !(setup->bRequestType & USB_DIR_IN) ||
                !setup->wLength;
    } else {
        //如果不是控制传输,在端点描述符的bEndportAddress的bit7 包含有端点的传输方向
        is_out = usb_endpoint_dir_out(&ep->desc);
    }

    /* Cache the direction for later use */
    //根据传输方向.置urb->transfer_flags的方向位
    urb->transfer_flags = (urb->transfer_flags & ~URB_DIR_MASK) |
            (is_out ? URB_DIR_OUT : URB_DIR_IN);

    //根据usb2.0 spec.除控制传输外的其它传输只有在config状态的时候才能进行
    if (xfertype != USB_ENDPOINT_XFER_CONTROL &&
            dev->state 
        return -ENODEV;

    //传送/接收的最大字节.如果这个最大巧若拙字节还要小于0,那就是非法的
    max = le16_to_cpu(ep->desc.wMaxPacketSize);
    if (max 
        dev_dbg(&dev->dev,
            "bogus endpoint ep%d%s in %s (bad maxpacket %d)n",
            usb_endpoint_num(&ep->desc), is_out ? "out" : "in",
            __FUNCTION__, max);
        return -EMSGSIZE;
    }

    /* periodic transfers limit size per frame/uframe,
     * but drivers only control those sizes for ISO.
     * while we're checking, initialize return status.
     */
     //如果是实时传输
    if (xfertype == USB_ENDPOINT_XFER_ISOC) {
        int n, len;

        /* "high bandwidth" mode, 1-3 packets/uframe? */
        //如果是高速传输.则要修正它的MAX值
        //高速传输时, 一个微帧内可以修输多个数据.bit 11~bit12用来表示一个微帧内
        //传输包的个数.
        //在USB1.1中是不支持HIGH的
        if (dev->speed == USB_SPEED_HIGH) {
            int mult = 1 + ((max >> 11) & 0x03);
            max &= 0x07ff;
            max *= mult;
        }

        //实现传输的数据包数目不能小于等于0
        if (urb->number_of_packets 
            return -EINVAL;
        //urb->number_of_packets: 实时数据包个数.每个实时数据包对应urb->iso_frame_desc[]中的一项
        for (n = 0; n number_of_packets; n++) {
            len = urb->iso_frame_desc[n].length;
            if (len  max)
                return -EMSGSIZE;
            urb->iso_frame_desc[n].status = -EXDEV;
            urb->iso_frame_desc[n].actual_length = 0;
        }
    }

    /* the I/O buffer must be mapped/unmapped, except when length=0 */
    //如果要传输的缓存区大小小于0.非法
    if (urb->transfer_buffer_length 
        return -EMSGSIZE;

#ifdef DEBUG
    /* stuff that drivers shouldn't do, but which shouldn't
     * cause problems in HCDs if they get it wrong.
     */
    {
    unsigned int    orig_flags = urb->transfer_flags;
    unsigned int    allowed;

    /* enforce simple/standard policy */
    allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP |
            URB_NO_INTERRUPT | URB_DIR_MASK | URB_FREE_BUFFER);
    switch (xfertype) {
    case USB_ENDPOINT_XFER_BULK:
        if (is_out)
            allowed |= URB_ZERO_PACKET;
        /* FALLTHROUGH */
    case USB_ENDPOINT_XFER_CONTROL:
        allowed |= URB_NO_FSBR; /* only affects UHCI */
        /* FALLTHROUGH */
    default:            /* all non-iso endpoints */
        if (!is_out)
            allowed |= URB_SHORT_NOT_OK;
        break;
    case USB_ENDPOINT_XFER_ISOC:
        allowed |= URB_ISO_ASAP;
        break;
    }
    urb->transfer_flags &= allowed;

    /* fail if submitter gave bogus flags */
    if (urb->transfer_flags != orig_flags) {
        err("BOGUS urb flags, %x --> %x",
            orig_flags, urb->transfer_flags);
        return -EINVAL;
    }
    }
#endif
    /*
     * Force periodic transfer intervals to be legal values that are
     * a power of two (so HCDs don't need to).
     *
     * FIXME want bus->{intr,iso}_sched_horizon values here.  Each HC
     * supports different values... this uses EHCI/UHCI defaults (and
     * EHCI can use smaller non-default values).
     */

//关于实时传输和中断传输的interval处理
    switch (xfertype) {
    case USB_ENDPOINT_XFER_ISOC:
    case USB_ENDPOINT_XFER_INT:
        /* too small? */
        //interval不能小于或等于0
        if (urb->interval 
            return -EINVAL;
        /* too big? */
        switch (dev->speed) {
        case USB_SPEED_HIGH:    /* units are microframes */
            /* NOTE usb handles 2^15 */
            if (urb->interval > (1024 * 8))
                urb->interval = 1024 * 8;
            max = 1024 * 8;
            break;
        case USB_SPEED_FULL:    /* units are frames/msec */
        case USB_SPEED_LOW:
            if (xfertype == USB_ENDPOINT_XFER_INT) {
                if (urb->interval > 255)
                    return -EINVAL;
                /* NOTE ohci only handles up to 32 */
                max = 128;
            } else {
                if (urb->interval > 1024)
                    urb->interval = 1024;
                /* NOTE usb and ohci handle up to 2^15 */
                max = 1024;
            }
            break;
        default:
            return -EINVAL;
        }
        /* Round down to a power of 2, no more than max */
        urb->interval = min(max, 1 interval));
    }

    return usb_hcd_submit_urb(urb, mem_flags);
}
这段代码虽然很长,但逻辑很清楚.对照代码中的注释理解应该是没有问题的.在这里要注意,UHCI是属于USB1.1的,它不支持HIGH传输.
对URB进行一系列处理之后,就会将urb丢给hcd进行处理了.usb_hcd_submit_urb()代码如下:
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
    int         status;
    //从usb_bus的地址取得usb_hcd的地址
    struct usb_hcd      *hcd = bus_to_hcd(urb->dev->bus);

    /* increment urb's reference count as part of giving it to the HCD
     * (which will control it).  HCD guarantees that it either returns
     * an error or calls giveback(), but not both.
     */
     //增加有关的引用计数,usbmon*系列的函数是编译选择的.忽略
    usb_get_urb(urb);
    atomic_inc(&urb->use_count);
    atomic_inc(&urb->dev->urbnum);
    usbmon_urb_submit(&hcd->self, urb);

    /* NOTE requirements on root-hub callers (usbfs and the hub
     * driver, for now):  URBs' urb->transfer_buffer must be
     * valid and usb_buffer_{sync,unmap}() not be needed, since
     * they could clobber root hub response data.  Also, control
     * URBs must be submitted in process context with interrupts
     * enabled.
     */
     //对传输的缓存区进行DMA映射
    status = map_urb_for_dma(hcd, urb, mem_flags);
    //出现错误,返回
    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        goto error;
    }

    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的设备 
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);

    if (unlikely(status)) {
        usbmon_urb_submit_error(&hcd->self, urb, status);
        unmap_urb_for_dma(hcd, urb);
error:
        urb->hcpriv = NULL;
        INIT_LIST_HEAD(&urb->urb_list);
        atomic_dec(&urb->use_count);
        atomic_dec(&urb->dev->urbnum);
        if (urb->reject)
            wake_up(&usb_kill_urb_queue);
        usb_put_urb(urb);
    }
    return status;
}
在这里函数里要注意到,urb->transfer_buffer是一个虚拟地址,用于UHCI的时候,必须要将其映射物理地址,以供设备使用.这 也就是map_urb_for_dma()要完成的工作. map_urb_for_dma()函数比较简单,这里就不做详细分析.
可能有人会有这样的疑惑,对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?
在后面的处理中我们可以看到,其实对于root hub ,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.
其次,要注意,这个函数不能用于中断上下文,因为该函数是同步的,会引起睡眠.
在这里,我们看到,流程最终转入到了下面的代码片段中:
    //如果是root hub
    if (is_root_hub(urb->dev))
        status = rh_urb_enqueue(hcd, urb);
    else
        //如果是一般的设备 
        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
下面,就分情况来剖析,各种传输到底是怎么完成的.

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>