linux usb 子系统(二)- host driver

本文深入解析Linux USB子系统的架构及工作原理,从USBSubsystem Framework到USB核心初始化过程,再到Ehci Host Driver的具体实现细节,包括S3C2410 Host Driver的Makefile配置与代码分析,帮助读者理解USB设备在Linux系统中的工作流程。
摘要由CSDN通过智能技术生成
  • 了解usb host driver.

1.USB Subsystem Framework

  The following chart shows the framework of the USB subsystem in Linux. Like i2c, the USB subsystem can be divided into three layers: ** Device Driver Layer - USB Core - Controller Driver Layer*

在这里插入图片描述

  The usb protocol is a complex protocol,The currently involved versions are usb1.0, usb2.0, usb3.0. If you open the kernel usb host directory, you will find that the following contains various forms of controller drivers such as ohci, uhci, ehci, xhci, and whci.

2.USB core 初始化

  usb_int 初始化整个usb系统的基础部分。(drivers/usb/core/usb.c)

865 static int __init usb_init(void)
866 {
867   int retval;
868   if (nousb) {
869    pr_info("%s: USB support disabled/n", usbcore_name);
870    return 0;
871   }
872
873   retval = ksuspend_usb_init();
874   if (retval)
875    goto out;
876   retval = bus_register(&usb_bus_type);
877   if (retval) 
878    goto bus_register_failed;
879   retval = usb_host_init();
880   if (retval)
881    goto host_init_failed;
882   retval = usb_major_init();
883   if (retval)
884    goto major_init_failed;
885   retval = usb_register(&usbfs_driver);
886   if (retval)
887    goto driver_register_failed;
888   retval = usb_devio_init();
889   if (retval)
890    goto usb_devio_init_failed;
891   retval = usbfs_init();
892   if (retval)
893    goto fs_init_failed;
894   retval = usb_hub_init();
895   if (retval)
896    goto hub_init_failed;
897   retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
898   if (!retval)
899    goto out;
900   ...
916 out:
917   return retval;
918 } 
  • 注册USB总线
    bus_register(&usb_bus_type);
  • 注册usbfs驱动
    usb_register(&usbfs_driver);
  • 注册usb hub驱动
    usb_hub_init -> usb_register(&hub_driver)
  • 注册通用设备驱动
    usb_register_device_driver(&usb_generic_driver, THIS_MODULE)

2.1.usb_hub_init

  usb子系统中所有和hub相关的都交由一个内核线程表征, 通过 static struct workqueue_struct *hub_wq 来管理。

  5469 static struct usb_driver hub_driver = {
  5470     .name =     "hub",
  5471     .probe =    hub_probe,
  5472     .disconnect =   hub_disconnect,
  5473     .suspend =  hub_suspend,
  5474     .resume =   hub_resume,
  5475     .reset_resume = hub_reset_resume,
  5476     .pre_reset =    hub_pre_reset,
  5477     .post_reset =   hub_post_reset,
  5478     .unlocked_ioctl = hub_ioctl,
  5479     .id_table = hub_id_table,
  5480     .supports_autosuspend = 1,
  5481 };

  5483 int usb_hub_init(void)
  5484 {
  5485     if (usb_register(&hub_driver) < 0) {
  5486         printk(KERN_ERR "%s: can't register hub driver\n",
  5487             usbcore_name);
  5488         return -1;
  5489     }
  5490 
  5491     /*
  5492      * The workqueue needs to be freezable to avoid interfering with
  5493      * USB-PERSIST port handover. Otherwise it might see that a full-speed
  5494      * device was gone before the EHCI controller had handed its port
  5495      * over to the companion full-speed controller.
  5496      */
  5497     hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
  5498     if (hub_wq)
  5499         return 0;
  5500 
  5501     /* Fall through if kernel_thread failed */
  5502     usb_deregister(&hub_driver);
  5503     pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);
  5504 
  5505     return -1;                                                                                                                                                                                              
  5506 }

  usb_hub_init()就做两件事:

  • 注册驱动–针对hub接口设备的驱动
  • 创建工作队列“usb_hub_wq”

  记住hub本省也是个usb设备, 跟普通的U盘使用的都是同样的结构体, 当有hub设备被创建时,hub驱动的probe()将会match调用, 那问题来了,一个普通设备是被hub创建的, 那hub设备是谁创建的呢?很显然最初的root hub设备必须是静态创建的, 且这部分代码没放在hub.c, 而是放到了hcd.c, 可以看出一个Host必然有一个root hub, 是绑定的!

int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
{
    int retval;
    struct usb_device *rhdev;


    /* 1. 创建一个root hub设备 */
    if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {
        dev_err(hcd->self.controller, "unable to allocate root hub\n");
        retval = -ENOMEM;
        goto err_allocate_root_hub;
    }
    /* 2. 让hcd与root hub 紧紧地绑在一起! */
    hcd->self.root_hub = rhdev;

    /* 3. 注册usb设备 */
    if ((retval = register_root_hub(hcd)) != 0)
        goto err_register_root_hub;

    return retval;

} 
EXPORT_SYMBOL_GPL(usb_add_hcd);

=========================================
/* root hub 设备默认就接在Host, 不是热拔插 */
static int register_root_hub(struct usb_hcd *hcd)
{
    struct device *parent_dev = hcd->self.controller;
    struct usb_device *usb_dev = hcd->self.root_hub;
    int retval;

    /* 4. 有效设备地址1~127, root hub默认使用地址1 */
    usb_dev->devnum = 1;
    /* 5. 直接进入地址阶段 */
    usb_set_device_state(usb_dev, USB_STATE_ADDRESS);

    /* 6. 直接设置ep0 size=64, 看来是协议规定的了 */
    usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
    /* 7. root hub 也是设备, 也要获取各种描述符 */
    retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
    retval = usb_get_bos_descriptor(usb_dev);
    /* 8. 注册设备(是注册usb_device, 不是usb_interface) */
    retval = usb_new_device (usb_dev);

    return retval;
}

3.Ehci host

流程图如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.s3c2410 host driver

4.1.Makefile

obj-$(config_usb_ohci_hcd_s3c2410) +=ohci-s3c2410.o
obj-$(config_usb_ohci_hcd) +=ohci-hcd.o

4.2.Kconfig

config usb_ohci_hcd_s3c2410
    tristate "ohci support for samsung s3c24xx/s3c64xx soc series"
    depends on usb_ohci_hcd&&(arch_s3c24xx || arch_s3c64xx)
    default y
    --- help ---
     enables support for the on-chip ohci controller on
     s3c24xx/s3c64xx chips.

config usb_ohci_hcd
  tristate "ohci hcd (usb 1.1) support"
  depends on has_dma && has_iomem

4.3.Files

  • drivers/usb/host/ohci-hcd.c
  • drivers/usb/host/ohci-s3c2410.c

代码分析:

drivers/usb/host/ohci-s3c2410.c :

  468 static int __init ohci_s3c2410_init(void)
  469 {
  470     if (usb_disabled())
  471         return -ENODEV;
  472 
  473     pr_info("%s: " DRIVER_DESC "\n", hcd_name);
  474     ohci_init_driver(&ohci_s3c2410_hc_driver, NULL);
  475 
  476     /*
  477      * The Samsung HW has some unusual quirks, which require
  478      * Sumsung-specific workarounds. We override certain hc_driver
  479      * functions here to achieve that. We explicitly do not enhance
  480      * ohci_driver_overrides to allow this more easily, since this
  481      * is an unusual case, and we don't want to encourage others to
  482      * override these functions by making it too easy.
  483      */
  484 
  485     ohci_s3c2410_hc_driver.hub_status_data  = ohci_s3c2410_hub_status_data;
  486     ohci_s3c2410_hc_driver.hub_control  = ohci_s3c2410_hub_control;
  487                                                                                                        
  488     return platform_driver_register(&ohci_hcd_s3c2410_driver);
  489 }
  490 module_init(ohci_s3c2410_init);

  ohci_hcd_s3c2410_driver作为platform_driver实例,指定当与bus上相应的platform_device匹配后需要执行的probe函数。ohci的platform_device在arch/arm/plat-samsumg/devs.c中定义:

   939 struct platform_device s3c_device_ohci = {
   940     .name       = "s3c2410-ohci",                                                                     
   941     .id     = -1,
   942     .num_resources  = ARRAY_SIZE(s3c_usb_resource),
   943     .resource   = s3c_usb_resource,
   944     .dev        = {
   945         .dma_mask       = &samsung_device_dma_mask,
   946         .coherent_dma_mask  = DMA_BIT_MASK(32),
   947     }
   948 };

  bus_match成功后,执行ohci_hcd_s3c2410_probe函数进行驱动的加载。

static int usb_hcd_s3c2410_probe(const struct hc_driver *driver,
				  struct platform_device *dev)
{
	struct usb_hcd *hcd = NULL;
	struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev);
	int retval;
 
	s3c2410_usb_set_power(info, 1, 1);
	s3c2410_usb_set_power(info, 2, 1);
 
	hcd = usb_create_hcd(driver, &dev->dev, "s3c24xx");
	if (hcd == NULL)
		return -ENOMEM;
 
	hcd->rsrc_start = dev->resource[0].start; // 指向hc 的寄存器首地址0x49000000
	hcd->rsrc_len	= resource_size(&dev->resource[0]); 
 
	hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]); // 转换为虚拟地址
	clk = devm_clk_get(&dev->dev, "usb-host");
	usb_clk = devm_clk_get(&dev->dev, "usb-bus-host");

	s3c2410_start_hc(dev, hcd);

	retval = usb_add_hcd(hcd, dev->resource[1].start, 0); //中断号26
	device_wakeup_enable(hcd->self.controller);
	return 0;
 
 err_ioremap:
	s3c2410_stop_hc(dev);
 
 err_put:
	usb_put_hcd(hcd);
	return retval;
}

4.1.usb_create_hcd

struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
  struct device *dev, char *bus_name)
{
 struct usb_hcd *hcd;
 //申请空间,hcd_priv_size为动态的附加结构长度, 附加在usb_hcd的尾部
 // 这段空间用于主机控制器的私有结构,uhci的私有结构为uhci_hcd,用于描述uhci的属性
 hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);
 if (!hcd) 
 {
  dev_dbg (dev, "hcd alloc failed\n");
  return NULL;
 }
 //连接usb_hcd到pci_device
 dev_set_drvdata(dev, hcd);
 //初始化引用计数器
 kref_init(&hcd->kref);
 //初始化usb总线
 usb_bus_init(&hcd->self);
 //连接pci_device到usb_hcd的usb_bus上
 hcd->self.controller = dev;
 //设置usb_bus名称
 hcd->self.bus_name = bus_name;
 //设置是否使用dma
 hcd->self.uses_dma = (dev->dma_mask != NULL);
 //hcd->rh_timer的注释为drives root-hub polling
 //字面意思是一个用于记时执行某函数的结构,例如5ms后执行指定函数
 init_timer(&hcd->rh_timer);
 hcd->rh_timer.function = rh_timer_func;
 hcd->rh_timer.data = (unsigned long) hcd;
#ifdef CONFIG_PM
 INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
#endif
 //连接hc_driver到usb_hcd上
 hcd->driver = driver;
 //设置设备名称
 hcd->product_desc = (driver->product_desc) ? driver->product_desc : "USB Host Controller";
 return hcd;
}
  • hcd->rh_timer.function = rh_timer_func;

  rh_timer_func这个是root_hub轮询计时器 ,控制器以轮询的方式查找端口变化状态。 rh_timer_func调用usb_hcd_poll_rh_status:

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)  
{  
     struct urb *urb;  
     int length;  
     unsigned long flags;  
     char buffer[4]; /* Any root hubs with > 31 ports? */  
     //检测主机控制器驱动是否已经注册  
     if (unlikely(!hcd->rh_registered))  
          return;  
     if (!hcd->uses_new_polling && !hcd->status_urb)  
          return;  
     //负责检测端口和td队列的状态  
     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;  
          //检测urb是否存在  
          if (urb)  
          {  
               hcd->poll_pending = 0;  
               //清除hcd的状态urb  
               hcd->status_urb = NULL;  
               //置实际传输长度为1  
               urb->actual_length = length;  
               //拷贝端口状态描述组到urb中  
               memcpy(urb->transfer_buffer, buffer, length);  
               //卸载urb与节点的连接  
               usb_hcd_unlink_urb_from_ep(hcd, urb);  
               spin_unlock(&hcd_root_hub_lock);  
              //返回urb给驱动程序  
               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 */  
     //每间隔HZ/4就执行一次hcd->rh_timer中指定的函数  
     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));  
}  

最终调用流程:

  ->usb_hcd_poll_rh_status                    //hcd.c
    ->hcd->driver->hub_status_data(hcd, buffer)
    ->usb_hcd_unlink_urb_from_ep(hcd, urb);
    ->usb_hcd_giveback_urb(hcd, urb, 0)
         ->usb_giveback_urb_bh();            //tasklet_hi_schedule(&bh->bh);
            ->__usb_hcd_giveback_urb(urb);
              ->urb->complete(urb);       //hub_irq
                ->hub_irq //hub.c  usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
                  ->kick_hub_wq(hub);
                    ->hub_event //INIT_WORK(&hub->events, hub_event);
                      ->port_event(hub, i);
                        ->hub_port_connect_change
                          ->hub_port_connect
                            ->hub_port_init
                            ->usb_new_device(udev);
                              ->usb_enumerate_device(udev);//开始枚举
                              ->device_add(&udev->dev);//枚举完毕后加载设备驱动

  device_add函数会出发总线的通知链发送通知,最终会调用总线的match方法。usb设备和驱动一旦match,则会调用驱动的drvwrap.driver.probe方法:

  • 若是设备则通过driver.c的usb_register_device_driver函数调用usb_probe_device方法
  • 若是接口则通过driver.c的usb_register_driver函数调用usb_probe_interface方法
  • 假设是U盘接入,则调用mass_storage驱动的probe,并在probe中使用usb_alloc_urb分配urb,最后usb_submit_urb提交urb。

Note: usb_hcd_poll_rh_status 其他调用流程:
usb_hcd_irq->ehci_irq->usb_hcd_poll_rh_status

4.2.usb_add_hcd 添加hcd到系统中

  • 向usb系统中注册一条总线
    usb_register_bus(&hcd->self))

  • 创建一个USB设备,作为根hub
    rhdev = usb_alloc_dev(NULL, &hcd->self, 0)
    hcd->self.root_hub = rhdev

  • usb_hcd_request_irqs:申请中断并注册中断函数usb_hub_irq

例如触发中断流程:
usb_hcd_irq->ehci_irq->usb_hcd_resume_root_hub->queue_work(pm_wq, &hcd->wakeup_work);->hcd_resume_work->usb_runtime_resume->usb_resume_both->usb_resume_device->hub_resume->hub_activate->kick_hub_wq->queue_work(hub_wq, &hub->events)->hub_events

  • 为该总线注册根hub
    register_root_hub(hcd)
    • 获取根hub的描述信息
      usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
    • 向usb系统中添加了一个usb设备
      usb_new_device (usb_dev);
      在这里插入图片描述

5.usb suspend/resume

   drivers/usb/core/usb.c:
   495 static const struct dev_pm_ops usb_device_pm_ops = {                                                                                                                                                        
   496     .prepare =  usb_dev_prepare,
   497     .complete = usb_dev_complete,
   498     .suspend =  usb_dev_suspend,
   499     .resume =   usb_dev_resume,
   500     .freeze =   usb_dev_freeze,
   501     .thaw =     usb_dev_thaw,
   502     .poweroff = usb_dev_poweroff,
   503     .restore =  usb_dev_restore,
   504     .runtime_suspend =  usb_runtime_suspend,
   505     .runtime_resume =   usb_runtime_resume,
   506     .runtime_idle =     usb_runtime_idle,
   507 };
   
   522 struct device_type usb_device_type = {
   523     .name =     "usb_device",
   524     .release =  usb_release_dev,
   525     .uevent =   usb_dev_uevent,
   526     .devnode =  usb_devnode,
   527 #ifdef CONFIG_PM
   528     .pm =       &usb_device_pm_ops,
   529 #endif
   530 };
  • suspend 流程:
    usb_dev_suspend->usb_suspend->usb_suspend_both->usb_suspend_interface->hub_suspend

refer to

  • https://www.shuzhiduo.com/A/xl56nWQo5r/
  • https://blog.csdn.net/chenliang0224/article/details/79692374
  • https://blog.csdn.net/chenliang0224/article/details/79692057
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值