Linux之 USB驱动框架-usb_submit_urb和usb_fill_*_urb分析(6)

本文详细解析了USB设备驱动中urb(UnsynchronizedBulkDataTransfer)的填充过程,特别是针对中断urb的填充,以及urb_complete函数在中断处理中的作用。文章还探讨了urb如何与USBHostController的调度、定时器和中断处理关联,包括urb_enqueue函数和tasklet在驱动中的角色。
摘要由CSDN通过智能技术生成

一、usb_fill_*_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)
// 中断
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)
// 批量

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)
    
// 实时   
// 实时urb 没有和中断、控制、批量urb 类似的初始化函数,因此它们在提交到USB核心之前,需要在驱动程序中手动的初始化    
    
 

 拿其中的 usb_fill_int_urb 具体分析

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)
{
        urb->dev = dev;
        urb->pipe = pipe;
        urb->transfer_buffer = transfer_buffer;
        urb->transfer_buffer_length = buffer_length;
        urb->complete = complete_fn;
        urb->context = context;

        if (dev->speed == USB_SPEED_HIGH || dev->speed >= USB_SPEED_SUPER) {
                /* make sure interval is within allowed range */
                interval = clamp(interval, 1, 16);

                urb->interval = 1 << (interval - 1);
        } else {
                urb->interval = interval;
        }

        urb->start_frame = -1;
}
 

 上面含义就是填充urb 成员,其中的complete_fn 函数,是urb 执行完毕后,usb core 执行的回调函数。

完成函数的作用:

比如:调用usb_fill_int_urb(),其中complete_fn相当于中断函数,即主机读取完数据会放到指定的内存data_dma中。然后通知CPU处理这些数据,这些数据只有USB设备驱动程序知道其含义。所以要在这个完成函数中处理这些数据,如上报。

1、urb的complete 执行流程

以usb鼠标驱动为例,具体分析如下:

 usb_mouse_probe->

                usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
                         (maxp > 8 ? 8 : maxp),
                         usb_mouse_irq, mouse, endpoint->bInterval); //填充 urb

usb_mouse_irq 为 urb 的complete 函数。

那么urb->complete 执行流程:

usb_mouse_irq  <- __usb_hcd_giveback_urb <-  usb_giveback_urb_bh

usb_giveback_urb_bh 这个函数是怎么初始化并触发执行?

 在增加usb 控制器驱动中,会调用 usb_add_hcd 函数 ,接着,在usb_add_hcd 函数中,会初始化两个软中断:

/* initialize tasklets */
        init_giveback_urb_bh(&hcd->high_prio_bh);
        init_giveback_urb_bh(&hcd->low_prio_bh);
 

 接着看 init_giveback_urb_bh :

static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
{

        spin_lock_init(&bh->lock);
        INIT_LIST_HEAD(&bh->head);
        tasklet_setup(&bh->bh, usb_giveback_urb_bh);
}


从上面可知,init_giveback_urb_bh 是初始化tasklet 软中断函数。

        

 如何调度到这个 tasklet 函数:usb_giveback_urb_bh  ???

答案:在主控驱动中,注册hcd 时,会调用usb_create_hcd:

ohci_hcd_s3c2410_probe -> 

        usb_create_hcd->

                __usb_create_hcd(driver, dev, dev, bus_name, NULL);->

                                timer_setup(&hcd->rh_timer, rh_timer_func, 0);

 这里创建了 timer 定时,初始化定时函数为 rh_timer_func。

rh_timer_func

/* timer callback */
static void rh_timer_func (struct timer_list *t)
{
        struct usb_hcd *_hcd = from_timer(_hcd, t, rh_timer);

        usb_hcd_poll_rh_status(_hcd);
}
 

 usb_hcd_poll_rh_status

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
        struct urb      *urb;
        int             length;
        int             status;
        unsigned long   flags;
        char            buffer[6];      /* Any root hubs with > 31 ports? */

        if (unlikely(!hcd->rh_pollable))
                return;
        if (!hcd->uses_new_polling && !hcd->status_urb)
                return;

        length = hcd->driver->hub_status_data(hcd, buffer);  //检测hub是否有数据到来 
        if (length > 0) {

                /* try to complete the status urb */
                spin_lock_irqsave(&hcd_root_hub_lock, flags);
                urb = hcd->status_urb;
                if (urb) {
                        clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                        hcd->status_urb = NULL;
                        if (urb->transfer_buffer_length >= length) {
                                status = 0;
                        } else {
                                status = -EOVERFLOW;
                                length = urb->transfer_buffer_length;
                        }
                        urb->actual_length = length;
                        memcpy(urb->transfer_buffer, buffer, length);

                        usb_hcd_unlink_urb_from_ep(hcd, urb);
                        usb_hcd_giveback_urb(hcd, urb, status);
                } else {
                        length = 0;
                        set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
                }
                spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
        }

        /* The USB 2.0 spec says 256 ms.  This is close enough and won't
         * exceed that limit if HZ is 100. The math is more clunky than
         * maybe expected, this is to make sure that all timers for USB devices
         * fire at the same time to give the CPU a break in between */
        if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) :
                        (length == 0 && hcd->status_urb != NULL))
                mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}
EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status);
 

 usb_hcd_giveback_urb

void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
{
        struct giveback_urb_bh *bh;
        bool running, high_prio_bh;
        dump_stack();
        /* pass status to tasklet via unlinked */
        if (likely(!urb->unlinked))
                urb->unlinked = status;

        if (!hcd_giveback_urb_in_bh(hcd) && !is_root_hub(urb->dev)) {
                __usb_hcd_giveback_urb(urb);
                return;
        }

        if (usb_pipeisoc(urb->pipe) || usb_pipeint(urb->pipe)) {
                bh = &hcd->high_prio_bh;
                high_prio_bh = true;
        } else {
                bh = &hcd->low_prio_bh;
                high_prio_bh = false;
        }

        spin_lock(&bh->lock);
        list_add_tail(&urb->urb_list, &bh->head);
        running = bh->running;
        spin_unlock(&bh->lock);

        if (running)
                ;
        else if (high_prio_bh)
                tasklet_hi_schedule(&bh->bh);
        else
                tasklet_schedule(&bh->bh);
}
EXPORT_SYMBOL_GPL(usb_hcd_giveback_urb);

上面的tasklet 函数会调用到 tasklet_hi_schedule(&bh->bh);或者 tasklet_schedule(&bh->bh);

这样就会触发usb_giveback_urb_bh 。

初始化后这个定时函数后,什么时候使用mod_timer 触发呢?

答案:

ohci_hcd_s3c2410_probe -> 

        usb_add_hcd ->

                register_root_hub->

                        usb_get_device_descriptor->

                                          usb_get_descriptor->

                                                        usb_control_msg->

                                                                       usb_internal_control_msg->

                                                                                        usb_fill_control_urb->

                                                                                                                usb_submit_urb->

                

 从下面的usb_submit_urb 分析可知,这是主控驱动注册root_hub,那么调用rh_urb_enqueue

usb_submit_urb->

               usb_hcd_submit_urb->

                                rh_urb_enqueue->

                                                rh_queue_status->

                                                                  mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));

                        

 上面可知,主控会注册一个timer 定时函数,进行检测hub 的端口是否有数据到来,然后处理数据,处理之后,调用处理完成函数。和之前分析的  hub_wq 工作队列检测是否有usb设备连接和断开 是不一样的。工作函数hub_event会被循环执行检测是否有设备连接和断开。

 二、usb_submit_urb

usb_submit_urb 调用流程:

usb_submit_urb->

        usb_hcd_submit_urb

 usb_hcd_submit_urb 函数:

int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
        int                     status;
        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.
         */
        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.
         */

        if (is_root_hub(urb->dev)) {          -------------------------------------(1)
                status = rh_urb_enqueue(hcd, urb);
        } else {
                status = map_urb_for_dma(hcd, urb, mem_flags);
                if (likely(status == 0)) {
                        status = hcd->driver->urb_enqueue(hcd, urb, mem_flags); -----------(2)
                        if (unlikely(status))
                                unmap_urb_for_dma(hcd, urb);
                }
        }

        if (unlikely(status)) {
                usbmon_urb_submit_error(&hcd->self, urb, status);
                urb->hcpriv = NULL;
                INIT_LIST_HEAD(&urb->urb_list);
                atomic_dec(&urb->use_count);
                /*
                 * Order the write of urb->use_count above before the read
                 * of urb->reject below.  Pairs with the memory barriers in
                 * usb_kill_urb() and usb_poison_urb().
                 */
                smp_mb__after_atomic();

                atomic_dec(&urb->dev->urbnum);
                if (atomic_read(&urb->reject))
                        wake_up(&usb_kill_urb_queue);
                usb_put_urb(urb);
        }
        return status;
}
 

 (1)如果是 root_hub,则调用 rh_urb_enqueue 函数,

static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
        if (usb_endpoint_xfer_int(&urb->ep->desc))   //中断传输
                return rh_queue_status (hcd, urb);
        if (usb_endpoint_xfer_control(&urb->ep->desc))  //控制传输
                return rh_call_control (hcd, urb);
        return -EINVAL;
}
共有两个事务。

 (2)如果非root_hub ,则调用回调函数urb_enqueue

拿ohci 举例:

static const struct hc_driver ohci_hc_driver = {
        .description =          hcd_name,
        .product_desc =         "OHCI Host Controller",
        .hcd_priv_size =        sizeof(struct ohci_hcd),

        /*
         * generic hardware linkage
        */
        .irq =                  ohci_irq,
        .flags =                HCD_MEMORY | HCD_DMA | HCD_USB11,

        /*
        * basic lifecycle operations
        */
        .reset =                ohci_setup,
        .start =                ohci_start,
        .stop =                 ohci_stop,
        .shutdown =             ohci_shutdown,

        /*
         * managing i/o requests and associated device resources
        */
        .urb_enqueue =          ohci_urb_enqueue,
        .urb_dequeue =          ohci_urb_dequeue,
        .endpoint_disable =     ohci_endpoint_disable,

        /*
        * scheduling support
        */
        .get_frame_number =     ohci_get_frame,

        /*
        * root hub support
        */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
#ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
#endif
        .start_port_reset =     ohci_start_port_reset,
};
 

  .urb_enqueue =          ohci_urb_enqueue,调用ohci_urb_enqueue 函数,

ohci_urb_enqueue ->td_submit_urb->ohci_writel

留一个问题:ohci 执行完urb 后,如何触发 compelte 函数?

root hub 有一个定时timer函数周期性检测,然后触发 tasklet 函数,调用urb->compelete 函数。

  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值