USB uhci主机控制器初始化分析

本文详细阐述了USB控制器在PCI设备驱动中初始化的过程,包括驱动匹配、设备创建、内存请求与映射、总线主能力设定、核心层主机控制器添加、根集线器处理、速度设置、复位操作及中断初始化等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PCI检测到USB主机控制器后会进行驱动的匹配
usb_hcd_pci_probe函数进行设备与驱动的匹配,并执行初始化动作
创建USB_HCD结构
 hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));
 if (!hcd) {
  retval = -ENOMEM;
  goto disable_pci;
 }
 if (driver->flags & HCD_MEMORY) {
  /* EHCI, OHCI */
  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 clear_companion;
  }
  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 release_mem_region;
  }

 } else {
  /* UHCI */
UHCI的IO资源是在IO地址空间中的
  int region;

  for (region = 0; region < PCI_ROM_RESOURCE; region++) {
   if (!(pci_resource_flags(dev, region) &
     IORESOURCE_IO))
    continue;

   hcd->rsrc_start = pci_resource_start(dev, region);
   hcd->rsrc_len = pci_resource_len(dev, region);
   if (request_region(hcd->rsrc_start, hcd->rsrc_len,
     driver->description))
    break;
  }
  if (region == PCI_ROM_RESOURCE) {
   dev_dbg(&dev->dev, "no i/o regions available\n");
   retval = -EBUSY;
   goto clear_companion;
  }
 }
使主机控制器有成为总线主的能力,否则无法使用DMA功能
 pci_set_master(dev);
向core层添加主机控制器 
 retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
 
usb_add_hcd函数:
初始化hcd->pool,按照全局数组pool_max中的size分配的DMA pool。
     if ((retval = hcd_buffer_create(hcd)) != 0) {
  dev_dbg(hcd->self.controller, "pool alloc failed\n");
  return retval;
 }
每个主机控制器代表一条USB总线,注册该条USB总线。
 if ((retval = usb_register_bus(&hcd->self)) < 0)
  goto err_register_bus;
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;
 }
 hcd->self.root_hub = rhdev;

 switch (hcd->speed) {
 case HCD_USB11:
  rhdev->speed = USB_SPEED_FULL;
  break;
 case HCD_USB2:
  rhdev->speed = USB_SPEED_HIGH;
  break;
 case HCD_USB3:
  rhdev->speed = USB_SPEED_SUPER;
  break;
 default:
  retval = -EINVAL;
  goto err_set_rh_speed;
 }
reset主机控制器
 if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {
  dev_err(hcd->self.controller, "can't setup\n");
  goto err_hcd_driver_setup;
 }
 hcd->rh_pollable = 1;
走的是uhci_pci_init函数:
 uhci->io_addr = (unsigned long) hcd->rsrc_start;
计算root hub的端口数,UHCI文档上是2个
 uhci->rh_numports = uhci_count_ports(hcd);

 /* Intel controllers report the OverCurrent bit active on.
  * VIA controllers report it active off, so we'll adjust the
  * bit value.  (It's not standardized in the UHCI spec.)
  */
 if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_VIA)
  uhci->oc_low = 1;

 /* HP's server management chip requires a longer port reset delay. */
 if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_HP)
  uhci->wait_for_hp = 1;

 /* Set up pointers to PCI-specific functions */
 uhci->reset_hc = uhci_pci_reset_hc;
 uhci->check_and_reset_hc = uhci_pci_check_and_reset_hc;
 uhci->configure_hc = uhci_pci_configure_hc;
 uhci->resume_detect_interrupts_are_broken =
  uhci_pci_resume_detect_interrupts_are_broken;
 uhci->global_suspend_mode_is_broken =
  uhci_pci_global_suspend_mode_is_broken;


 /* Kick BIOS off this hardware and reset if the controller
  * isn't already safely quiescent.
  */
 check_and_reset_hc(uhci);
结束reset

 /* 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 (usb_hcd_is_primary_hcd(hcd)) {
  retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);
  if (retval)
   goto err_request_irq;
 }
调用start函数,后面分析。
 hcd->state = HC_STATE_RUNNING;
 retval = hcd->driver->start(hcd);
 if (retval < 0) {
  dev_err(hcd->self.controller, "startup error %d\n", retval);
  goto err_hcd_driver_start;
 }
注册root hub
 /* 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(hcd))
  usb_hcd_poll_rh_status(hcd);
 return retval;
usb_add_hcd结束
usb_hcd_pci_probe结束。

分配root hub的时候使用了usb_alloc_dev函数,分析如下:
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;

 dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 if (!dev)
  return NULL;

 if (!usb_get_hcd(bus_to_hcd(bus))) {
  kfree(dev);
  return NULL;
 }
 /* Root hubs aren't true devices, so don't allocate HCD resources */
 if (usb_hcd->driver->alloc_dev && parent &&
  !usb_hcd->driver->alloc_dev(usb_hcd, dev)) {
  usb_put_hcd(bus_to_hcd(bus));
  kfree(dev);
  return NULL;
 }

 device_initialize(&dev->dev);
设置要挂在哪个bus_type上,设备驱动模型方面的代码
 dev->dev.bus = &usb_bus_type;
 dev->dev.type = &usb_device_type;
 dev->dev.groups = usb_device_groups;
 dev->dev.dma_mask = bus->controller->dma_mask;
 set_dev_node(&dev->dev, dev_to_node(bus->controller));
设备ATTACHED状态
 dev->state = USB_STATE_ATTACHED;
 atomic_set(&dev->urbnum, 0);

 INIT_LIST_HEAD(&dev->ep0.urb_list);
ep0,默认控制端点,首先设置它的maxpacket为默认大小,后面得到设备描述符后再更新
 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, false);
=========================================================
该函数如下:
 void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep,
  bool reset_ep)
{
 int epnum = usb_endpoint_num(&ep->desc);为0
 int is_out = usb_endpoint_dir_out(&ep->desc);为1
 int is_control = usb_endpoint_xfer_control(&ep->desc);为1

 if (reset_ep)
  usb_hcd_reset_endpoint(dev, ep);
is_control为1,也就是会执行后面这几个条件语句
 if (is_out || is_control)
  dev->ep_out[epnum] = ep;
 if (!is_out || is_control)
  dev->ep_in[epnum] = ep;
 ep->enabled = 1;
}
===========================================================
 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 name isn't
  * as stable:  bus->busnum changes easily from modprobe order,
  * cardbus or pci hotplugging, and so on.
  */
 if (unlikely(!parent)) {
如果设备是root hub 则名字是usb1,usb2。。。。devpath[0]是0
  dev->devpath[0] = '0';
  dev->route = 0;

  dev->dev.parent = bus->controller;
  dev_set_name(&dev->dev, "usb%d", bus->busnum);
  root_hub = 1;
 } else {
对于root hub下的设备.它的名称为:总线号+”-”+端口号.例如,第一条usb总线上的root hub的第一个端口上的设备叫”1-0”.第二个端口上的设备名称为”1-1”
对于父结点不是root hub的设备.它的名称为: 总线号+”-”+端口路径. 例如.在第一条usb总线上的root hub的第一个端口上的hub上.第一个端口上的设备名称叫做: 1-0.1 ,第二个端口上的设备名称叫做1-0.2
依次往下推……
如果你到/sys中查看usb设备的话,看到的名称跟这里分析的会不一样.这是因为,对bus_id的处理还没完呢!后面还有相关的处理.等代码分析到了的时候再看. *^_^*.
  /* match any labeling on the hubs; it's one-based */
  if (parent->devpath[0] == '0') {
   snprintf(dev->devpath, sizeof dev->devpath,
    "%d", port1);
   /* Root ports are not counted in route string */
   dev->route = 0;
  } else {
   snprintf(dev->devpath, sizeof dev->devpath,
    "%s.%d", parent->devpath, port1);
   /* Route string assumes hubs have less than 16 ports */
   if (port1 < 15)
    dev->route = parent->route +
     (port1 << ((parent->level - 1)*4));
   else
    dev->route = parent->route +
     (15 << ((parent->level - 1)*4));
  }

  dev->dev.parent = &parent->dev;
  dev_set_name(&dev->dev, "%d-%s", bus->busnum, dev->devpath);

  /* hub driver sets up TT records */
 }

 dev->portnum = port1;
 dev->bus = bus;
 dev->parent = parent;
 INIT_LIST_HEAD(&dev->filelist);

#ifdef CONFIG_PM
 pm_runtime_set_autosuspend_delay(&dev->dev,
   usb_autosuspend_delay * 1000);
 dev->connect_time = jiffies;
 dev->active_duration = -jiffies;
#endif
 if (root_hub) /* Root hub always ok [and always wired] */
  dev->authorized = 1;
 else {
  dev->authorized = usb_hcd->authorized_default;
  dev->wusb = usb_bus_is_wusb(bus)? 1 : 0;
 }
 return dev;
}

 

 


static int uhci_start(struct usb_hcd *hcd)
{
 struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 int retval = -EBUSY;
 int i;
 struct dentry __maybe_unused *dentry;

 hcd->uses_new_polling = 1;

 spin_lock_init(&uhci->lock);
 setup_timer(&uhci->fsbr_timer, uhci_fsbr_timeout,
   (unsigned long) uhci);
 INIT_LIST_HEAD(&uhci->idle_qh_list);
 init_waitqueue_head(&uhci->waitqh);

#ifdef UHCI_DEBUG_OPS
 dentry = debugfs_create_file(hcd->self.bus_name,
   S_IFREG|S_IRUGO|S_IWUSR, uhci_debugfs_root,
   uhci, &uhci_debug_operations);
 if (!dentry) {
  dev_err(uhci_dev(uhci), "couldn't create uhci debugfs entry\n");
  return -ENOMEM;
 }
 uhci->dentry = dentry;
#endif
分配DMA内存区,1024个frame
 uhci->frame = dma_alloc_coherent(uhci_dev(uhci),
   UHCI_NUMFRAMES * sizeof(*uhci->frame),
   &uhci->frame_dma_handle, 0);
 if (!uhci->frame) {
  dev_err(uhci_dev(uhci), "unable to allocate "
    "consistent memory for frame list\n");
  goto err_alloc_frame;
 }
 memset(uhci->frame, 0, UHCI_NUMFRAMES * sizeof(*uhci->frame));

 uhci->frame_cpu = kcalloc(UHCI_NUMFRAMES, sizeof(*uhci->frame_cpu),
   GFP_KERNEL);
 if (!uhci->frame_cpu) {
  dev_err(uhci_dev(uhci), "unable to allocate "
    "memory for frame pointers\n");
  goto err_alloc_frame_cpu;
 }
TD池 用于以后TD的分配
 uhci->td_pool = dma_pool_create("uhci_td", uhci_dev(uhci),
   sizeof(struct uhci_td), 16, 0);
 if (!uhci->td_pool) {
  dev_err(uhci_dev(uhci), "unable to create td dma_pool\n");
  goto err_create_td_pool;
 }
=============================================================================================
QH池 用于以后QH的分配
QH结构中硬件直接使用的是以下两个字段(UHCI文档定义)
 /* Hardware fields */
 __hc32 link;   /* Next QH in the schedule */ 指向下一个QH
 __hc32 element;   /* Queue element (TD) pointer */这个QH中的TD元素

 uhci->qh_pool = dma_pool_create("uhci_qh", uhci_dev(uhci),
   sizeof(struct uhci_qh), 16, 0);
 if (!uhci->qh_pool) {
  dev_err(uhci_dev(uhci), "unable to create qh dma_pool\n");
  goto err_create_qh_pool;
 }
============================================================================================

 


=============================================================================================
分配TD结构,分配后td->dma_handle为DMA使用的物理地址
TD结构中硬件直接使用的是以下四个字段(UHCI文档定义)
 /* Hardware fields */
 __hc32 link;         指向QH或TD,同QH的link
 __hc32 status;
 __hc32 token;
token有32个bit:
bit31到bit21表示Maximum Length,即这次传输的最大允许字节.
bit20是保留位
bit19表示Data Toggle
bit18到bit15表示Endpoint的地址,即我们曾经说的端点号
bit14到bit8表示设备地址
bit7到bit0表示PID,即Packet ID
有一套宏专门用来操作token的:
    208 /*

    209  * for TD <info>: (a.k.a. Token)

    210  */

    211 #define td_token(td)            le32_to_cpu((td)->token)

    212 #define TD_TOKEN_DEVADDR_SHIFT  8

    213 #define TD_TOKEN_TOGGLE_SHIFT   19

    214 #define TD_TOKEN_TOGGLE         (1 << 19)

    215 #define TD_TOKEN_EXPLEN_SHIFT   21

    216 #define TD_TOKEN_EXPLEN_MASK    0x7FF   /* expected length, encoded as n-1 */

    217 #define TD_TOKEN_PID_MASK       0xFF

    218

    219 #define uhci_explen(len)        ((((len) - 1) & TD_TOKEN_EXPLEN_MASK) << /

    220                                         TD_TOKEN_EXPLEN_SHIFT)

    221

    222 #define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + /

    223                                         1) & TD_TOKEN_EXPLEN_MASK)

    224 #define uhci_toggle(token)      (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1)

    225 #define uhci_endpoint(token)    (((token) >> 15) & 0xf)

    226 #define uhci_devaddr(token)     (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f)

    227 #define uhci_devep(token)       (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff)

    228 #define uhci_packetid(token)    ((token) & TD_TOKEN_PID_MASK)

    229 #define uhci_packetout(token)   (uhci_packetid(token) != USB_PID_IN)

    230 #define uhci_packetin(token)    (uhci_packetid(token) == USB_PID_IN)

 __hc32 buffer;

 uhci->term_td = uhci_alloc_td(uhci);
 if (!uhci->term_td) {
  dev_err(uhci_dev(uhci), "unable to allocate terminating TD\n");
  goto err_alloc_term_td;
 }
 
================================================================================================

 

分配QH,skelqh有11个元素
 for (i = 0; i < UHCI_NUM_SKELQH; i++) {
  uhci->skelqh[i] = uhci_alloc_qh(uhci, NULL, NULL);
  if (!uhci->skelqh[i]) {
   dev_err(uhci_dev(uhci), "unable to allocate QH\n");
   goto err_alloc_skelqh;
  }
 }
 
 
 
====================================================================================
 /*
  * 8 Interrupt queues; link all higher int queues to int1 = async
  */
uhci->skelqh数组的2到8项的后续指针都指向了skelqh[9].skelqh[9]指向了UHCI_PTR_TERM.(skel_async_qh为skelqh[9])
uhci->skelqh[2]~ uhci->skelqh[9].代表8个时间间隔的调度队列.
依次被称为 int128,int64,int32,int16,int8,int4,int2,int1.
即对于int128,即每隔128ms调度一次.int1.即每隔1ms调度一次,
 for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)
  uhci->skelqh[i]->link = LINK_TO_QH(uhci, uhci->skel_async_qh);
skel_async_qh的link无效
 uhci->skel_async_qh->link = UHCI_PTR_TERM(uhci);
skel_term_qh为skelqh[10](最后一个数组元素,一共11个元素),它的link指向它自己。
 uhci->skel_term_qh->link = LINK_TO_QH(uhci, uhci->skel_term_qh);
关于LINK_TO_QH(qh)
#define LINK_TO_QH(qh)          (UHCI_PTR_QH | cpu_to_le32((qh)->dma_handle))

76 #define UHCI_PTR_BITS           __constant_cpu_to_le32(0x000F)

77 #define UHCI_PTR_TERM           __constant_cpu_to_le32(0x0001)

78 #define UHCI_PTR_QH             __constant_cpu_to_le32(0x0002) 设置link指针中的bit1

79 #define UHCI_PTR_DEPTH          __constant_cpu_to_le32(0x0004)

80 #define UHCI_PTR_BREADTH        __constant_cpu_to_le32(0x0000)
 
__le32 link 是一个指针,这个指针指向下一个QH,
它包含着下一个QH或者下一个TD的地址.
一共32个bits,其中只有bit31到bit4这些位是用来记录地址的,QH,TD都是16字节对齐的,所以低4位可以做以下用途:
bit3和bit2是保留位,
bit1则用来表示该指针指向的是一个QH还是一个TD.
bit1如果为1,表示本指针指向的是一个QH,
    如果为0,表示本指针指向的是一个TD
bit0表示本QH是否是最后一个QH,为1表示本QH是最后一个QH,所以当bit0为1时,link指针其他位都是无效的,所以用UHCI_PTR_TERM来设置一个link指针无效。
======================================================================================


 /* This dummy TD is to work around a bug in Intel PIIX controllers */
 uhci_fill_td(uhci, uhci->term_td, 0, uhci_explen(0) |
   (0x7f << TD_TOKEN_DEVADDR_SHIFT) | USB_PID_IN, 0);
=======================================================================================
uhci_fill_td函数如下:
static inline void uhci_fill_td(struct uhci_hcd *uhci, struct uhci_td *td,
  u32 status, u32 token, u32 buffer)
{
 td->status = cpu_to_hc32(uhci, status);
 td->token = cpu_to_hc32(uhci, token);
 td->buffer = cpu_to_hc32(uhci, buffer);
}
按照上面的TD token操作宏 解释如下:
uhci_explen(0)表示将bit31到bit21的Maximum Length设置为0.
(0x7f << TD_TOKEN_DEVADDR_SHIFT)表示将bit14到bit8的设备地址设置为0x7f
USB_PID_IN表示将bit7到bit0的PID设置为0x69,该值是文档中定义的表示PID IN。
=======================================================================================

将link指针无效
 uhci->term_td->link = UHCI_PTR_TERM(uhci);
skelqh数组的9和10元素的element都指向term_td
 uhci->skel_async_qh->element = uhci->skel_term_qh->element =
  LINK_TO_TD(uhci, uhci->term_td);

 /*
  * Fill the frame list: make all entries point to the proper
  * interrupt queue.
  */
将skelqh数组根据周期连接到frame上
 for (i = 0; i < UHCI_NUMFRAMES; i++) {

  /* Only place we don't use the frame list routines */
  uhci->frame[i] = uhci_frame_skel_link(uhci, i);
 }
===============================================================================
uhci_frame_skel_link该函数有点复杂:
static __hc32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)
{
 int skelnum;

 /*
  * The interrupt queues will be interleaved as evenly as possible.
  * There's not much to be done about period-1 interrupts; they have
  * to occur in every frame.  But we can schedule period-2 interrupts
  * in odd-numbered frames, period-4 interrupts in frames congruent
  * to 2 (mod 4), and so on.  This way each frame only has two
  * interrupt QHs, which will help spread out bandwidth utilization.
  *
  * ffs (Find First bit Set) does exactly what we need:
  * 1,3,5,...  => ffs = 0 => use period-2 QH = skelqh[8],
  * 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc.
  * ffs >= 7 => not on any high-period queue, so use
  * period-1 QH = skelqh[9].
  * Add in UHCI_NUMFRAMES to insure at least one bit is set.
  */
 skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);
 if (skelnum <= 1)
  skelnum = 9;
 return LINK_TO_QH(uhci, uhci->skelqh[skelnum]);
}
__ffs函数功能如下:
给它一个输入参数,它找到这个输入的参数的第一个为1的位
输入数字:0-1023
1,3,5,7,9等奇数:      那么返回0,skelnum为8,也就是连接到skelqh[8]上,这是一个2ms周期的QH。
2,6,10,14,18,22,26等           1           7                     7             4
4,12,20,28,36等
===================================================================================

目前frame里保存的是对应周期的qh的地址(就是skelqh数组里面的那几个元素),
而每个QH的link都指向skelqh[9],skelqh[9]的link是UHCI_PTR_TERM,element指向uhci->term_td。
也就是以后只要将TD连接到对应周期的skelqh数组元素上,那么就会被处理。
 /*
  * Some architectures require a full mb() to enforce completion of
  * the memory writes above before the I/O transfers in configure_hc().
  */
 mb();
设置frame地址什么的,最后配置
 configure_hc(uhci);
 uhci->is_initialized = 1;
 spin_lock_irq(&uhci->lock);
驱动root hub
 start_rh(uhci);
 spin_unlock_irq(&uhci->lock);
 return 0;
主机控制器的初始化完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值