关键词不能为空

当前您在: 主页 > 英语 >

linux设备驱动之pci设备的IO和内存

作者:高考题库网
来源:https://www.bjmy2z.cn/gaokao
2021-02-19 14:55
tags:

-

2021年2月19日发(作者:伐树)


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



,

< p>
对应的


资源也就是它所连桥上的资源


.

< p>


pci_bus-


>


s elf.


也就是说,下层总线的资源是它上层总线资源的子集。上层总线资源是下层总 线资源的父集。



其实


,


每个


PCI


设备的资源地始地址都是由操作系统设置 的


.



x86



,


都由


bios

设置好了


.


假若


没有


bios


的时候


,


我们应该 怎么去设置设备的资源起始范围呢


?


可能在

< br>pci


枚举完成之后


:


1:< /p>


从根总线开始


,


设置根总线的资源范围是 从


0


开始


,



0xFFFF


或者


0xFFFFFFF F


的最大范围


.


2:


对其它的设备


,


可往其资源寄存器全部写入

< p>
1,


就可以求得该资源项的类型和长度


.


3:


设备从根总线的资源那里分得对应长度的资源

.


4:


如果设备是


pci- pci bridge,


则递归配置它


.



可能有人会有这样迷惑


,


对应于上图


,


如果


pc i-pci bridge


的资源大小是


N.

< br>而


SCSI



video



源范围超过了


N


怎 么办呢


?


我们必须要注意一点


,


总线的区间是可以自已设定的


,


而设备资源 的区间是在设计的时候就已经


确定好了


.


也就是说


,


我们可以更改


pci d evice


区间的起始地址


,


但我们不 能改变它的大小


.


因此


,

< p>
出现了上面所说的这种情况


.


可能是由

< p>
bios


在处理


PCI


的 时候出现了


BUG.


我们需要调整


总线 的资源区间


.



其实对于


pci_bus


的资源范围就是它的过滤窗口


.


对于过滤窗口的作用


,


我们在枚举的时 候分析


的很清楚了


.


< p>
CPU


访问


PC


过程是这 样的


(


只有一个根总线和


pci- pci bridge


过滤窗口功能打开的情况


):


1:cpu



pci


发 出一个


I/O


请求


.

< br>首先经过根总线


.


它会判断是否在它的资源范围内


.


如果在它的


范围


,


它就会丢向总线所在的每一个设备


.


包 括


pci bridge.


如果没有在根总线的资源范围


,


则不


会处理这个请求

.


2:


如果


pci


设备判断该地址属于它的资源范围


,


则处理后发出应 答



4:pci bridge


接收到 这个请求


,


它会判断


I/O

< p>
地址是否在它的资源范围内


.


如果在它的范围


,


它会


把它丢到它的下层子线


.


5:


下层总线经过经过相同的处理后

< p>
,


就会找到这个


PCI


设 备了




一个


PCI


设备访问其它


PCI


设备或者 其它外设的过程


:


1:


首先这个


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

< p>
桥的资源


.


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;



}


}


这个函数的逻辑比较简单。


即在它父节点的子节点中找个合适的 位置插下去。


父结点的子结点都


是按照起始资源地址从小到大的 顺序排列的。




返回到

< p>
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

< p>
设备。如果该设备的相应空间已经启用了(


I/O


或者内存)。那在以


0


为参


数调用的时 候就让它分配好资源。对于没有被启用的资源。要等到第二次以


1


为参数调用参数


时才会处理。



另外 :在第一次处理中就把设备的


ROM


区间关闭。要等到使用设备 的时候再把区间打开。这个


打开的过程一般在设备驱动程序里完成。


到这里,


我们终于知道为什么要用不同的参数调用函数二 次。


这样做是为了优先让已经被启用的


资源从父节点中分得资源 。



如果资源分配失败了。就会将相应资源的

< br>start


设为


0


< p>
End


成员则设为这个区间的长度。




有必要提一下


linux2.4. 12


中这部份的处理


,


它是这样子的< /p>


:


对于


pci bus


的资源分配


,


如果分配失败


.


不做任何事情


,


只是打印出一条警告语 句


.


而在


linux2.6.25< /p>



.


如果


pci bus


资源分配失败


,


会将资源的


flag


置为


0.


为什么


2.6


要这样做呢


?


其实


2.4.12


中的处理是一个

< p>
BUG.


如果


pci bus

资源分配失败没有任何的处理的话


.


那接下来为

< p>
该总线下面的


pci device


分配资源的时 候


,


是从该总线的资源中请求的


.


而事实上


,


该总线的资源

< br>又是有冲突的


.


这样也造成了该总线下层设备的资源无效


.


而在


linux 2.6



.


将分配失败总线的


flag


置为了


0.


下层设备在请求资 源的分配的时候


,


就会类


型匹配失败< /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


空间的资源分配。照以前的方式一样,如果资源分配失败,就让它的

< p>
start


置为


0



End


置为区间的长度。



处理完之后,


进入到


pci_assign_unas signed_resources()



我们希望每一个


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;



-


-


-


-


-


-


-


-



本文更新与2021-02-19 14:55,由作者提供,不代表本网站立场,转载请注明出处:https://www.bjmy2z.cn/gaokao/668021.html

linux设备驱动之pci设备的IO和内存的相关文章