操作系统在启动时会扫描I/O设备,以确定设备所使用的I/O端口,内存以及中断号等资源,以提供给设备管理模块如驱动等系统其他部分使用,这个过程就叫做设备初始化。不过在T2上,I/O设备扫描的工作并不是由并不是由处于priviled模式下的Linux来完成的,而是由处于hypervilged模式下的hypervisor来完成的。在系统启动时,OBP会把hypervisor扫描到的设备资源以设备节点树的形式呈现给Linux。因此T2 Linux设备初始化的一个工作是与该这个节点树交互,建立自己的设备资源列表和设备基本结构。
在T2 CPU上集成了PCIe总线中的root complex,因此T2 Linux设备初始化会以这个root complex作为PCIe主控器初始化PCI总线以及该总线上扩出的pci总线及pci设备。
本章的主要内容包括一下三个部分代码的分析:
1,Linux中的设备节点树的建立,它会与OBP设备节点树交互,在Linux中建立一个设备节点树,在之后的初始化过程中,从该节点树中得到设备的资源信息。
2,of虚拟总线的建立,会建立一个虚拟的of总线,作为系统中所有设备挂载的根总线。
3,PCI总线及设备的初始化,会初始化好pci总线及设备的基本结构,并以设备驱动模型的形式组织起来。
Linux中的设备节点树的建立:
代表设备节点树一个节点的数据结构定义如下:
struct device_node {
const char *name;
const char *type;
phandle node;
char *path_component_name;
char *full_name;
truct property *properties;
struct property *deadprops;
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct device_node *next;
struct device_node *allnext;
struct proc_dir_entry *pde;
struct kref kref;
unsigned long _flags;
void *data;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
};
该结构的各成员的含义如下:
Name:该节点的名字,该名字其实就是该节点所代表设备的名字。
Type:该节点所代表设备的设备类型。
Node:该节点的节点号。
path_component_name:该成员是一个组合的名字,它是由name成员与该设备的索引组成的,用来与父节点的名称组合成一个完整的名字,比如系统中第一个pci网卡的名称可能是ethernet,则name成员的即是字符串“name“,而这里的path_component_name成员则是字符串"ethernet@0",不过设备节点树的根节点的path_component_name成员却不适用这一规则,因为根节点并不与一个具体的设备实体相关联,因此它叫什么名字并不重要,根节点的名字直接赋值为NULL。
full_name:全名,即该节点设备所代表设备的完整的名字,它是由父节点的full_name与该节点的path_component_name组成的,比如如果系统中第一个pci网卡的父节点的full_name是"/pci@0/pci@1"的话,该节点的全名就是"/pci@0/pci@1/ethernet@0",设备节点树的根节点的full_name直接赋值为"/"。
Properties:该成员后根的是该节点的属性链表,其中每一个成员都是该节点的一个属性,代表该节点所代表设备所使用的某一种资源的信息。
Deadprops:该成员后跟的是已经删除的属性。
Parent:指向该节点的父节点。
Child:指向该节点的第一个子节点。
Sibling:指向该节点的兄弟节点,它们有一个共同的父节点。
Next:该成员把相同类型设备的节点链接到一个链表中。
Allnext:该成员把设备节点树中的所有节点链接到一个链表中。
这些是一些比较重要的成员,其他的成员就不做介绍了。
每个节点都有很多属性,每一个属性都代表了设备的某一种资源信息,代表节点属性的结构体定义如下:
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
};
其各成员含义如下:
Name:属性的名称。
Length:属性的长度。
Value:属性的值。
Next:该成员把同一节点的所有属性链接到一个链表中。
T2 Linux设备节点树的代码定义在文件arch/sparc/kernel/prom_common.c文件中,它是由函数prom_build_devicetree来完成的,我们来看这个函数:
306 void __init prom_build_devicetree(void)
307 {
308 struct device_node **nextp;
309
310 allnodes = prom_create_node(prom_root_node, NULL);
311 allnodes->path_component_name = "";
312 allnodes->full_name = "/";
313
314 nextp = &allnodes->allnext;
315 allnodes->child = prom_build_tree(allnodes,
316 prom_getchild(allnodes->node),
317 &nextp);
318 of_console_init();
319
320 printk("PROM: Built device tree with %u bytes of memory.\n",
321 prom_early_allocated);
322 }
第310行,allnodes是一个全局变量,它保存了指向设备节点树根节点的指针,通过该变量就能够找到设备节点树,prom_create_node函数会创建一个设备节点树的节点,在这里它是创建设备节点树的根节点,根节点的节点号prom_root_node在很早之前就已经得到了,我们在head_64.S文件的分析中,提到过,你可以在那里去看。我们来看这个函数:
223 static struct device_node * __init prom_create_node(phandle node,
224 struct device_node *parent)
225 {
226 struct device_node *dp;
227
228 if (!node)
229 return NULL;
230
231 dp = prom_early_alloc(sizeof(*dp));
232 dp->unique_id = prom_unique_id++;
233 dp->parent = parent;
234
235 kref_init(&dp->kref);
236
237 dp->name = get_one_property(node, "name");
238 dp->type = get_one_property(node, "device_type");
239 dp->node = node;
240
241 dp->properties = build_prop_list(node);
242
243 irq_trans_init(dp);
244
245 return dp;
246 }
第231行,为该节点分配空间。
第232行,为节点的unique_id成员赋值,该成员可以看作是节点的id号,它实 际上就是按照节点树中所有节点创建的顺序从0开始逐次递加计算出来的一个值。
第233行,使parent成员指向其父节点,父节点的指针是通过参数传进来的,对与根节点来说,该值为空。
第235行,初始化节点的引用计数。
第237行,以该节点的节点号为参数调用get_one_property从OBP的设备节点树中得到节点的name属性,它实际上就是设备的名称,赋值给节点的name成员。
第238行,从OBP的设备节点树,得到设备的设备类型,赋值给节点的device_type成员。
第239行,把节点的节点号赋值给节点的node成员。
第241行,初始化节点的properties,他会从OBP节点树中得到该节点的所有属性,然后链接到properties成员中形成一个链表。build_prop_list函数我们过一会儿分析。
第243行,irq_trans_init函数与中断初始化函数相关,它会为不同类型的设备提过不同的分配中断请求号的函数,参见Linux T2 中断机制那一章。
我们现在再来看刚才提到为节点建立属性链表的函数build_prop_list,当然这个函数也是直接从OBP的设备节点树中取出相应节点的属性加入到这个节点的节点链表中来:
191 static struct property * __init build_prop_list(phandle node)
192 {
193 struct property *head, *tail;
194
195 head = tail = build_one_prop(node, NULL,
196 ".node", &node, sizeof(node));
197
198 tail->next = build_one_prop(node, NULL, NULL, NULL, 0);
199 tail = tail->next;
200 while(tail) {
201 tail->next = build_one_prop(node, tail->name,
202 NULL, NULL, 0);
203 tail = tail->next;
204 }
205
206 return head;
207 }
第195行,调用build_one_prop函数建立节点的属性链表的第一个属性,实际上这个属性并不是从OBP的节点的属性中得到的,而是直接把该节点的节点号作为该节点的第一个属性。
第198行,调用build_one_prop函数建立节点属性链表的第二个属性,这个属性是从OBP中的到的节点的第一个属性。
第200到204行的循环,一直调用build_one_prop从OBP中得到该节点的属性,直到得到所有的属性,这时就把该节点的属性链表完全建立起来了。
第206行,返回指向属性链表的指针。
现在我们看build_one_prop函数,它会为节点建立一个属性,它有5个参数,其中第一个参数是节点的节点号,第是节点的前一个属性的名称,利用该参数可以在OBP节点的属性中得到其下一个属性,第3个参数是一个指定的名称,如果该参数不为空,则build_one_prop不会从OBP节点树中得到该节点的一个属性,而是直接以该参数为名称建立一个属性,它会同后面的两个参数一起共同建立一个属性,第4个参数是,属性的属性值,第5个参数,属性值的长度。我们来看这个函数:
136 static struct property * __init build_one_prop(phandle node, char *prev,
137 char *special_name,
138 void *special_val,
139 int special_len)
140 {
141 static struct property *tmp = NULL;
142 struct property *p;
143 const char *name;
144
145 if (tmp) {
146 p = tmp;
147 memset(p, 0, sizeof(*p) + 32);
148 tmp = NULL;
149 } else {
150 p = prom_early_alloc(sizeof(struct property) + 32);
151 p->unique_id = prom_unique_id++;
152 }
153
154 p->name = (char *) (p + 1);
155 if (special_name) {
156 strcpy(p->name, special_name);
157 p->length = special_len;
158 p->value = prom_early_alloc(special_len);
159 memcpy(p->value, special_val, special_len);
160 } else {
161 if (prev == NULL) {
162 name = prom_firstprop(node, p->name);
163 } else {
164 name = prom_nextprop(node, prev, p->name);
165 }
166
167 if (!name || strlen(name) == 0) {
168 tmp = p;
169 return NULL;
170 }
171 #ifdef CONFIG_SPARC32
172 strcpy(p->name, name);
173 #endif
174 p->length = prom_getproplen(node, p->name);
175 if (p->length <= 0) {
176 p->length = 0;
177 } else {
178 int len;
179
180 p->value = prom_early_alloc(p->length + 1);
181 len = prom_getproperty(node, p->name, p->value,
182 p->length);
183 if (len <= 0)
184 p->length = 0;
185 ((unsigned char *)p->value)[p->length] = '\0';
186 }
187 }
188 return p;
189 }
第145到148行,这是一个if语句,tmp是指向属性结构的指针,它是一个静态变量。从名字就可以看出它是一个临时变量,这个临时变量是做什么用呢?在建立一个属性的时候需要为其分配属性结构的空间,但是在获取一个属性的时候可能会出现获取失败的情况,比如已经获取到一个节点的最后一个属性,再获取其下一个属性就会获取失败了,出现这种情况时一般的做法是把之前分配的属性结构的空间释放掉,但是这里没有这样,这里是把之前分配的空间保存到一个静态临时变量中,以备下一次建立属性时使用,这个临时变量就是这里的tmp变量。如果tmp不为空,则说明在前一次建立属性时存在一个未被分配的空间
,直接把该空间清空,用它来建立新属性。
第150,151行,如果tmp不为空,则要为要新建立的属性分配空间。可以看出这里不仅为属性结构分配了空间,而且还多分配了32个字节的空间,这32个字节是用来保存属性的名称的。第151行还初始化了属性结够的unique_id成员。
第154行,使属性结构的name成员指向多分配的那32个字节的空间的首地址。
第155到159行,如果special_name不为空,则不从OBP中得到节点属性,而是直接使用第3,4,5个参数建立属性,这几条语句把这几个参数分别赋值给属性结构的各成员。其中属性值的空间是新分配的。
否则,如果special_name为空,则需要从从OBP中获取节点属性了。
第161,162行,如果prev参数为空,则说明这里是为一个节点获得在OBP节点树中的第一个属性,调用prom_firstprop函数与获得第一个属性的名称。
prom_firstprop实际上是调用OBP服务接口,这里就不分析了。
第164行,如果prev参数不为空,则以前一个属性的名称为参数调用prom_nextprop函数得到节点的下一个属性的名称。prom_nextprop也是调用OBP服务接口,这里也不分析了。
第167到170行,如果前面获取属性名称失败,则把分配的空间保存到tmp中,然后返回退出。
第174行,以属性名称为参数调用prom_getproplen函数获取属性值的长度,prom_getproplen函数也是调OBP服务接口。
第175,176行,如果属性长度小于或等于0,则把属性长度置为0,返回新建立的属性的指针。
否则,
第180行,为属性值分配空间。
第181行,调用prom_getproperty函数从OBP中获取该属性的属性值。
第184到186行,进行一些错误处理,就不管了。
最后返回新建立的属性的指针。
我们回到prom_build_devicetree函数中来,
在第310行的语句中已经为设备节点树的根节点建立了节点实体,它的节点号为prom_root_node,父节点为NULL。
第311行,把根节点的path_component_name成员置为空,上文中提过。
第312行,把根节点的full_name置为"/"。
第314行,把根节点的allnext保存到nextp中,节点的该成员会把所有的节点链接到一个链表中来。
第315行,调用prom_build_tree函数建立设备节点树,它的第一个参数是指向一个要建立的节点的父节点的指针。第二个参数是父亲节点的第一个子节点的节点号,也就是要建立的节点的节点号,最后一个参数是nextp。prom_build_tree是一个递归函数,它是按照深度遍历的顺序递归的建立设备节点树的节点的。即当一个节点的所有的子节点都已经建立时才去接着建立下一个兄弟节点。
第275行,while循环,直到所有的设备节点树的所有节点都已经建立,才退出循环。
第276行,调用prom_create_node创建一个节点,这个函数在上文中分析过,这里建立的节点即有可能是一个子节点,也有可能是一个兄弟节点。
第280,281行,prev_sibling变量指向该节点的前一个兄弟节点,一个父亲节点的第一个子节点是没有前一个兄弟节点的,这里如果有前一个兄弟节点,则使前一个兄弟节点的prev_sibling成员指向该节点,利用该成员可以把一个父节点的所有兄弟节点链接到一个链表中来。
第283,284行,ret变量是prom_build_tree函数的返回值,它返回的实际上是第一个子节点的指针,这里使它指向第一个子节点。
第285行,为prev_sibling变量赋值,以使建立下一个兄弟节点时可以把下一个兄弟节点链接到兄弟节点链表中去。
第287,288行,利用节点的allnext成员把节点链接到所有节点的链表中来。
第290行,为节点的path_component_name赋值,build_path_component函数就不分析了,有兴趣的读者可自行分析。
第291行,为节点的full_name成员赋值。
第293行,递归调用prom_build_tree建立字节点,这个递归调用会建立该节点所有的子节点。
第298行,得到其兄弟节点的节点号,以在下一次循环中该兄弟节点及该兄弟节点的所有子节点。
第310行,返回父节点的第一个子节点的指针。
当prom_build_tree函数执行玩后,设备节点树就建立起来了。
of虚拟总线的建立:
在Linux 2.6的内核中引入了设备驱动模型,在设备驱动模型中,所有的设备都被认为是挂载在某一个"总线"上,"总线"可以是实 际的物理总线,也可以是一个虚拟总线。对于pci设备等挂载在一条实际物理总线上的设备而言,在设备驱动模型中有一个与该实际总线相对应的"总线"实例,但是对于PCIe root complex等并没有挂载在一条实际物理总线上的设备,为了使用设备驱动模型,Linux在设备驱动模型为这种设备提过了一些虚拟的总线,如platform总线和of总线,T2 Linux使用了of总线。Of总线的大部分代码是Linux提供的体系结构无关的公共代码,这部分代码的实现分析我们这里不会涉及,我们这里要分析的T2 Linux如何使用of虚拟总线,在这个过程中,会调用大量的of总线的公共代码,在代码分析中遇到这些公共代码时都会一笔带过,不会深入分析。因此从本文中你无法获得of总线的实现及工作原理,有兴趣的读者可以查阅其他资料。
T2 Linux使用of总线时并不仅仅是只把没有挂载在实际总线上的设备挂载在of总线上来,它会把设备节点树中的所有设备都挂载到of总线上来,这样做的好处是接下来初始化pci设备等其他设备时不许要从设备节点树中查找其资源信息,只需要使用Linux公共代码从of总线里查找就行了。
T2 Linux把设备节点树里的设备挂载到of总线中的代码定义在arch/sparc/kernel/of_device_64.c文件中。
我们从其初始化函数开始:
715 static int __init of_bus_driver_init(void)
716 {
717 int err;
718
719 err = of_bus_type_init(&of_platform_bus_type, "of");
720 if (!err)
721 scan_of_devices();
722
723 return err;
724 }
725
726 postcore_initcall(of_bus_driver_init);
第719行,of_bus_type_init函数是of公共代码提过的函数,它会初始化一个of总线实体,初始化的总线的名字叫做"of",of_platform_bus_type定义于arch/sparc/kernel/of_device_common.c文件中,定义如下:
struct bus_type of_platform_bus_type;
第722行,scan_of_devices函数会扫描设备节点树,把设备节点树中的所有设备都挂载到of总线上来。我们来看这个函数:
703 static void __init scan_of_devices(void)
704 {
705 struct device_node *root = of_find_node_by_path("/");
706 struct of_device *parent;
707
708 parent = scan_one_device(root, NULL);
709 if (!parent)
710 return;
711
712 scan_tree(root->child, &parent->dev);
713 }
第705行,of_find_node_by_path会根据设备节点树中节点的路径(也就是节点的full_name)查找某个节点,返回指向该节点的指针,这里是查找设备节点树的根节点。of_find_node_by_path是of代码提过的公共函数。
第708行,scan_one_devic函数扫描一个设备,该函数的实际作用就是把一个设备挂载到of总线上来,这里是把设备节点树的根设备挂载到of总线上来,我们来看scan_one_devic函数:
631 static struct of_device * __init scan_one_device(struct device_node *dp,
632 struct device *parent)
633 {
634 struct of_device *op = kzalloc(sizeof(*op), GFP_KERNEL);
635 const unsigned int *irq;
636 struct dev_archdata *sd;
637 int len, i;
638
639 if (!op)
640 return NULL;
641
642 sd = &op->dev.archdata;
643 sd->prom_node = dp;
644 sd->op = op;
645
646 op->node = dp;
647
648 op->clock_freq = of_getintprop_default(dp, "clock-frequency",
649 (25*1000*1000));
650 op->portid = of_getintprop_default(dp, "upa-portid", -1);
651 if (op->portid == -1)
652 op->portid = of_getintprop_default(dp, "portid", -1);
653
654 irq = of_get_property(dp, "interrupts", &len);
655 if (irq) {
656 op->num_irqs = len / 4;
657
658 /* Prevent overrunning the op->irqs[] array. */
659 if (op->num_irqs > PROMINTR_MAX) {
660 printk(KERN_WARNING "%s: Too many irqs (%d), "
661 "limiting to %d.\n",
662 dp->full_name, op->num_irqs, PROMINTR_MAX);
663 op->num_irqs = PROMINTR_MAX;
664 }
665 memcpy(op->irqs, irq, op->num_irqs * 4);
666 } else {
667 op->num_irqs = 0;
668 }
669
670 build_device_resources(op, parent);
671 for (i = 0; i < op->num_irqs; i++)
672 op->irqs[i] = build_one_device_irq(op, parent, op->irqs[i]);
673
674 op->dev.parent = parent;
675 op->dev.bus = &of_platform_bus_type;
676 if (!parent)
677 dev_set_name(&op->dev, "root");
678 else
679 dev_set_name(&op->dev, "%08x", dp->node);
680
681 if (of_device_register(op)) {
682 printk("%s: Could not register of device.\n",
683 dp->full_name);
684 kfree(op);
685 op = NULL;
686 }
687
688 return op;
689 }
第634行,of总线上挂载的设备都是以struct of_device结构的形式挂载的,因此对于设备节点树中的所有设备,都会先将其转换为struct of_device结构,这里是为struct of_device结构分配空间。
第642行,使sd指向struct of_device的dev成员的archdata成员,该成员是用来保存设备的平台相关信息的。当然这个结构的定义也是平台相关的,在sparc平台,它的定义如下:
struct dev_archdata {
void *iommu;
void *stc;
void *host_controller;
struct device_node *prom_node;
struct of_device *op;
int numa_node;
};
其他的成员我们现在不要关心,只关系其prom_node成员和op成员,prom_node成员保存该设备的设备节点树中节点的指针,op成员保存设备的struct of_device结构的指针。
第643行,archdata的prom_node成员指向设备的设备节点。
第644行,archdata的op成员执行设备的struct of_device结构。
第646行,使设备的struct of_device结构的node成员指向设备的设备节点。
第648行,使struct of_device结构的clock_freq成员赋值为设备节点的
clock-frequency属性,该属性反映了设备的工作频率,对于根节点,它实际上是cpu的频率,当然有些设备是没有该属性的。of_getintprop_default函数会到设备节点的属性链表中查找,找到名称与传入参数匹配的属性,然后返回其属性值,这里就不分析了。
第650到652行,得到节点的upa-portid或者portid属性,赋值给struct of_device结构的portid成员。
第654行,得到节点的interrupts属性,实际上其返回值是一个指向存放其使用中断资源的内存快,其中每个中断资源占4个字节,这里的中断资源实际上就是devino(参见Linux T2 中断机制那一章)。
第655行,如果能够获取到中断资源,则:
第656行,计算出使用的中断资源的数量,保存到struct of_device结构的num_irqs。
第659到664行,如果中断资源的数目超过了限定的最大值,则把num_irqs成员置为限定的最大值。
第665行,把设备的中断资源保存到struct of_device结构的irqs成员中。
第666,667行,如果没有获取的设备的中断资源,则把num_irqs成员置为0。
第670行,对于根节点来说,build_device_resources函数什么都不做,但是对于其他节点来说,该函数会从设备节点中取出一些资源信息,保存到struct of_device结构中来,这个函数我们后面再分析。
第671,672行,为设备使用的每一个中断资源,调用build_one_device_irq函数为其分配中断请求号。这个函数在Linux T2 中断机制那一章中提到过,该函数就不仔细分析了,有兴趣的读者可自行分析。
第674行,为struct of_device结构的dev成员的parent成员赋值,对于根节点来说,该值赋值为空。
第675行,为struct of_device结构的dev成员的parent成员赋值,赋值为我们使用的of总线的描述符。
第676,677行,如果是根节点则为struct of_device结构的dev成员的name成员赋值为名称"root"。
第678,679行,否则为struct of_device结构的dev成员的name成员赋值为设备节点树中节点的名称。
第681行,of_device_register是公共代码,它会把struct of_device结构挂载到of总线上去。
第688行,返回为该设备实现的struct of_device结构实体的指针。
现在回到scan_of_devices函数中来。
第712行,扫描完根节点后,scan_tree函数会扫描设备节点树中的所有节点,然后把节点所代表设备都挂载到of总线上来,这样,整个of总线结构就建立起来了。我们来看这个函数:
691 static void __init scan_tree(struct device_node *dp, struct device *parent)
692 {
693 while (dp) {
694 struct of_device *op = scan_one_device(dp, parent);
695
696 if (op)
697 scan_tree(dp->child, &op->dev);
698
699 dp = dp->sibling;
700 }
701 }
可以看出,这个函数的核心是前面分析过的scan_one_device函数,该函数会按照深度遍历的顺序遍历节点树中的每个节点,然后为每个节点调用scan_one_device函数,scan_one_device函数会从节点树中取出资源信息,然后把设备挂载到of总线上去。
在scan_one_device函数中,从设备节点中取出资源信息的函数为build_device_resources,在分析scan_one_device函数时这个函数没有分析,现在来分析这个函数:
313 static void __init build_device_resources(struct of_device *op,
314 struct device *parent)
315 {
316 struct of_device *p_op;
317 struct of_bus *bus;
318 int na, ns;
319 int index, num_reg;
320 const void *preg;
321
322 if (!parent)
323 return;
324
325 p_op = to_of_device(parent);
326 bus = of_match_bus(p_op->node);
327 bus->count_cells(op->node, &na, &ns);
328
329 preg = of_get_property(op->node, bus->addr_prop_name, &num_reg);
330 if (!preg || num_reg == 0)
331 return;
332
333 /* Convert to num-cells. */
334 num_reg /= 4;
335
336 /* Convert to num-entries. */
337 num_reg /= na + ns;
338
339 /* Prevent overrunning the op->resources[] array. */
340 if (num_reg > PROMREG_MAX) {
341 printk(KERN_WARNING "%s: Too many regs (%d), "
342 "limiting to %d.\n",
343 op->node->full_name, num_reg, PROMREG_MAX);
344 num_reg = PROMREG_MAX;
345 }
346
347 for (index = 0; index < num_reg; index++) {
348 struct resource *r = &op->resource[index];
349 u32 addr[OF_MAX_ADDR_CELLS];
350 const u32 *reg = (preg + (index * ((na + ns) * 4)));
351 struct device_node *dp = op->node;
352 struct device_node *pp = p_op->node;
353 struct of_bus *pbus, *dbus;
354 u64 size, result = OF_BAD_ADDR;
355 unsigned long flags;
356 int dna, dns;
357 int pna, pns;
358
359 size = of_read_addr(reg + na, ns);
360 memcpy(addr, reg, na * 4);
361
362 flags = bus->get_flags(addr, 0);
363
364 if (use_1to1_mapping(pp)) {
365 result = of_read_addr(addr, na);
366 goto build_res;
367 }
368
369 dna = na;
370 dns = ns;
371 dbus = bus;
372
373 while (1) {
374 dp = pp;
375 pp = dp->parent;
376 if (!pp) {
377 result = of_read_addr(addr, dna);
378 break;
379 }
380
381 pbus = of_match_bus(pp);
382 pbus->count_cells(dp, &pna, &pns);
383
384 if (build_one_resource(dp, dbus, pbus, addr,
385 dna, dns, pna))
386 break;
387
388 flags = pbus->get_flags(addr, flags);
389
390 dna = pna;
391 dns = pns;
392 dbus = pbus;
393 }
394
395 build_res:
396 memset(r, 0, sizeof(*r));
397
398 if (of_resource_verbose)
399 printk("%s reg[%d] -> %llx\n",
400 op->node->full_name, index,
401 result);
402
403 if (result != OF_BAD_ADDR) {
404 if (tlb_type == hypervisor)
405 result &= 0x0fffffffffffffffUL;
406
407 r->start = result;
408 r->end = result + size - 1;
409 r->flags = flags;
410 }
411 r->name = op->node->name;
412 }
413 }
第325行,得到父亲节点的struct of_device 结构。
第326行,of_match_bus函数会找到父设备的所属的总线类型,注意这里的总线类型并不是指设备驱动模型里面的"总线",而是指物理总线,比如一个pci设备对应的就是pci总线。T2 linux定义了一个所需匹配的总线的数组,这些总线都是T2上可能使用的总线:
static struct of_bus of_busses[] = {
/* PCI */
{
.name = "pci",
.addr_prop_name = "assigned-addresses",
.match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map,
.get_flags = of_bus_pci_get_flags,
},
/* SIMBA */
{
.name = "simba",
.addr_prop_name = "assigned-addresses",
.match = of_bus_simba_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_simba_map,
.get_flags = of_bus_pci_get_flags,
},
/* SBUS */
{
.name = "sbus",
.addr_prop_name = "reg",
.match = of_bus_sbus_match,
.count_cells = of_bus_sbus_count_cells,
.map = of_bus_default_map,
.get_flags = of_bus_default_get_flags,
},
/* FHC */
{
.name = "fhc",
.addr_prop_name = "reg",
.match = of_bus_fhc_match,
.count_cells = of_bus_fhc_count_cells,
.map = of_bus_default_map,
.get_flags = of_bus_default_get_flags,
},
/* Default */
{
.name = "default",
.addr_prop_name = "reg",
.match = NULL,
.count_cells = of_bus_default_count_cells,
.map = of_bus_default_map,
.get_flags = of_bus_default_get_flags,
},
};
of_match_bus函数实际上就是遍历这个数组,调用每个成员的match函数,如果match函数匹配成功,则返回1,当然如果没有总线匹配成功,最终会返回最后一个成员,即default成员。我们只看其中的pci总线的匹配函数,因为我们系统中大部分设备都是pci设备。
44 static int of_bus_pci_match(struct device_node *np)
45 {
46 if (!strcmp(np->name, "pci")) {
47 const char *model = of_get_property(np, "model", NULL);
48
49 if (model && !strcmp(model, "SUNW,simba"))
50 return 0;
51
52 /* Do not do PCI specific frobbing if the
53 * PCI bridge lacks a ranges property. We
54 * want to pass it through up to the next
55 * parent as-is, not with the PCI translate
56 * method which chops off the top address cell.
57 */
58 if (!of_find_property(np, "ranges", NULL))
59 return 0;
60
61 return 1;
62 }
63
64 return 0;
65 }
第46行,对于pci设备,其父节点的名称应该是pci,因此这里比较,如果不相等,则直接返回。
第47到50行,得到节点的model属性,应该是"SUNW,simba",否则也直接返回,因为T2中pci总线的根设备是cpu上集成的,其model属性应该是"SUNW,simba"。
第58行,该节点应该有一个ranges属性,否则直接返回
第61行,如果上面的条件都满足,则代表匹配成功,返回1。
现在回到build_device_resources函数,
第327行,如果找到匹配的总线,则为该节点调用该总线类型的count_cells函数,我们还是看pci总线的该函数:
91 static void of_bus_pci_count_cells(struct device_node *np,
92 int *addrc, int *sizec)
93 {
94 if (addrc)
95 *addrc = 3;
96 if (sizec)
97 *sizec = 2;
98 }
可以看出该函数是直接把传入的两个参数分别置为3和2。这两个参数也就是build_device_resources函数的局部变量na和ns,至于这两个变量有什么作用,用到的时候再说。
回到build_device_resources函数中来,
第329行,调用of_get_property结构得到总线类型结构里面指定的属性,对于pci总线来说,该属性是assigned-addresses。当然,该属性可能不止一个属性值,这些属性值顺序存放在一个内存块中,该内存块的长度会保存到传入的第3个参数中。实际上该属性反映的是设备使用的I/O或内存资源的信息。对pci设备来说,每个I/O或内存资源占有20个字节的空间,其中第一个4字节反映是该资源是I/O资源,还是32位或者64位内存资源,第二个和第三个4字节反映的是I/O或内存空间的基地址,第4和第5个4字节空间的信息反映的是I/O或内存资源的长度。
第334到337行,计算出I/O或内存资源的数目,它是把内存块的大小除以20。
第340到345行,判断资源数目是否大于限定的最大值,如果大于限定的最大值,则置为限定的最大值。
第347到413行,这些行就不仔细分析了,它实际上是把I/O或内存资源信息保存到struct of_device结构的resource数组成员中去,struct resource结构有3个成员,分别是I/O或内存基地址,I/O或内存大小,以及反映是I/O资源,32位内存资源还是64位内存资源的标志。注意,这些语句取出资源信息时会先判断该节点的父亲节点中有没有保存相关的资源信息,如果父亲节点有保存,则使用父亲节点保存的资源信息。这些行看着复杂,其实就是因为要从父节点中取资源信息。
到这里of虚拟总线的建立就分析完了。在建立of总线的过程中,把设备节点树中所有的节点所代表设备都挂载到了of总线上,同时在struct of_device结构中保存了设备所使用的中断资源,I/O或内存资源等信息。
PCI总线及设备的初始化: