-
linux
设备驱动之
pci
< br>设备的
I/O
和内存
---------------------------
---------------
Pci
设备的
I/O
和内存是一个比较复杂的问题
.
如下的总线结构
:
在上图的总线结构中
,ethern
et
设备和
pci-pci
bridge
的同类型资源空间必须要是
pci
bus0
的
一个子集
例如
,pci bus 0
的
I/O
端口资源是
0x00CC~0x01CC
. Ethernet
设备的
I/O
范
围的是
0x00CC~0x0xE0.
那么
pci-pci bridge
的
I/O
< br>端口范围就必须要在
0x0xE0~0x01CC
之间<
/p>
.
同样
,SCSI
和
VIDEO
同类型资源必须要是
pci_bus1
的子集
.pci bus1
< br>上有一个
pci
桥
,
对应的
资源也就是它所连桥上的资源
.
即
pci_bus-
>
s
elf.
也就是说,下层总线的资源是它上层总线资源的子集。上层总线资源是下层总
线资源的父集。
其实
,
每个
PCI
设备的资源地始地址都是由操作系统设置
的
.
在
x86
上
,
都由
bios
设置好了
.
假若
没有
bios
的时候
,
我们应该
怎么去设置设备的资源起始范围呢
?
可能在
< br>pci
枚举完成之后
:
1:<
/p>
从根总线开始
,
设置根总线的资源范围是
从
0
开始
,
到
0xFFFF
或者
0xFFFFFFF
F
的最大范围
.
2:
对其它的设备
,
可往其资源寄存器全部写入
1,
就可以求得该资源项的类型和长度
.
3:
设备从根总线的资源那里分得对应长度的资源
.
4:
如果设备是
pci-
pci bridge,
则递归配置它
.
可能有人会有这样迷惑
,
对应于上图
,
如果
pc
i-pci bridge
的资源大小是
N.
< br>而
SCSI
和
video
资
源范围超过了
N
怎
么办呢
?
我们必须要注意一点
,
p>
总线的区间是可以自已设定的
,
而设备资源
的区间是在设计的时候就已经
确定好了
.
也就是说
,
我们可以更改
pci d
evice
区间的起始地址
,
但我们不
能改变它的大小
.
因此
,
出现了上面所说的这种情况
.
可能是由
bios
在处理
PCI
的
时候出现了
BUG.
我们需要调整
总线
的资源区间
.
其实对于
pci_bus
的资源范围就是它的过滤窗口
.
对于过滤窗口的作用
,
我们在枚举的时
候分析
的很清楚了
.
CPU
访问
PC
过程是这
样的
(
只有一个根总线和
pci-
pci bridge
过滤窗口功能打开的情况
):
1:cpu
向
pci
发
出一个
I/O
请求
.
< br>首先经过根总线
.
它会判断是否在它的资源范围内
.
如果在它的
范围
,
它就会丢向总线所在的每一个设备
.
包
括
pci bridge.
如果没有在根总线的资源范围
p>
,
则不
会处理这个请求
.
2:
如果
pci
设备判断该地址属于它的资源范围
,
则处理后发出应
答
4:pci bridge
接收到
这个请求
,
它会判断
I/O
地址是否在它的资源范围内
.
如果在它的范围
p>
,
它会
把它丢到它的下层子线
.
5:
下层总线经过经过相同的处理后
,
就会找到这个
PCI
设
备了
一个
PCI
设备访问其它
PCI
设备或者
其它外设的过程
:
1:
首先这个
p>
PCI
发出一个请求
,
这个请求会在总线上广播
2:
如
果要请求的设备是在同级总线
,
就会产生应答
< br>
3:
请求的设备不是在同层总线
,
就会进行
pci
桥判断该请求不
在它的范围内
(
目的地
不是它下层的设
备
),
就会将它丢向上层
.
4:
这样辗转之后
,
就
能找到对应的设备了
经过这样的
分析过来
,
相信对
pci
bridge
的过滤窗口有更深的理解了
.
Linux
中使用
struct re
source
的结构来表示
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
和内存两种
:
< br>用来表示资源的所属关系。分别表示它的父结点,兄弟结点和子结点。
从前面的分析可以看到,有一些总线可能
< br>bios
没有遍历到或许
bios
的处理有错误
,
所以需要对
整个系统
的
PCI
总线和
PCI
设备的资源占用情况遍历一次。
完整的建立上述的
st
ruct resource
结构
(
在
之前枚举的时候,只是处理了
start
和
end
成员
).
。这个过程是在<
/p>
pcibios_resource_survey(
)
完成的。如下所示:
subsys_initcall(pcibios_init);
static int __init
pcibios_init(void)
{
……
…….
pcibios_resource_survey();
}
pcibios_init<
/p>
这个函数是被
fs_initcall()
所描述的。在
kernel
启动的时候,会调用宏所描述的<
/p>
函数。在
pcibios_init
(
)又会调用
pcibios_assign_resources
(),它的代码如下所示:
void __init
pcibios_resource_survey(void)
{
DBG(
pcib
ios_allocate_bus_resources(&pci_root_buses);
pcibios_allocate_resources(0);
pcibios_allocate_resources(1);
}
它先对总线的资源进行处理。然后再对
PCI
设备的资源进行处理。我们先看
pcibios_a
llocate_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
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_RESO
URCES.
对于它的每个
资源区间。都要从它的上层总线中获
得
.
代码中遍历
PCI
桥的每一个资源区间,然后找到它在上
层总线的对应区间。然后为它建立结构关
系。
pci_find_parent_resource<
/p>
()就是为
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 ->bu
s
就找到了它的上层总线,
每条总线拥有
PCI_BUS_NUM_RESOURCES
个资源区间
.
所要寻找的父结点必须要满足以后几个条件:
1
:子结点的范围必须要落在父结点的区间范围内
<
/p>
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_reso
urce
()代码如下:
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()
中
,如果它的资源分配过程失败,它会怎么处理
呢?看下面的代码片段:
< br>
static void __init
pcibios_allocate_bus_resources(struct list_head
*bus_list)
{
……
……
if
(!r-
>
start || !pr ||
request_resource(pr, r) <
0) {
printk(KERN_ERR
idx, pci_name(dev));
r->
flags =
0;
}
……
……
}
也
就是说,如果分配失败了,它会将资源的
flags
标志置为<
/p>
0
回到
p
cibios_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(
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
idx, pci_name(dev));
/* We'll assign a new address later */
r->
end -=
r-
>
start;
r->
start =
0;
}
}
}
if (!pass) {
//
对于
ROM
。在第一次扫描时就将它关闭
r =
&dev-
>resourc
e[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_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
区间关闭。要等到使用设备
的时候再把区间打开。这个
打开的过程一般在设备驱动程序里完成。
到这里,
我们终于知道为什么要用不同的参数调用函数二
次。
这样做是为了优先让已经被启用的
资源从父节点中分得资源
。
如果资源分配失败了。就会将相应资源的
< br>start
设为
0
。
End
成员则设为这个区间的长度。
有必要提一下
linux2.4.
12
中这部份的处理
,
它是这样子的<
/p>
:
对于
pci bus
的资源分配
,
如果分配失败
.
不做任何事情
,
只是打印出一条警告语
句
.
而在
linux2.6.25<
/p>
中
.
如果
pci
bus
资源分配失败
,
会将资源的
p>
flag
置为
0.
为什么
2.6
要这样做呢
?
其实
2.4.12
中的处理是一个
BUG.
如果
pci bus
资源分配失败没有任何的处理的话
.
那接下来为
该总线下面的
pci device
分配资源的时
候
,
是从该总线的资源中请求的
.
p>
而事实上
,
该总线的资源
< br>又是有冲突的
.
这样也造成了该总线下层设备的资源无效
.
而在
linux 2.6
中
.
将分配失败总线的
flag
置为了
0.
下层设备在请求资
源的分配的时候
,
就会类
型匹配失败<
/p>
,
而避免了上面说的这个部问题
.
p>
这个
BUG
不知道在后面的版本中有没有被
修正
:-)
< br>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-
>resourc
e[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_r
esource_survey
()中没有处理
ROM
的区间。在这里,先遍历每个设备,
处理一下它的
ROM
空间的资源分配。照以前的方式一样,如果资源分配失败,就让它的
start
置为
0
。
p>
End
置为区间的长度。
处理完之后,
进入到
pci_assign_unas
signed_resources()
。
我们希望每一个
p>
PCI
设备,
bios
都为我们处理好了。
可是总是事与愿违。
进行的这里,
p>
不得不处理一下之前资源分配失败的设备
了。代码如下:
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
()处理下层
p
ci bus.
对于每一条不是根总线的
pci bus
都会经过大循
环后面的处理,即对应于
cas
e PCI_CLASS_BRIDGE_PCI
后面的处理
.
它要经过的第一个函
数是
pci_br
idge_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;