linux2.6 USB设备发现过程机制 飞思卡尔imx283

drivers/usb/core/usb.c
//这个是USB子系统初始化
subsys_initcall(usb_init);

static int __init usb_init(void)
{
int retval;
retval = usb_debugfs_init();
retval = bus_register(&usb_bus_type);
retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
retval = usb_major_init();
retval = usb_register(&usbfs_driver);
retval = usb_devio_init();
retval = usbfs_init();
retval = usb_hub_init();
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
}
///
USB总线驱动
drivers/usb/core/driver.c
struct bus_type usb_bus_type = {
.name = “usb”,
.match = usb_device_match,
.uevent = usb_uevent,
.pm = &usb_bus_pm_ops,
};

具体分析

情况一:当插入USB设备时USB host会检测到这一事件;然后通过USB core去匹配驱动。

当守护程序第一次运行(特殊USB设备USB hub就是这种情况)或usb port上状态发生变化
(其余所有USB设备插入都是这种情况)守护进程被唤醒时,
会运行hub_events函数、USB的枚举过程就是由它完成。

///
从硬件层面来看,ehci主控器从PCI总线桥接,是PCI驱动程序实例。usb host做为pci总线下的一个设备存在.

drivers/usb/host/ehci-hcd.c

module_init(ehci_hcd_init);

//这个宏定义了的。飞思卡尔。
#ifdef CONFIG_USB_EHCI_ARC
#include “ehci-arc.c”
#define PLATFORM_DRIVER ehci_fsl_driver //利用定时器轮询
#endif
#ifdef CONFIG_PCI
#include “ehci-pci.c”
#define PCI_DRIVER ehci_pci_driver //利用pci中断
#endif

static int __init ehci_hcd_init(void)
{
#ifdef PLATFORM_DRIVER
retval = platform_driver_register(&PLATFORM_DRIVER);
#endif

#ifdef PCI_DRIVER
retval = pci_register_driver(&PCI_DRIVER);
#endif

}

/

定时器轮询:

kernel/drivers/usb/host/ehci-arc.c
static struct platform_driver ehci_fsl_driver = {
.probe = ehci_fsl_drv_probe,
.remove = ehci_fsl_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
#ifdef CONFIG_PM
.suspend = ehci_fsl_drv_suspend,
.resume = ehci_fsl_drv_resume,
#endif
.driver = {
.name = “fsl-ehci”,
},
};

kernel/drivers/usb/host/ehci-arc.c
static int ehci_fsl_drv_probe(struct platform_device pdev)
{
/
FIXME we only want one one probe() not two /
return usb_hcd_fsl_probe(&ehci_fsl_hc_driver, pdev);
}
kernel/drivers/usb/host/ehci-arc.c
/
*

  • usb_hcd_fsl_probe - initialize FSL-based HCDs
  • @drvier: Driver to be used for this HCD
  • @pdev: USB Host Controller being probed
  • Context: !in_interrupt()
  • Allocates basic resources for this USB host controller.

/
/
*

  • usb_hcd_fsl_probe - initialize FSL-based HCDs
  • @drvier: Driver to be used for this HCD
  • @pdev: USB Host Controller being probed
  • Context: !in_interrupt()
  • Allocates basic resources for this USB host controller.

*/
int usb_hcd_fsl_probe(const struct hc_driver *driver,
struct platform_device *pdev)
{
struct fsl_usb2_platform_data *pdata;
struct usb_hcd *hcd;
struct resource *res;
int irq;
int retval;

pr_debug("initializing FSL-SOC USB Controller\n");

/* Need platform data for setup */
pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data;

/*
 * This is a host mode driver, verify that we're supposed to be
 * in host mode.
 */
if (!((pdata->operating_mode == FSL_USB2_DR_HOST) ||
      (pdata->operating_mode == FSL_USB2_MPH_HOST) ||
      (pdata->operating_mode == FSL_USB2_DR_OTG))) {
    dev_err(&pdev->dev,
        "Non Host Mode configured for %s. Wrong driver linked.\n",
        dev_name(&pdev->dev));
    return -ENODEV;
}

hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));

res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
irq = res->start;

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hcd->rsrc_start = res->start;
hcd->rsrc_len = resource_size(res);

if (pdata->operating_mode != FSL_USB2_DR_OTG) {
    if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
                driver->description)) {
        dev_dbg(&pdev->dev, "controller already in use\n");
        retval = -EBUSY;
        goto err2;
    }
}

hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);

pdata->regs = hcd->regs;

fsl_platform_set_host_mode(hcd);
hcd->power_budget = pdata->power_budget;
/*
 * The ehci_fsl_pre_irq must be registered before usb_hcd_irq, in that case
 * it can be called before usb_hcd_irq when irq occurs
 */
retval = request_irq(irq, ehci_fsl_pre_irq, IRQF_SHARED,
        "fsl ehci pre interrupt", (void *)pdev);


retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);


if (pdata->operating_mode == FSL_USB2_DR_OTG) {
    struct ehci_hcd *ehci = hcd_to_ehci(hcd);

    dbg("pdev=0x%p  hcd=0x%p  ehci=0x%p\n", pdev, hcd, ehci);

    ehci->transceiver = otg_get_transceiver();
    dbg("ehci->transceiver=0x%p\n", ehci->transceiver);

    if (!ehci->transceiver) {
        printk(KERN_ERR "can't find transceiver\n");
    }

    retval = otg_set_host(ehci->transceiver, &ehci_to_hcd(ehci)->self);
    if (retval)
        otg_put_transceiver(ehci->transceiver);
} else if ((pdata->operating_mode == FSL_USB2_MPH_HOST) || \
        (pdata->operating_mode == FSL_USB2_DR_HOST))
    fsl_platform_set_vbus_power(pdata, 1);

if (pdata->suspended) {
    pdata->suspended = 0;
    if (pdata->already_suspended)
        pdata->already_suspended = 0;
}

fsl_platform_set_ahb_burst(hcd);
ehci_testmode_init(hcd_to_ehci(hcd));

}
//
kernel/drivers/usb/core/hcd.c
struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name){
return usb_create_shared_hcd(driver, dev, bus_name, NULL);
}
struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name, struct usb_hcd *primary_hcd){
init_timer(&hcd->rh_timer);
//定时器函数
hcd->rh_timer.function = rh_timer_func;
}

kernel/drivers/usb/core/hcd.c
//定时器轮询方式,定时器函数
static void rh_timer_func (unsigned long _hcd)
{
//重点函数,定时器轮询方式和PCI中断方式都会调用到这个函数
usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
}

//
当有pci中断发生后:
kernel/drivers/usb/host/ehci-pci.c

/* pci driver glue; this is a “new style” PCI driver module */
static struct pci_driver ehci_pci_driver = {
.name = (char *) hcd_name,
.id_table = pci_ids,

.probe =    usb_hcd_pci_probe,
.remove =    usb_hcd_pci_remove,
.shutdown =     usb_hcd_pci_shutdown,

};
kernel/drivers/usb/host/ehci-pci.c
/* PCI driver selection metadata; PCI hotplugging uses this /
static const struct pci_device_id pci_ids [] = { {
/
handle any USB 2.0 EHCI controller /
PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_EHCI, ~0),
.driver_data = (unsigned long) &ehci_pci_hc_driver,
},
{ /
end: all zeroes */ }
};
///
kernel/drivers/usb/host/ehci-pci.c
static const struct hc_driver ehci_pci_hc_driver = {
.description = hcd_name,
.product_desc = “EHCI Host Controller”,
.hcd_priv_size = sizeof(struct ehci_hcd),

/*
 * generic hardware linkage
 */
.irq =            ehci_irq,  //kernel/drivers/usb/host/xhci-pci.c 中是.irq = xhci_irq,
.flags =        HCD_MEMORY | HCD_USB2,

/*
 * basic lifecycle operations
 */
.reset =        ehci_pci_setup,
.start =        ehci_run,

.stop =            ehci_stop,
.shutdown =        ehci_shutdown,

/*
 * managing i/o requests and associated device resources
 */
.urb_enqueue =        ehci_urb_enqueue,
.urb_dequeue =        ehci_urb_dequeue,
.endpoint_disable =    ehci_endpoint_disable,
.endpoint_reset =    ehci_endpoint_reset,

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

/*
 * root hub support
 */
.hub_status_data =    ehci_hub_status_data,
.hub_control =        ehci_hub_control,
.bus_suspend =        ehci_bus_suspend,
.bus_resume =        ehci_bus_resume,
.relinquish_port =    ehci_relinquish_port,
.port_handed_over =    ehci_port_handed_over,

.clear_tt_buffer_complete    = ehci_clear_tt_buffer_complete,

};
/
kernel/drivers/usb/host/ehci-hcd.c

/-------------------------------------------------------------------------/

static irqreturn_t ehci_irq (struct usb_hcd *hcd)
{
//可以对比查看drivers/usb/host/xhci-ring.c irqreturn_t xhci_irq(struct usb_hcd *hcd)函数
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
u32 status, masked_status, pcd_status = 0, cmd;
int bh;

spin_lock (&ehci->lock);

status = ehci_readl(ehci, &ehci->regs->status);

/* e.g. cardbus physical eject */
if (status == ~(u32) 0) {
    ehci_dbg (ehci, "device removed\n");
    goto dead;
}

masked_status = status & INTR_MASK;
if (!masked_status) {        /* irq sharing? */
    spin_unlock(&ehci->lock);
    return IRQ_NONE;
}

/* clear (just) interrupts */
ehci_writel(ehci, masked_status, &ehci->regs->status);
cmd = ehci_readl(ehci, &ehci->regs->command);
bh = 0;

/* INT, ERR, and IAA interrupt rates can be throttled */

/* normal [4.15.1.2] or error [4.15.1.1] completion */
if (likely ((status & (STS_INT|STS_ERR)) != 0)) {
    if (likely ((status & STS_ERR) == 0))
        COUNT (ehci->stats.normal);
    else
        COUNT (ehci->stats.error);
    bh = 1;
}

/* complete the unlinking of some qh [4.15.2.3] */
if (status & STS_IAA) {
    /* guard against (alleged) silicon errata */
    if (cmd & CMD_IAAD) {
        ehci_writel(ehci, cmd & ~CMD_IAAD,
                &ehci->regs->command);
        ehci_dbg(ehci, "IAA with IAAD still set?\n");
    }
    if (ehci->reclaim) {
        COUNT(ehci->stats.reclaim);
        end_unlink_async(ehci);
    } else
        ehci_dbg(ehci, "IAA with nothing to reclaim?\n");
}

/* remote wakeup [4.3.1] */
if (status & STS_PCD) {
    unsigned    i = HCS_N_PORTS (ehci->hcs_params);

    /* kick root hub later */
    pcd_status = status;

    /* resume root hub? */
    if (!(cmd & CMD_RUN))
        usb_hcd_resume_root_hub(hcd);

    while (i--) {
        int pstatus = ehci_readl(ehci,
                     &ehci->regs->port_status [i]);

        if (pstatus & PORT_OWNER)
            continue;
        if (!(test_bit(i, &ehci->suspended_ports) &&
                ((pstatus & PORT_RESUME) ||
                    !(pstatus & PORT_SUSPEND)) &&
                (pstatus & PORT_PE) &&
                ehci->reset_done[i] == 0))
            continue;

        /* start 20 msec resume signaling from this port,
         * and make khubd collect PORT_STAT_C_SUSPEND to
         * stop that signaling.  Use 5 ms extra for safety,
         * like usb_port_resume() does.
         */
        ehci->reset_done[i] = jiffies + msecs_to_jiffies(25);
        ehci_dbg (ehci, "port %d remote wakeup\n", i + 1);
        mod_timer(&hcd->rh_timer, ehci->reset_done[i]);
    }
}

/* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) {
    ehci_err(ehci, "fatal error\n");
    dbg_cmd(ehci, "fatal", cmd);
    dbg_status(ehci, "fatal", status);
    ehci_halt(ehci);

dead:
ehci_reset(ehci);
ehci_writel(ehci, 0, &ehci->regs->configured_flag);
/* generic layer kills/unlinks all urbs, then
* uses ehci_stop to clean up the rest
*/
bh = 1;
}

if (bh)
    ehci_work (ehci);
spin_unlock (&ehci->lock);
if (pcd_status)
    usb_hcd_poll_rh_status(hcd);//重点函数,定时器轮询方式也会调用到这个函数
return IRQ_HANDLED;

}

kernel/drivers/usb/core/hcd.c
/*

  • Root Hub interrupt transfers are polled using a timer if the

  • driver requests it; otherwise the driver is responsible for

  • calling usb_hcd_poll_rh_status() when an event occurs.

  • Completions are called in_interrupt(), but they may or may not

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

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

    length = hcd->driver->hub_status_data(hcd, buffer);
    if (length > 0) {

     /* try to complete the status urb */
     spin_lock_irqsave(&hcd_root_hub_lock, flags);
     urb = hcd->status_urb;
     if (urb) {
         hcd->poll_pending = 0;
         hcd->status_urb = NULL;
         urb->actual_length = length;
         memcpy(urb->transfer_buffer, buffer, length);
    
         usb_hcd_unlink_urb_from_ep(hcd, urb);
         spin_unlock(&hcd_root_hub_lock);
           //重点函数,定时器轮询方式和PCI中断方式都会调用到这个函数
         usb_hcd_giveback_urb(hcd, urb, 0);
         spin_lock(&hcd_root_hub_lock);
     } else {
         length = 0;
         hcd->poll_pending = 1;
     }
     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 inbetween */
      if (hcd->uses_new_polling ? hcd->poll_rh :
      (length == 0 && hcd->status_urb != NULL))
      mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
      }
      =====================================================================

从以上分析可以看出;不论是定时器轮询还是pci中断,最终都会执行usb_hcd_giveback_urb函数:

kernel/drivers/usb/core/hcd.c
/**

  • usb_hcd_giveback_urb - return URB from HCD to device driver

  • @hcd: host controller returning the URB

  • @urb: urb being returned to the USB device driver.

  • @status: completion status code for the URB.

  • Context: in_interrupt()

  • This hands the URB from HCD to its USB device driver, using its

  • completion function. The HCD has freed all per-urb resources

  • (and is done using urb->hcpriv). It also released all HCD locks;

  • the device driver won’t cause problems if it frees, modifies,

  • or resubmits this URB.

  • If @urb was unlinked, the value of @status will be overridden by

  • @urb->unlinked. Erroneous short transfers are detected in case

  • the HCD hasn’t checked for them.
    */
    void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status)
    {
    urb->hcpriv = NULL;
    if (unlikely(urb->unlinked))
    status = urb->unlinked;
    else if (unlikely((urb->transfer_flags & URB_SHORT_NOT_OK) &&
    urb->actual_length < urb->transfer_buffer_length &&
    !status))
    status = -EREMOTEIO;

    unmap_urb_for_dma(hcd, urb);
    usbmon_urb_complete(&hcd->self, urb, status);
    usb_unanchor_urb(urb);

    /* pass ownership to the completion handler */
    urb->status = status;
    urb->complete (urb);//hub.c static void hub_irq(struct urb *urb)
    atomic_dec (&urb->use_count);
    if (unlikely(atomic_read(&urb->reject)))
    wake_up (&usb_kill_urb_queue);
    usb_put_urb (urb);
    }
    //
    而上处urb->complete函数其实就是如下的hub_irq函数,后边会分析:

kernel/drivers/usb/core/hub.c
/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
struct usb_hub *hub = urb->context;
int status = urb->status;
unsigned i;
unsigned long bits;

switch (status) {
case -ENOENT:        /* synchronous unlink */
case -ECONNRESET:    /* async unlink */
case -ESHUTDOWN:    /* hardware going away */
    return;

default:        /* presumably an error */
    /* Cause a hub reset after 10 consecutive errors */
    dev_dbg (hub->intfdev, "transfer --> %d\n", status);
    if ((++hub->nerrors < 10) || hub->error)
        goto resubmit;
    hub->error = status;
    /* FALL THROUGH */

/* let khubd handle things */
case 0:            /* we got data:  port status changed */
    bits = 0;
    for (i = 0; i < urb->actual_length; ++i)
        bits |= ((unsigned long) ((*hub->buffer)[i]))
                << (i*8);
    hub->event_bits[0] = bits;
    break;
}

hub->nerrors = 0;

/* Something happened, let khubd figure it out */

//此处唤醒
kick_khubd(hub);

}
/
kernel/drivers/usb/core/hub.c
static void kick_khubd(struct usb_hub *hub)
{
unsigned long flags;

spin_lock_irqsave(&hub_event_lock, flags);
if (!hub->disconnected && list_empty(&hub->event_list)) {
    list_add_tail(&hub->event_list, &hub_event_list);

    /* Suppress autosuspend until khubd runs */
    usb_autopm_get_interface_no_resume(
            to_usb_interface(hub->intfdev));

//唤醒khubd_wait
wake_up(&khubd_wait);
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}

kernel/drivers/usb/core/hub.c

这里特别强调:hub设备是第一个USB设备,
也是必须的USB设备;它不需要通过USB总线定时器轮询或PCI总线中断来触发。
从下边代码也可以看出,在执行第一次hub_events之后(hub驱动的probe函数被执行、urv->complete被赋值hub_irq),
该线程才会睡眠!
int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) {
}

khubd_task = kthread_run(hub_thread, NULL, "khubd");

}
static int hub_thread(void *__unused)
{
do {
hub_events(); //重要!最核心部分
//等待kick_khubd函数唤醒khubd_wait
wait_event_freezable(khubd_wait,!list_empty(&hub_event_list) || kthread_should_stop());
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
}
//内核守护线程khubd,它被kick_khubd唤醒(当prot上状态发生变化时,USB host会调用usb_hcd_poll_rh_status去查询usb root hub port状态,并调用hub中的interrupt urb的回调函数hub_irq,最终去唤醒usb内核守护线程)、通过自身调用wait_event_freezable进入睡眠。
static void hub_events(void)
{
if (connect_change) hub_port_connect_change(hub, i, portstatus, portchange);
}

hub_port_connect_change,顾名思义,当hub端口上有连接变化时调用这个函数,这种变化既可以是物理变化也可以是逻辑变化.注释里说得也很清楚.有三种情况会调用这个函数,一个是连接有变化,一个是端口本身重新使能,即所谓的enable,这种情况通常就是为了对付电磁干扰的,正如我们前面的判断中所说的那样,第三种情况就是在复位一个设备的时候发现其描述符变了,这通常对应的是硬件本身有了升级.很显然,第一种情况是真正的物理变化,后两者就算是逻辑变化
kernel/drivers/usb/core/hub.c
static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange){
status = hub_port_init(hub, udev, port1, i);
status = usb_new_device(udev);
}
int usb_new_device(struct usb_device udev){
err = device_add(&udev->dev);
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
/

kernel/drivers/usb/core/endpoint.c
int usb_create_ep_devs(struct device *parent,struct usb_host_endpoint *endpoint,struct usb_device *udev){
device_register(&ep_dev->dev);
}
*/
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xx-xx-xxx-xxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值