转:linux设备模型之pci设备的I/O和内存


转:linux设备模型之pci设备的I/O和内存
2011年06月16日
  linux设备模型之pci设备的I/O和内存 
  来源: ChinaUnix博客 日期: 2008.08.25 16:36 (共有0条评论) 我要评论 
  ------------------------------------------ 
  本文系本站原创,欢迎转载!
  转载请注明出处:http://ericxiao.cublog.cn/
  ------------------------------------------
  Pci设备的I/O和内存是一个比较复杂的问题.如下的总线结构:
  
  在上图的总线结构中,ethernet设备和pci-pci bridge的同类型资源空间必须要是pci bus0的一个子集
  例如,pci bus 0的I/O端口资源是0x00CC~0x01CC. Ethernet设备的I/O范围的是0x00CC~0x0xE0.那么pci-pci bridge的I/O端口范围就必须要在0x0xE0~0x01CC之间.
  同样,SCSI和VIDEO同类型资源必须要是pci_bus1的子集.pci bus1上有一个pci桥,对应的资源也就是它所连桥上的资源.即pci_bus->self. 
  也就是说,下层总线的资源是它上层总线资源的子集。上层总线资源是下层总线资源的父集。
  其实,每个PCI设备的资源地始地址都是由操作系统设置的.在x86上,都由bios设置好了.假若没有bios的时候,我们应该怎么去设置设备的资源起始范围呢?
  可能在pci枚举完成之后:
  1:从根总线开始,设置根总线的资源范围是从0开始,到0xFFFF或者0xFFFFFFFF的最大范围.
  2:对其它的设备,可往其资源寄存器全部写入1,就可以求得该资源项的类型和长度.
  3:设备从根总线的资源那里分得对应长度的资源.
  4:如果设备是pci-pci bridge,则递归配置它.
  可能有人会有这样迷惑,对应于上图,如果pci-pci bridge的资源大小是N.而SCSI和video资源范围超过了N怎么办呢?
  我们必须要注意一点,总线的区间是可以自已设定的,而设备资源的区间是在设计的时候就已经确定好了.也就是说,我们可以更改pci device区间的起始地址,但我们不能改变它的大小.
  因此,出现了上面所说的这种情况.可能是由bios在处理PCI的时候出现了BUG.我们需要调整总线的资源区间.
  其实对于pci_bus的资源范围就是它的过滤窗口.对于过滤窗口的作用,我们在枚举的时候分析的很清楚了.
  CPU访问PC过程是这样的(只有一个根总线和pci-pci bridge过滤窗口功能打开的情况):
  1:cpu向pci发出一个I/O请求.首先经过根总线.它会判断是否在它的资源范围内.如果在它的范围,它就会丢向总线所在的每一个设备.包括pci bridge. 如果没有在根总线的资源范围,则不会处理这个请求.
  2:如果pci设备判断该地址属于它的资源范围,则处理后发出应答
  4:pci bridge接收到这个请求,它会判断I/O地址是否在它的资源范围内.如果在它的范围,它会把它丢到它的下层子线.
  5:下层总线经过经过相同的处理后,就会找到这个PCI设备了
  一个PCI设备访问其它PCI设备或者其它外设的过程:
  1:首先这个PCI发出一个请求,这个请求会在总线上广播
  2:如果要请求的设备是在同级总线,就会产生应答
  3:请求的设备不是在同层总线,就会进行pci bridge.pci桥判断该请求不在它的范围内(目的地不是它下层的设备),就会将它丢向上层.
  4:这样辗转之后,就能找到对应的设备了
  经过这样的分析过来,相信对pci bridge的过滤窗口有更深的理解了.
  Linux中使用struct resource的结构来表示I/O端口或者是设备内存。定义如下:
  struct resource {
  resource_size_t start;
  resource_size_t end;
  const char *name;
  unsigned long flags;
  struct resource *parent, *sibling, *child;
  };
  Start: 表示它所占资源的起始地址。
  End: 表示它所占资源的未尾地址
  Name: 所占资源的名字
  Flags: 资源的类型。目前有I/O和内存两种
  Parent.sibling.child:用来表示资源的所属关系。分别表示它的父结点,兄弟结点和子结点。
  从前面的分析可以看到,有一些总线可能bios没有遍历到或许bios的处理有错误,所以需要对整个系统的PCI总线和PCI设备的资源占用情况遍历一次。完整的建立上述的struct resource结构(在之前枚举的时候,只是处理了start和end成员).。这个过程是在pcibios_resource_survey( )完成的。如下所示:
  subsys_initcall(pcibios_init);
  static int __init pcibios_init(void)
  {
  ……
  …….
  pcibios_resource_survey();
  }
  pcibios_init这个函数是被fs_initcall()所描述的。在kernel启动的时候,会调用宏所描述的函数。在pcibios_init ()又会调用pcibios_assign_resources(),它的代码如下所示:
  void __init pcibios_resource_survey(void)
  {
  DBG("PCI: Allocating resources\n");
  pcibios_allocate_bus_resources(&pci_root_buses);
  pcibios_allocate_resources(0);
  pcibios_allocate_resources(1);
  }
  它先对总线的资源进行处理。然后再对PCI设备的资源进行处理。我们先看pcibios_allocate_bus_resources()
  static void __init pcibios_allocate_bus_resources(struct list_head *bus_list)
  {
  struct pci_bus *bus;
  struct pci_dev *dev;
  int idx;
  struct resource *r, *pr;
  /* Depth-First Search on bus tree */
  list_for_each_entry(bus, bus_list, node) {
  //pci-bridge
  if ((dev = bus->self)) {
  for (idx = PCI_BRIDGE_RESOURCES;
  idx 
  r = &dev->resource[idx];
  if (!r->flags)
  continue;
  pr = pci_find_parent_resource(dev, r);
  if (!r->start || !pr ||
  request_resource(pr, r) 
  printk(KERN_ERR "PCI: Cannot allocate "
  "resource region %d "
  "of bridge %s\n",
  idx, pci_name(dev));
  /*
  * Something is wrong with the region.
  * Invalidate the resource to prevent
  * child resource allocations in this
  * range.
  */
  r->flags = 0;
  }
  }
  }
  pcibios_allocate_bus_resources(&bus->children);
  }
  }
  这个是一个深度优先搜索算法。类似的算法在pci结构中用得很多。
  它遍历pci_root_buses中的每一个根总线下面的所有总线。如果该总线有对应的pci-pci bridge,就先处理这个pci桥的资源.
  PCI桥的资源范围是PCI_BRIDGE_RESOURCES~ PCI_NUM_RESOURCES.对于它的每个资源区间。都要从它的上层总线中获得.代码中遍历PCI桥的每一个资源区间,然后找到它在上层总线的对应区间。然后为它建立结构关系。
  pci_find_parent_resource()就是为PCI的资源区间找到一个合适的父结点。代码如下:
  struct resource *
  pci_find_parent_resource(const struct pci_dev *dev, struct resource *res)
  {
  const struct pci_bus *bus = dev->bus;
  int i;
  struct resource *best = NULL;
  for(i = 0; i 
  struct resource *r = bus->resource;
  if (!r)
  continue;
  if (res->start && !(res->start >= r->start && res->end end))
  continue;  /* Not contained */
  if ((res->flags ^ r->flags) & (IORESOURCE_IO | IORESOURCE_MEM))
  continue;  /* Wrong type */
  if (!((res->flags ^ r->flags) & IORESOURCE_PREFETCH))
  return r;     /* Exact match */
  if ((res->flags & IORESOURCE_PREFETCH) && !(r->flags & IORESOURCE_PREFETCH))
  best = r;    /* Approximating prefetchable by non-prefetchable */
  }
  return best;
  }
  首先从pci_dev ->bus就找到了它的上层总线,每条总线拥有PCI_BUS_NUM_RESOURCES个资源区间. 所要寻找的父结点必须要满足以后几个条件:
  1:子结点的范围必须要落在父结点的区间范围内
  2:父子区间的基本类型应该一致。(基本类型即IO或者内存)
  3:如果父子窗口都是可预读的,就完全匹配了
  4:如果子结点可预读,而父结点不可预读。也将就着可以了.
  注意:父结点可预读而子结点不可预读是不允许的。
  找到它所属的父结点之后,会调用request_resource()从父结点中请求资源。代码如下:
  int request_resource(struct resource *root, struct resource *new)
  {
  struct resource *conflict;
  write_lock(&resource_lock);
  conflict = __request_resource(root, new);
  write_unlock(&resource_lock);
  return conflict ? -EBUSY : 0;
  }
  __request_resource()代码如下:
  static struct resource * __request_resource(struct resource *root, struct resource *new)
  {
  resource_size_t start = new->start;
  resource_size_t end = new->end;
  struct resource *tmp, **p;
  if (end 
  return root;
  if (start start)
  return root;
  if (end > root->end)
  return root;
  p = &root->child;
  for (;;) {
  tmp = *p;
  if (!tmp || tmp->start > end) {
  new->sibling = tmp;
  *p = new;
  new->parent = root;
  return NULL;
  }
  p = &tmp->sibling;
  if (tmp->end 
  continue;
  return tmp;
  }
  }
  这个函数的逻辑比较简单。即在它父节点的子节点中找个合适的位置插下去。父结点的子结点都是按照起始资源地址从小到大的顺序排列的。
  返回到pcibios_allocate_bus_resources()中,如果它的资源分配过程失败,它会怎么处理呢?看下面的代码片段:
  static void __init pcibios_allocate_bus_resources(struct list_head *bus_list)
  {
  ……
  ……
  if (!r->start || !pr || request_resource(pr, r) 
  printk(KERN_ERR "PCI: Cannot allocate "
  "resource region %d "
  "of bridge %s\n",
  idx, pci_name(dev));
  r->flags = 0;
  }
  ……
  ……
  }
  也就是说,如果分配失败了,它会将资源的flags标志置为0
  回到pcibios_resource_survey()中,接着往下来,会发现它以不同的参数调用了pcibios_allocate_resources()两次。跟进这个函数的代码进行分析:
  static void __init pcibios_allocate_resources(int pass)
  {
  struct pci_dev *dev = NULL;
  int idx, disabled;
  u16 command;
  struct resource *r, *pr;
  for_each_pci_dev(dev) {
  pci_read_config_word(dev, PCI_COMMAND, &command);
  for (idx = 0; idx 
  r = &dev->resource[idx];
  if (r->parent)               /* Already allocated */
  continue;
  if (!r->start)                  /* Address not assigned at all */
  continue;
  if (r->flags & IORESOURCE_IO)
  disabled = !(command & PCI_COMMAND_IO);
  else
  disabled = !(command & PCI_COMMAND_MEMORY);
  //对于已经启用的,在第一次扫描的时候就将其配制
  //否则。要等到第二次
  if (pass == disabled) {
  DBG("PCI: Resource %08lx-%08lx "
  "(f=%lx, d=%d, p=%d)\n",
  r->start, r->end, r->flags, disabled, pass);
  pr = pci_find_parent_resource(dev, r);
  if (!pr || request_resource(pr, r) 
  printk(KERN_ERR "PCI: Cannot allocate "
  "resource region %d "
  "of device %s\n",
  idx, pci_name(dev));
  /* We'll assign a new address later */
  r->end -= r->start;
  r->start = 0;
  }
  }
  }
  if (!pass) {
  //对于ROM。在第一次扫描时就将它关闭
  r = &dev->resource[PCI_ROM_RESOURCE];
  if (r->flags & IORESOURCE_ROM_ENABLE) {
  /* Turn the ROM off, leave the resource region,
  * but keep it unregistered. */
  u32 reg;
  DBG("PCI: Switching off ROM of %s\n",
  pci_name(dev));
  r->flags &= ~IORESOURCE_ROM_ENABLE;
  pci_read_config_dword(dev,
  dev->rom_base_reg, &reg);
  pci_write_config_dword(dev, dev->rom_base_reg,
  reg & ~PCI_ROM_ADDRESS_ENABLE);
  }
  }
  }
  }
  该函数遍历整个pci设备。如果该设备的相应空间已经启用了(I/O或者内存)。那在以0为参数调用的时候就让它分配好资源。对于没有被启用的资源。要等到第二次以1为参数调用参数时才会处理。
  另外:在第一次处理中就把设备的ROM区间关闭。要等到使用设备的时候再把区间打开。这个打开的过程一般在设备驱动程序里完成。
  到这里,我们终于知道为什么要用不同的参数调用函数二次。这样做是为了优先让已经被启用的资源从父节点中分得资源。
  如果资源分配失败了。就会将相应资源的start设为0。End成员则设为这个区间的长度。
  有必要提一下linux2.4.12中这部份的处理,它是这样子的:
  对于pci bus的资源分配,如果分配失败.不做任何事情,只是打印出一条警告语句.
  而在linux2.6.25中.如果pci bus资源分配失败,会将资源的flag置为0.
  为什么2.6要这样做呢?
  其实2.4.12中的处理是一个BUG.如果pci bus资源分配失败没有任何的处理的话.那接下来为该总线下面的pci device分配资源的时候,是从该总线的资源中请求的.而事实上,该总线的资源又是有冲突的.这样也造成了该总线下层设备的资源无效.
  而在linux 2.6中.将分配失败总线的flag置为了0.下层设备在请求资源的分配的时候,就会类型匹配失败,而避免了上面说的这个部问题.
  这个BUG不知道在后面的版本中有没有被修正 :-)
  pcibios_resource_survey()执先完了之后。Pci的所有总线和设备的资源都被验证分配了一次。对于不能正确分配资源的设备。也做好了标记。接下来,我们来看一下怎么处理资源分配失败的设备。
  看下面的这段代码:
  fs_initcall(pcibios_assign_resources);
  fs_initcall()的优先级比subsys_initcall的优先级小,它在pcibios_init()之后才会得到运行。看一下它的代码:
  static int __init pcibios_assign_resources(void)
  {
  struct pci_dev *dev = NULL;
  struct resource *r, *pr;
  if (!(pci_probe & PCI_ASSIGN_ROMS)) {
  /*
  * Try to use BIOS settings for ROMs, otherwise let
  * pci_assign_unassigned_resources() allocate the new
  * addresses.
  */
  for_each_pci_dev(dev) {
  r = &dev->resource[PCI_ROM_RESOURCE];
  if (!r->flags || !r->start)
  continue;
  pr = pci_find_parent_resource(dev, r);
  if (!pr || request_resource(pr, r) 
  r->end -= r->start;
  r->start = 0;
  }
  }
  }
  pci_assign_unassigned_resources();
  return 0;
  }
  之前在pcibios_resource_survey()中没有处理ROM的区间。在这里,先遍历每个设备,处理一下它的ROM空间的资源分配。照以前的方式一样,如果资源分配失败,就让它的start置为0。End置为区间的长度。
  处理完之后,进入到pci_assign_unassigned_resources()。我们希望每一个PCI设备,bios都为我们处理好了。可是总是事与愿违。进行的这里,不得不处理一下之前资源分配失败的设备了。代码如下:
  void __init
  pci_assign_unassigned_resources(void)
  {
  struct pci_bus *bus;
  /* Depth first, calculate sizes and alignments of all
  subordinate buses. */
  list_for_each_entry(bus, &pci_root_buses, node) {
  pci_bus_size_bridges(bus);
  }
  /* Depth last, allocate resources and update the hardware. */
  list_for_each_entry(bus, &pci_root_buses, node) {
  pci_bus_assign_resources(bus);
  pci_enable_bridges(bus);
  }
  }
  首先我们要处理资源分配失败的pci_bus。在上面的分析中,如果pci bus资源分配失败。就会将其所属资源的flags置为0.对于这些总线,是在第一个循环里处理的.
  第一个循环,遍历挂在pci_root_buses上的所有根结点。然后调用pci_bus_size_bridges()。代码如下:
  void __ref pci_bus_size_bridges(struct pci_bus *bus)
  {
  struct pci_dev *dev;
  unsigned long mask, prefmask;
  list_for_each_entry(dev, &bus->devices, bus_list) {
  struct pci_bus *b = dev->subordinate;
  if (!b)
  continue;
  switch (dev->class >> {
  case PCI_CLASS_BRIDGE_CARDBUS:
  pci_bus_size_cardbus(b);
  break;
  case PCI_CLASS_BRIDGE_PCI:
  default:
  pci_bus_size_bridges(b);
  break;
  }
  }
  /* The root bus? */
  if (!bus->self)
  return;
  switch (bus->self->class >> {
  case PCI_CLASS_BRIDGE_CARDBUS:
  /* don't size cardbuses yet. */
  break;
  case PCI_CLASS_BRIDGE_PCI:
  pci_bridge_check_ranges(bus);
  default:
  pbus_size_io(bus);
  /* If the bridge supports prefetchable range, size it
  separately. If it doesn't, or its prefetchable window
  has already been allocated by arch code, try
  non-prefetchable range for both types of PCI memory
  resources. */
  mask = IORESOURCE_MEM;
  prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH;
  if (pbus_size_mem(bus, prefmask, prefmask))
  mask = prefmask; /* Success, size non-prefetch only. */
  pbus_size_mem(bus, mask, IORESOURCE_MEM);
  break;
  }
  }
  这是一个深度优先搜索算法。首先遍历总线上的所有设备,如果是pci bridge,递归调用pci_bus_size_bridges()处理下层pci bus.对于每一条不是根总线的pci bus都会经过大循环后面的处理,即对应于case PCI_CLASS_BRIDGE_PCI后面的处理.它要经过的第一个函数是pci_bridge_check_ranges().代码如下:
  static void pci_bridge_check_ranges(struct pci_bus *bus)
  {
  u16 io;
  u32 pmem;
  struct pci_dev *bridge = bus->self;
  struct resource *b_res;
  b_res = &bridge->resource[PCI_BRIDGE_RESOURCES];
  b_res[1].flags |= IORESOURCE_MEM;
  pci_read_config_word(bridge, PCI_IO_BASE, &io);
  if (!io) {
  pci_write_config_word(bridge, PCI_IO_BASE, 0xf0f0);
  pci_read_config_word(bridge, PCI_IO_BASE, &io);
  pci_write_config_word(bridge, PCI_IO_BASE, 0x0);
  }
  if (io)
  b_res[0].flags |= IORESOURCE_IO;
  /*  DECchip 21050 pass 2 errata: the bridge may miss an address
  disconnect boundary by one PCI data phase.
  Workaround: do not use prefetching on this device. */
  if (bridge->vendor == PCI_VENDOR_ID_DEC && bridge->device == 0x0001)
  return;
  pci_read_config_dword(bridge, PCI_PREF_MEMORY_BASE, &pmem);
  if (!pmem) {
  pci_write_config_dword(bridge, PCI_PREF_MEMORY_BASE,
  0xfff0fff0);
  pci_read_config_dword(bridge, PCI_PREF_MEMORY_BASE, &pmem);
  pci_write_config_dword(bridge, PCI_PREF_MEMORY_BASE, 0x0);
  }
  if (pmem)
  b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH;
  }
  Pci bridge设备的7,8,9项是属于过滤窗口,也就是对应于总线的资源。
  在这里,检查pci bus是否支持I/O和prefetch memory类型的窗口.如果不支持,则相应寄存器是只读的,而且值为0. 顺便说一句,所有的pci bus都是支持memory窗口的.如果相应类型的资源有效则给相应资源的flags置相应的标志。最后还要记得将相关寄存器清空。
  我们在前面分析过.
  经过这里的处理,只要判断区间存在,就会置相应的标志。那么,对于我们在上面分配资源失败的pci bus.如果区间确实存在的话。就会重新设置这个标志。
  重新设置标志的操作是很重要的一个步骤.它能将前面处理时,因资源冲突的pci bus修正过来.运行到这里.怎么区分哪些pci bus是有待修正的,而哪些pci bus是正常的呢?
  可能根据 resource->parent来判断.如果是正常的pci bus.该成员会指向它的父结点.如果是有待修正的pci bus.它的这个域是空的.也就是说,还没有链接到父结点.
  回到pci_bus_size_bridges()中,经过pci_bridge_check_ranges()的处理过后,流程会转向pbus_size_io().
  从字面意思看这个函数是用来计算i/o的大小.主要是用来处理有待修正的pci bus.我们来思考一下,为什么pci bus的资源会有冲突呢?可能有两个原因:
  1:pci bus的起始地址冲突.在上层pci bus中,该区间已经被其它设备占用了.这种情况只需要在上层设备中偏移到特定位置就可以了.
  2:pci bus的长度过长.这可能是bios在处理PCI的时候出现了错误,我们需要重新计算pci bus的长度.
  而pbus_size_io()就是用来处理第二种情况的,处理资源的类型为I/O.代码如下:
  static void pbus_size_io(struct pci_bus *bus)
  {
  struct pci_dev *dev;
  struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO);
  unsigned long size = 0, size1 = 0;
  if (!b_res)
  return;
  list_for_each_entry(dev, &bus->devices, bus_list) {
  int i;
  for (i = 0; i 
  struct resource *r = &dev->resource;
  unsigned long r_size;
  if (r->parent || !(r->flags & IORESOURCE_IO))
  continue;
  r_size = r->end - r->start + 1;
  if (r_size 
  /* Might be re-aligned for ISA */
  size += r_size;
  else
  size1 += r_size;
  }
  }
  /* To be fixed in 2.5: we should have sort of HAVE_ISA
  flag in the struct pci_bus. */
  #if defined(CONFIG_ISA) || defined(CONFIG_EISA)
  size = (size & 0xff) + ((size & ~0xffUL) 
  #endif
  size = ALIGN(size + size1, 4096);
  if (!size) {
  b_res->flags = 0;
  return;
  }
  /* Alignment of the IO window is always 4K */
  b_res->start = 4096;
  b_res->end = b_res->start + size - 1;
  }
  首先,它查看对应类型(I/O)的总线资源是否有冲突的情况.如果有冲突,则计算下层需要该资源的总数.冲突资源的长度就是下层设备所需该资源的总数.只不过,对于pci bus 的I/O资源是4K对齐的.因此总长度也是4K对齐.
  find_free_bus_resource()就是用来检查冲突资源项的.代码如下:
  static struct resource *find_free_bus_resource(struct pci_bus *bus, unsigned long type)
  {
  int i;
  struct resource *r;
  unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM |
  IORESOURCE_PREFETCH;
  for (i = 0; i 
  r = bus->resource;
  if (r == &ioport_resource || r == &iomem_resource)
  continue;
  if (r && (r->flags & type_mask) == type && !r->parent)
  return r;
  }
  return NULL;
  }
  对于资源是ioport_resource, iomem_resource的情况.表示该总线是根总线,不需要处理.注意这一句:
  if (r && (r->flags & type_mask) == type && !r->parent)
  不仅要求类型匹配,而且要父节点为空.父节点为空.说明之前在分配资源的时候失败,也即资源冲突.
  因为pci_bus_size_bridges()使用的是深度优先搜索算法,它是先处理最底层的总线,然后再逐层上处理.而下层总线又是上层总线的一个设备(通过pci-pci bridge相连)所以,下层所需的资源长度就可以从下往上反应到上层总线中.最后,到最上层资源冲突的pci bus就可以知道下层需要多少长度的资源了.
  pbus_size_mem()类似于pbus_size_io(),只是它是用来查找memory类型资源的长度.在代码中,以不同的参数两次调用了这个函数,分别是用来查找带预读的存储区间和一般的存储区间.
  pbus_size_mem()和pbus_size_io()相比只是对齐因子不一样,其它大部份的处理都是一样,所以在这里不分析这个函数的代码了.请自行查阅.
  回到pci_assign_unassigned_resources()函数中.剩余的代码如下所示:
  void __init
  pci_assign_unassigned_resources(void)
  {
  ……
  ……
  list_for_each_entry(bus, &pci_root_buses, node) {
  pci_bus_assign_resources(bus);
  //启用pci-pci briage的窗口机制
  pci_enable_bridges(bus);
  }
  }
  遍历pci_root_buses中存放的根总线,对每条根总线调用pci_bus_assign_resources().代码如下:
  void __ref pci_bus_assign_resources(struct pci_bus *bus)
  {
  struct pci_bus *b;
  struct pci_dev *dev;
  pbus_assign_resources_sorted(bus);
  list_for_each_entry(dev, &bus->devices, bus_list) {
  b = dev->subordinate;
  if (!b)
  continue;
  pci_bus_assign_resources(b);
  switch (dev->class >> {
  case PCI_CLASS_BRIDGE_PCI:
  pci_setup_bridge(b);
  break;
  case PCI_CLASS_BRIDGE_CARDBUS:
  pci_setup_cardbus(b);
  break;
  default:
  printk(KERN_INFO "PCI: not setting up bridge %s "
  "for bus %d\n", pci_name(dev), b->number);
  break;
  }
  }
  }
  这也是一个使用深度优先搜索算法的函数.
  现在是时候处理资源冲突的设备了.这是在pbus_assign_resources_sorted()中完成的,代码如下:
  static void pbus_assign_resources_sorted(struct pci_bus *bus)
  {
  struct pci_dev *dev;
  struct resource *res;
  struct resource_list head, *list, *tmp;
  int idx;
  head.next = NULL;
  //遍历该总线下的所有设备
  list_for_each_entry(dev, &bus->devices, bus_list) {
  u16 class = dev->class >> 8;
  /* Don't touch classless devices or host bridges or ioapics.  */
  if (class == PCI_CLASS_NOT_DEFINED ||
  class == PCI_CLASS_BRIDGE_HOST)
  continue;
  /* Don't touch ioapic devices already enabled by firmware */
  if (class == PCI_CLASS_SYSTEM_PIC) {
  u16 command;
  pci_read_config_word(dev, PCI_COMMAND, &command);
  if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY))
  continue;
  }
  //所有没有成功分配资源的resource会放到head链表中
  pdev_sort_resources(dev, &head);
  }
  for (list = head.next; list;) {
  res = list->res;
  idx = res - &list->dev->resource[0];
  if (pci_assign_resource(list->dev, idx)) {
  res->start = 0;
  res->end = 0;
  res->flags = 0;
  }
  tmp = list;
  list = list->next;
  kfree(tmp);
  }
  }
  首先,它将有总线下有资源冲突的设备加到链表中,然后遍历这个链表,处理每个资源冲突的设备.
  pdev_sort_resources()代码很简单,就是计算对齐因子,然后按照对齐因子大小添加到链表.在这里要特别注意.对于pci bus中的资源是要按起始地址对齐的(还记得我们在为冲突的pci bus计算I/O资源长度的时候,它的start成员就是设为4K.表示要4K对齐).这个函数代码比较简单.不再详细分析,请自己阅读.
  具体的资源冲突处理过程是在pci_assign_resource()中完成的,代码如下:
  int pci_assign_resource(struct pci_dev *dev, int resno)
  {
  struct pci_bus *bus = dev->bus;
  struct resource *res = dev->resource + resno;
  resource_size_t size, min, align;
  int ret;
  //空间大小
  size = res->end - res->start + 1;
  //起点最小值
  min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM;
  /* The bridge resources are special, as their
  size != alignment. Sizing routines return
  required alignment in the "start" field. */
  //对于pci-pci bridge.有特殊的对齐方式
  align = (resno start;
  /* First, try exact prefetching match.. */
  //首先,带IORESOURCE_PREFETCH 标志处理
  ret = pci_bus_alloc_resource(bus, res, size, align, min,
  IORESOURCE_PREFETCH,
  pcibios_align_resource, dev);
  if (ret flags & IORESOURCE_PREFETCH)) {
  /*
  * That failed.
  *
  * But a prefetching area can handle a non-prefetching
  * window (it will just not perform as well).
  */
  //分配失败,不带IORESOURCE_PREFETCH标志处理
  ret = pci_bus_alloc_resource(bus, res, size, align, min, 0,
  pcibios_align_resource, dev);
  }
  if (ret) {
  printk(KERN_ERR "PCI: Failed to allocate %s resource "
  "#%d:%llx@%llx for %s\n",
  res->flags & IORESOURCE_IO ? "I/O" : "mem",
  resno, (unsigned long long)size,
  (unsigned long long)res->start, pci_name(dev));
  } else if (resno 
  //如果冲突修正成功
  pci_update_resource(dev, res, resno);
  }
  return ret;
  }
  首先计算该资源的长度和起始地址的最小值以及对齐因子.然后向父结点去申请资源.如果分配失败,那就放松条件,不带预读标志再去分配一次.如果成功,则更新设备的寄存器.如果还是失败,那就没什么办法了.
  pci_bus_alloc_resource()代码如下:
  int
  pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
  resource_size_t size, resource_size_t align,
  resource_size_t min, unsigned int type_mask,
  void (*alignf)(void *, struct resource *, resource_size_t,
  resource_size_t),
  void *alignf_data)
  {
  int i, ret = -ENOMEM;
  type_mask |= IORESOURCE_IO | IORESOURCE_MEM;
  for (i = 0; i 
  struct resource *r = bus->resource;
  if (!r)
  continue;
  /* type_mask must match */
  if ((res->flags ^ r->flags) & type_mask)
  continue;
  /* We cannot allocate a non-prefetching resource
  from a pre-fetching area */
  if ((r->flags & IORESOURCE_PREFETCH) &&
  !(res->flags & IORESOURCE_PREFETCH))
  continue;
  /* Ok, try it out.. */
  ret = allocate_resource(r, res, size,
  r->start ? : min,
  -1, align,
  alignf, alignf_data);
  if (ret == 0)
  break;
  }
  return ret;
  }
  遍历上层总线类型相同的资源区,从中分配资源.如果分配成功退出.
  allocate_resource()如下所示:
  int allocate_resource(struct resource *root, struct resource *new,
  resource_size_t size, resource_size_t min,
  resource_size_t max, resource_size_t align,
  void (*alignf)(void *, struct resource *,
  resource_size_t, resource_size_t),
  void *alignf_data)
  {
  int err;
  write_lock(&resource_lock);
  err = find_resource(root, new, size, min, max, align, alignf, alignf_data);
  if (err >= 0 && __request_resource(root, new))
  err = -EBUSY;
  write_unlock(&resource_lock);
  return err;
  }
  先到父节点中寻找是否有符合的资源区间.如果有,则向父节点请求这个区间.
  __request_resource()的代码之前我们研究了,这里不再赘述. find_resource()代码如下:
  static int find_resource(struct resource *root, struct resource *new,
  resource_size_t size, resource_size_t min,
  resource_size_t max, resource_size_t align,
  void (*alignf)(void *, struct resource *,
  resource_size_t, resource_size_t),
  void *alignf_data)
  {
  struct resource *this = root->child;
  new->start = root->start;
  /*
  * Skip past an allocated resource that starts at 0, since the assignment
  * of this->start - 1 to new->end below would cause an underflow.
  */
  if (this && this->start == 0) {
  new->start = this->end + 1;
  this = this->sibling;
  }
  for(;;) {
  if (this)
  new->end = this->start - 1;
  else
  new->end = root->end;
  if (new->start 
  new->start = min;
  if (new->end > max)
  new->end = max;
  new->start = ALIGN(new->start, align);
  if (alignf)
  alignf(alignf_data, new, size, align);
  if (new->start end && new->end - new->start >= size - 1) {
  new->end = new->start + size - 1;
  return 0;
  }
  if (!this)
  break;
  new->start = this->end + 1;
  this = this->sibling;
  }
  return -EBUSY;
  }
  这个函数比较简单,就是判断父节点是否有足够长的空间区间.
  返回到pci_assign_resource()中,如果修正成功,就会调用pci_update_resource().因为pci设备的资源区间的起始地址改了,要将它更新到寄存器.这正是pci_update_resource()要处理的事情.函数比较简单.就不详细分析了,但这个函数中有个特别的处理,需要指出来.代码片段如下:
  void
  pci_update_resource(struct pci_dev *dev, struct resource *res, int resno)
  {
  ……
  ……
  if (resno 
  reg = PCI_BASE_ADDRESS_0 + 4 * resno;
  } else if (resno == PCI_ROM_RESOURCE) {
  if (!(res->flags & IORESOURCE_ROM_ENABLE))
  return;
  new |= PCI_ROM_ADDRESS_ENABLE;
  reg = dev->rom_base_reg;
  } else {
  /* Hmm, non-standard resource. */
  return;                /* kill uninitialised var warning */
  }
  …… 
  ……
  }
  从上面的处理可以看到,它只会处理前7个资源区间,对于8.9.10区间,是pci-pci bridge的中存放的关于pci bus中的资源信息.
  也就是说,这个函数只会处理pci device中的寄存器,不会处理pci bus.
  返回到pci_bus_assign_resources()中.剩余的代码片段如下:
  void __ref pci_bus_assign_resources(struct pci_bus *bus)
  {
  ……
  ……
  list_for_each_entry(dev, &bus->devices, bus_list) {
  b = dev->subordinate;
  if (!b)
  continue;
  pci_bus_assign_resources(b);
  switch (dev->class >> {
  case PCI_CLASS_BRIDGE_PCI:
  pci_setup_bridge(b);
  break;
  case PCI_CLASS_BRIDGE_CARDBUS:
  pci_setup_cardbus(b);
  break;
  default:
  printk(KERN_INFO "PCI: not setting up bridge %s "
  "for bus %d\n", pci_name(dev), b->number);
  break;
  }
  }
  }
  处理完pbus_assign_resources_sorted()之后,遍历总线下面的pci-pci bridge(dev->subordinate不为空).对每一条下层总线,都递归调用pci_bus_assign_resources().
  Switch后面的部份,是处理完下层总线,回到本层的处理了.忽略对cardbus的处理.看到了pci_setup_bridge().
  我们在前面已经分析到.对于修正过后的处理,只会更新pci device的寄存器信息,而不会更新pci bus中的寄存器. pci_setup_bridge()就是更新pci bus寄存器信息的.只是不管它之前是不是资源冲突的.所有的pci bus全部都会更新一下.该函数代码比较简单.就不做分析了.
  回到pci_assign_unassigned_resources()中.对每条根总线,我们还要经过pci_enable_bridges()的处理,这个函数是这一节分析的最后一个函数了.
  代码如下:
  void pci_enable_bridges(struct pci_bus *bus)
  {
  struct pci_dev *dev;
  int retval;
  list_for_each_entry(dev, &bus->devices, bus_list) {
  if (dev->subordinate) {
  retval = pci_enable_device(dev);
  pci_set_master(dev);
  pci_enable_bridges(dev->subordinate);
  }
  }
  }
  运行到这里,pci总线和设备都准备好了,现在就可以启用pci-pci bridge了.pci_enable_bridge()做的工作如下:
  1:启用所有pci-pci bridge的所有I/O和内存.
  2:启用pci-pci bridge的中断
  3:设置pci-pci bridge 的电源管理状态
  4:设置pci-pci bridge对总线的控制能力
  在这里要注意了,这个函数只是启用了pci-pci bridge的相关功能.对于一般pci_dev.需要驱动程序在使用的时候启用它们.
  函数比较简单,内部就不加详细分析了. 

猜你喜欢

转载自sfp69sfp.iteye.com/blog/1362005