linux设备驱动之pci设备的IO和内存
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
linux设备驱动之pci设备的I/O和内存
------------------------------------------
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 *nam e;
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 < PCI_NUM_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) < 0) {
printk(KERN_ERR "PCI: Cannot allocate "
"resource region %d "
"of bridge %s\n",
idx, pci_name(dev));
/*
* Som ething 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 < PCI_BUS_NUM_RESOURCES; i++) {
struct resource *r = bus->resource[i];
if (!r)
continue;
if (res->start && !(res->start >= r->start && res->end <= r->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 m atch */
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 *tm p, **p;
if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child;
for (;;) {
tmp = *p;
if (!tm p || tm p->start > end) {
new->sibling = t m p;
*p = new;
new->parent = root;
return NULL;
}
p = &t mp->sibling;
if (tmp->end < start)
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) < 0) {
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 < PCI_ROM_RESOURCE; 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) < 0) {
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_nam e(dev));
r->flags &= ~IORESOURCE_ROM_ENABLE;
pci_read_config_dword(dev,
dev->rom_base_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) < 0) {
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 >> 8) {
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 >> 8) {
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 m emory
resources. */
m ask = IORESOURCE_MEM;
prefm ask = IORESOURCE_MEM | IORESOURCE_PREFETCH;
if (pbus_size_mem(bus, prefmask, prefmask))
m ask = 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 (!pm em) {
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 (pm em)
b_res[2].flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH;
}
Pci bridge设备的7,8,9项是属于过滤窗口,也就是对应于总线的资源。
在这里,检查pci bus是否支持I/O和prefetch m emory类型的窗口.如果不支持,则相应寄存器是只读的,而且值为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 < PCI_NUM_RESOURCES; i++) {
struct resource *r = &dev->resource[i];
unsigned long r_size;
if (r->parent || !(r->flags & IORESOURCE_IO))
continue;
r_size = r->end - r->start + 1;
if (r_size < 0x400)
/* 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) << 2);
#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_m ask = IORESOURCE_IO | IORESOURCE_MEM |
IORESOURCE_PREFETCH;
for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) {
r = bus->resource[i];
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_m em()类似于pbus_size_io(),只是它是用来查找memory类型资源的长度.在代码中,以不同的参数两次调用了这个函数,分别是用来查找带预读的存储区间和一般的存储区间.
pbus_size_m em()和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 >> 8) {
case PCI_CLASS_BRIDGE_PCI:
pci_setup_bridge(b);
break;
case PCI_CLASS_BRIDGE_CARDBUS:
pci_setup_cardbus(b);
break;
default:
print k(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 ioapic s. */
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(t m p);
}
}
首先,它将有总线下有资源冲突的设备加到链表中,然后遍历这个链表,处理每个资源冲突的设备. 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 < PCI_BRIDGE_RESOURCES) ? size : res->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 < 0 && (res->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_nam e(dev));
} else if (resno < PCI_BRIDGE_RESOURCES) {
//如果冲突修正成功
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_m ask,
void (*alignf)(void *, struct resource *, resource_size_t,
resource_size_t),
void *alignf_data)
{
int i, ret = -ENOMEM;
type_m ask |= IORESOURCE_IO | IORESOURCE_MEM;
for (i = 0; i < PCI_BUS_NUM_RESOURCES; i++) {
struct resource *r = bus->resource[i];
if (!r)
continue;
/* type_m ask must m atch */
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 ? : m in,
-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 m in,
resource_size_t m ax, 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, m in, m ax, 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 m ax, 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 < min)
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 < new->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 < 6) {
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
} else if (resno == PCI_ROM_RESOURCE) {
if (!(res->flags & IORESOURCE_ROM_ENA BLE))
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 >> 8) {
case PCI_CLASS_BRIDGE_PCI:
pci_setup_bridge(b);
break;
case PCI_CLASS_BRIDGE_CARDBUS:
pci_setup_cardbus(b);
break;
default:
print k(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_m aster(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.需要驱动程序在使用的时候启用它们.
函数比较简单,内部就不加详细分析了.。