绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
LINUX下USB1.1设备学习小记(4)_uhci(2)
2020-06-03 11:24:36
好~ 现在万事俱备,只欠uhci硬件的注册了
现在谈一下uhci硬件的组成,uhci的硬件分为两个大的部分,主机控制器和根集线器,
 
当提交uhci硬件的注册到pci总线后,经过一轮匹配,终于找到了uhci,进入到uhci_pci_driver->probe这个函数下
 
usb_hcd_pci_probe在/drivers/usb/host/uhci-hcd.c

int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
 struct hc_driver *driver;
 struct usb_hcd *hcd;
 int retval;
 //判断内核参数是否启用USB
 if (usb_disabled())
  return -ENODEV;
 //判断匹配表是否存在,该表为uhci_pci_driver->id_table中的内容
 if (!id)
  return -EINVAL;
 //取得匹配表中的私有结构,为uhci_driver
 driver = (struct hc_driver *)id->driver_data;
 //私有结构不存在则返回错误
 if (!driver)
  return -EINVAL;
 //使能pci设备
 if (pci_enable_device(dev) < 0)
  return -ENODEV;
 //设置电源为正常状态
 dev->current_state = PCI_D0;
 //检测是否有中断号
 if (!dev->irq) 
 {
  dev_err(&dev->dev,"Found HC with no IRQ. Check BIOS/PCI %s setup!\n",pci_name(dev));
  retval = -ENODEV;
  goto err1;
 }
 //创建usb_hcd结构
 hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));
 //检测创建是否成功
 if (!hcd) 
 {
  retval = -ENOMEM;
  goto err1;
 }
 //检测是否需要映射I/O内存空间,EHCI和OHCI这两个主机控制器有自己的内存空间,访问的时候需要映射到计算机的内存空间中才能对设备进行操作,而UHCI没有自己的内存空间,对UHCI的操作是对IO端口的访问,所以UHCI需要分配IO端口空间
 if (driver->flags & HCD_MEMORY) 
 {
  /* EHCI, OHCI */
  //这两个设备需要映射I/O内存空间
  hcd->rsrc_start = pci_resource_start(dev, 0);
  hcd->rsrc_len = pci_resource_len(dev, 0);
  if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,driver->description)) 
  {
   dev_dbg(&dev->dev, "controller already in use\n");
   retval = -EBUSY;
   goto err2;
  }
  hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);
  if (hcd->regs == NULL) 
  {
   dev_dbg(&dev->dev, "error mapping memory\n");
   retval = -EFAULT;
   goto err3;
  }
 } 
 else 
 {
  /* UHCI */
  //UHCI需要映射I/O端口
  int region;
  for (region = 0; region < PCI_ROM_RESOURCE; region++) 
  {
   //获取PCI I/O区域region号资源标记并判断是否为IO接口资源
   if (!(pci_resource_flags(dev, region) & IORESOURCE_IO))
    continue;
   //获取PCI I/O区域refion的首地址
   hcd->rsrc_start = pci_resource_start(dev, region);
   //计算资源长度
   hcd->rsrc_len = pci_resource_len(dev, region);
   //申请映射I/O端口
   if (request_region(hcd->rsrc_start, hcd->rsrc_len,driver->description))
    break;
  }
  //没有IO接口资源则返回出错
  if (region == PCI_ROM_RESOURCE) 
  {
   dev_dbg(&dev->dev, "no i/o regions available\n");
   retval = -EBUSY;
   goto err1;
  }
 }
 //开始竞争总线
 pci_set_master(dev);
 //PCI层初始化完毕,进入Host Controller的初始化
 retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
 if (retval != 0)
  goto err4;
 return retval;
}

现在看看usb_create_hcd,这个函数创建每个主机控制器必备的usb_hcd结构

usb_create_hcd在/drivers/usb/core/hcd.c
 

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;
}

分配并初始化好的usb_hcd结构如下,其bus_name和uses_dma因为我还没看pci,所以这两个值不清楚,不过在uhcidma是肯定会用到的
500)this.width=500;" border="0" style="overflow-wrap: break-word; border-width: 0px; border-style: initial; max-width: ;">
 
为什么会有个白框呢?还记得hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);
这句么,这个白框就是所申请的hcd_priv_size大小的空间,里面是什么我们还不知道,所以是一片白的,等用到的时候我再把它画出来
hcd准备好之后,就要进入更深一个层次的初始化了, usb_add_hcd负责这部分的工作
 
usb_add_hcd在/drivers/usb/core/hcd.c
 

int usb_add_hcd(struct usb_hcd *hcd,
  unsigned int irqnum, unsigned long irqflags)
{
 int retval;
 struct usb_device *rhdev;
 dev_info(hcd->self.controller, "%s\n", hcd->product_desc);
 //设置默认的批准标志,为无线设备设0,否则设1
 hcd->authorized_default = hcd->wireless? 0 : 1;
 //设置主机控制器的状态为硬件可用
 set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 /* HC is in reset state, but accessible. Now do the one-time init,
  * bottom up so that hcds can customize the root hubs before khubd
  * starts talking to them. (Note, bus id is assigned early too.)
  */

  //初始化缓冲池
 if ((retval = hcd_buffer_create(hcd)) != 0)
 {
  dev_dbg(hcd->self.controller, "pool alloc failed\n");
  return retval;
 }
 //注册USB host controller
 if ((retval = usb_register_bus(&hcd->self)) < 0)
  goto err_register_bus;
 //分配一个设备描述符用于根设备
 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;
 }
 //设置高速度
 rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :USB_SPEED_FULL;
 //连接根集线设备到usb_bus上
 hcd->self.root_hub = rhdev;
 /* wakeup flag init defaults to "everything works" for root hubs,
  * but drivers can override it in reset() if needed, along with
  * recording the overall controller's system wakeup capability.
  */

  //打开ROOT HUB的WAKEUP
 device_init_wakeup(&rhdev->dev, 1);
 /* "reset" is misnamed; its role is now one-time init. the controller
  * should already have been reset (and boot firmware kicked off etc).
  */

  //复位设备
 if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) 
 {
  dev_err(hcd->self.controller, "can't setup\n");
  goto err_hcd_driver_setup;
 }
 /* NOTE: root hub and controller capabilities may not be the same */
 if (device_can_wakeup(hcd->self.controller) && device_can_wakeup(&hcd->self.root_hub->dev))
  dev_dbg(hcd->self.controller, "supports USB remote wakeup\n");
 /* enable irqs just before we start the controller */
 //判断是否有中断处理函数
 if (hcd->driver->irq) 
 {
  /* IRQF_DISABLED doesn't work as advertised when used together
   * with IRQF_SHARED. As usb_hcd_irq() will always disable
   * interrupts we can remove it here.
   */

  irqflags &= ~IRQF_DISABLED;
  //设置中断描述串
  snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
    hcd->driver->description, hcd->self.busnum);
  //安装中断处理函数
  if ((retval = request_irq(irqnum, &usb_hcd_irq, irqflags,hcd->irq_descr, hcd))!= 0) 
  {
   dev_err(hcd->self.controller,"request interrupt %d failed\n", irqnum);
   goto err_request_irq;
  }
  
  hcd->irq = irqnum;
  dev_info(hcd->self.controller, "irq %d, %s 0x%08llx\n", irqnum,
    (hcd->driver->flags & HCD_MEMORY) ?"io mem" : "io base",
     (unsigned long long)hcd->rsrc_start);
 } 
 else 
 {
  hcd->irq = -1;
  if (hcd->rsrc_start)
   dev_info(hcd->self.controller, "%s 0x%08llx\n",
     (hcd->driver->flags & HCD_MEMORY) ?
     "io mem" : "io base",
     (unsigned long long)hcd->rsrc_start);
 }
 //启动设备
 if ((retval = hcd->driver->start(hcd)) < 0) 
 {
  dev_err(hcd->self.controller, "startup error %d\n", retval);
  goto err_hcd_driver_start;
 }
 /* starting here, usbcore will pay attention to this root hub */
 rhdev->bus_mA = min(500u, hcd->power_budget);
 //注册根集线器
 if ((retval = register_root_hub(hcd)) != 0)
  goto err_register_root_hub;
 //添加设备属性文件
 retval = sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group);
 if (retval < 0) 
 {
  printk(KERN_ERR "Cannot register USB bus sysfs attributes: %d\n",
         retval);
  goto error_create_attr_group;
 }
 if (hcd->uses_new_polling && hcd->poll_rh)
  //查询设备状态
  usb_hcd_poll_rh_status(hcd);
 return retval;
}

首先是hcd_buffer_create,这个函数用于初始化主机控制器的dma内存池,为以后的dma内存分配做准备
 
hcd_buffer_create在/drivers/usb/core/buffer.c
 

int hcd_buffer_create(struct usb_hcd *hcd)
{
 char name[16];
 int i, size;
 if (!hcd->self.controller->dma_mask && !(hcd->driver->flags & HCD_LOCAL_MEM))
  return 0;
 //创建4个不同大小的缓冲池,为不同大小的dma请求分配合适的dma内存
 for (= 0; i < HCD_BUFFER_POOLS; i++) 
 {
  size = pool_max[i];
  if (!size)
   continue;
  snprintf(name, sizeof name, "buffer-%d", size);
  //创建DMA内存池,此时还没有真正分配空间
  hcd->pool[i] = dma_pool_create(name, hcd->self.controller,size, size, 0);
  if (!hcd->pool [i]) 
  {
   hcd_buffer_destroy(hcd);
   return -ENOMEM;
  }
 }
 return 0;
}

usb_register_bus用于注册uhci自己的总线,管理连接到uhci的所有设备

static int usb_register_bus(struct usb_bus *bus)
{
 int result = -E2BIG;
 int busnum;
 mutex_lock(&usb_bus_list_lock);
 //在usbmap位图中寻找未占用的位
 busnum = find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1);
 //大于限制则返回错误
 if (busnum >= USB_MAXBUS) 
 {
  printk (KERN_ERR "%s: too many buses\n", usbcore_name);
  goto error_find_busnum;
 }
 //置相应位为已经使用
 set_bit (busnum, busmap.busmap);
 //设置uhci总线的地址号
 bus->busnum = busnum;
 //创建一个usb_host%目录,用于表达该设备为主机控制器
 bus->dev = device_create_drvdata(usb_host_class, bus->controller,
      MKDEV(, 0), bus,"usb_host%d", busnum);
 result = PTR_ERR(bus->dev);
 if (IS_ERR(bus->dev))
  goto error_create_class_dev;
 /* Add it to the local list of buses */
 //添加到usb_bus_list的链表中
 list_add (&bus->bus_list, &usb_bus_list);
 mutex_unlock(&usb_bus_list_lock);
 //新型的总线通知机制,我还没研究,放着等我弄懂先吧
 usb_notify_add_bus(bus);
 dev_info (bus->controller, "new USB bus registered, assigned bus "
    "number %d\n", bus->busnum);
 return 0;
}

这个函数执行完后的结构图如下
 
500)this.width=500;" border="0" style="overflow-wrap: break-word; border-width: 0px; border-style: initial; max-width: ;">
 
 
然后到usb_alloc_dev,这个函数负责分配一个usb设备数据结构,在这里,这个usb数据结构用于描述主机控制器的根集线器,这个根集线器也是usb设备的一种
 
usb_alloc_dev在/drivers/usb/core/usb.c

struct usb_device *usb_alloc_dev(struct usb_device *parent,
     struct usb_bus *bus, unsigned port1)
{
 struct usb_device *dev;
 //取得设备所连接的主机控制器结构
 struct usb_hcd *usb_hcd = container_of(bus, struct usb_hcd, self);
 unsigned root_hub = 0;
 //分配usb_device结构
 dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 if (!dev)
  return NULL;
 //增加主机控制器的设备计数器
 if (!usb_get_hcd(bus_to_hcd(bus))) 
 {
  kfree(dev);
  return NULL;
 }
 //初始化设备
 device_initialize(&dev->dev);
 //设置所属总线类型,这里是重点,为什么匹配的时候不是匹配pci总线或者其它总线,因为在这里设置了这个设备属于usb总线
 dev->dev.bus = &usb_bus_type;
 //设置设备自身类型,这里也是重点,这里设置了设备的类型,是设备还是接口,对于usb驱动来说分两类,设备驱动和接口驱动, usb_generic_driver为usb设备驱动,hub为usb接口驱动
 dev->dev.type = &usb_device_type;
 //设置属性组,也就是目录下的描述文件
 dev->dev.groups = usb_device_groups;
 //设置是否使用dma
 dev->dev.dma_mask = bus->controller->dma_mask;
 set_dev_node(&dev->dev, dev_to_node(bus->controller));
 //设置设备状态为连接
 dev->state = USB_STATE_ATTACHED;
 atomic_set(&dev->urbnum, 0)
 INIT_LIST_HEAD(&dev->ep0.urb_list);
 //初始化端点0
 dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;
 dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT;
 /* ep0 maxpacket comes later, from device descriptor */
 usb_enable_endpoint(dev, &dev->ep0);
 //设置URB发送允许位
 dev->can_submit = 1;
 /* Save readable and stable topology id, distinguishing devices
  * by location for diagnostics, tools, driver model, etc. The
  * string is a path along hub ports, from the root. Each device's
  * dev->devpath will be stable until USB is re-cabled, and hubs
  * are often labeled with these port numbers. The bus_id isn't
  * as stable: bus->busnum changes easily from modprobe order,
  * cardbus or pci hotplugging, and so on.
  */

  //检测是否为根集线器,分配不同的名字
 if (unlikely(!parent)) 
 {
  //设置设备的层次为0
  dev->devpath[] = '0';
  //设置父设备,这是根集线器,没有父设备,所以为NULL
  dev->dev.parent = bus->controller;
  sprintf(&dev->dev.bus_id[], "usb%d", bus->busnum);
  root_hub = 1;
 } 
 else 
 {
  /* match any labeling on the hubs; it's one-based */
  if (parent->devpath[] == '0')
   snprintf(dev->devpath, sizeof dev->devpath,"%d", port1);
  else
   snprintf(dev->devpath, sizeof dev->devpath,"%s.%d", parent->devpath, port1);
  dev->dev.parent = &parent->dev;
  sprintf(&dev->dev.bus_id[], "%d-%s",bus->busnum, dev->devpath);
  /* hub driver sets up TT records */
 }
 //设置usb设备的节点号
 dev->portnum = port1;
 //设置usb设备的usb总线
 dev->bus = bus;
 //设置上层hub
 dev->parent = parent;
 INIT_LIST_HEAD(&dev->filelist);
//这部分为电源管理机制,我们不关心这部分内容
#ifdef CONFIG_PM
 mutex_init(&dev->pm_mutex);
 INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work);
 dev->autosuspend_delay = usb_autosuspend_delay * HZ;
 dev->connect_time = jiffies;
 dev->active_duration = -jiffies;
#endif
 //检测是否为根集线器,这里为根集线器
 if (root_hub) /* Root hub always ok [and always wired] */
  //设置设备的批准标志为1
  dev->authorized = 1;
 else 
 {
  dev->authorized = usb_hcd->authorized_default;
  dev->wusb = usb_bus_is_wusb(bus)? 1 : 0;
 }
 
 return dev;
}



 
现在到hcd->driver->reset,这个函数为uhci_init
 
uhci_init在/drivers/usb/host/uhci-hcd.c
 

static int uhci_init(struct usb_hcd *hcd)
{
 //这里解开白框的秘密了,原来是一个uhci_hcd结构,如果大家仔细的话,其实这个结构很早就出现了,在uhci_driver中,hcd_priv_size = sizeof(struct uhci_hcd), hcd_priv_size的值正是uhci_hcd的大小
 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 int port;
 //取得IO端口资源区长度
 unsigned io_size = (unsigned) hcd->rsrc_len;
 //取得IO端口资源区起始地址
 uhci->io_addr = (unsigned long) hcd->rsrc_start;
 /* The UHCI spec says devices must have 2 ports, and goes on to say
  * they may have more but gives no way to determine how many there
  * are. However according to the UHCI spec, Bit 7 of the port
  * status and control register is always set to 1. So we try to
  * use this to our advantage. Another common failure mode when
  * a nonexistent register is addressed is to return all ones, so
  * we test for that also.
  */

  //判断根集线器有几个端口
 for (port = 0; port < (io_size - USBPORTSC1) / 2; port++) 
 {
  unsigned int portstatus;
  //inw - 读IO端口
  portstatus = inw(uhci->io_addr + USBPORTSC1 + (port * 2));
  //第7位不为1或者全为1则说明端口寄存器,判断完毕
  if (!(portstatus & 0x0080) || portstatus == 0xffff)
   break;
 }
 if (debug)
  dev_info(uhci_dev(uhci), "detected %d ports\n", port);
 /* Anything greater than 7 is weird so we'll ignore it. */
 //多于7个端口则强制设置为2个
 if (port > UHCI_RH_MAXCHILD) 
 {
  dev_info(uhci_dev(uhci), "port count misdetected? ""forcing to 2 ports\n");
  port = 2;
 }
 //记录端口数
 uhci->rh_numports = port;
 /* Kick BIOS off this hardware and reset if the controller
  * isn't already safely quiescent.
  */

  //判断UHCI是否需要重置
 check_and_reset_hc(uhci);
 return 0;
}


文章来源CU社区:LINUX下USB1.1设备学习小记(4)_uhci(2)

分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~