0、背景
在调试LCD驱动时,查看内核打印信息发现 “failed to get mipi dphy”
定位到是 dw_mipi_dsi_probe 函数中出了问题;
进一步添加打印信息可以定位到问题出现在在函数devm_phy_optional_get中:
该函数返回了个错误值,在IS_ERR中报错;
1、devm_phy_optional_get
该函数的功能是查找并返回一个phy;
dev: 请求该phy的设备;
string: phy的名称
该函数调用devm_phy_get完成上述功能。
2、devm_phy_get
该函数调用phy_get完成主要功能;
3、phy_get
实际上获取phy的函数为_of_phy_get
通过添加打印信息确定of_property_match_string 返回值正确
4、_of_phy_get
添加打印信息定位到问题所在:of_phy_provider_lookup,该函数的返回值为错误值。
5、of_phy_provider_lookup
该函数功能为查找到phy provider并返回。但是发现该函数似乎没有执行
list_for_each_entry 这个循环结构直接返回了错误值。定位问题出现在该循环结构中;
6、list_for_each_entry
定位该函数,发现其是一个for循环;其功能是对链表进行遍历。对该函数的传参进行分析:
pos:是一个指向宿主结构的指针
head:是需要进行遍历的链表的头
member:是list_head 成员在宿主结构中的名字(通常为list)
下面分析它是如何实现对链表的循环:
其中有一个中的函数:list_entry
其实现是由container_of完成;
分析该函数前先分析offstof函数:
功能:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量
一层一层分析:
1、((TYPE *)0) 将零转换为TYPE类型指针
2、((TYPE *)0)->MEMBER 访问TYPE结构体中的名为MEMBER的成员
3、&((TYPE *)0)->MEMBER 取出MEMBER成员的地址,TYPE的地址是0,所以这里获得的地址就是MEMBER在TYPE中的偏移地址。
4、((size_t) &((TYPE *)0)->MEMBER) 类型转换,64为系统size_t 为unsigned long类型。
示例:
struct studen{
char gender;
int id;
int age;
char name[20];
}
int gender_offset = offsetof( struct studen,gender);
int id_offset = offsetof( struct studen,id);
int age_offset = offsetof( struct studen,age);
int name_offset = offsetof( struct studen,name);
综上,当我们知道一个结构体和该结构体中的一个成员后,可以通过offsetof 计算该成员在结构体中的偏移量。
回到container_of 的分析中:
功能:根据结构体(type)中的成员(member)的指针(ptr)来获取指向整个结构体的指针。
1、typeof( ((type *)0)->member ) 获得结构体(type)中成员(member)的类型
2、const typeof( ((type *)0)->member ) *__mptr = (ptr) 定义了一个_mptr指针,并将ptr赋值给了_mptr,这样_mptr就一个member数据类型的指针,其指向ptr所指向的地址。
3、(char *)__mptr 将__mptr 转换成char型指针。
4、( (char *)__mptr - offsetof(type,member) ) 获取结构体(type)的指针的起始地址
5、(type *)( (char *)__mptr - offsetof(type,member) ) 转换为type * 型的指针。
综上,当我们知道一个结构体type,以及其中的一个成员member和该成员的地址ptr,我们就能推算出该结构体的地址。
回到list_for_each_entry 的分析中:
这个函数的功能是遍历链表,什么是链表?
struct studen{
char gender;
int id;
int age;
struct list_head list;
char name[20];
}
在结构体中添加一个双向链表的节点list,当很多个list 首尾相连的时候,我们就可以遍历这个链表,然后根据节点的指针获取节点所在结构体的指针,从而获取数据。
怎么使用链表?
使用链表首先得有个链表头:
LIST_HEAD 的作用就是定义表头:新建一个双向链表的表头。
有了表头后,就可以将新的节点加入这个链表,通过list_add 实现:
再回到list_for_each_entry 的分析中:
1、pos = list_entry((head)->next, typeof(*pos), member)
链表头
2、&pos->member != (head);
循环终止条件
3、pos = list_entry(pos->member.next, typeof(*pos), member)
链表中的下一个节点
回到我们debug中,我们需要完成一个双向链表的遍历,首先需要建立一个双向链表:
可知我们需要遍历的双向链表表头为:phy_provider_list;
通过搜索得知:
在代码中我们确实定义了这个双向链表的表头。
这个情况下,我们没有遍历链表的原因只有一个了:
我们只定义了表头(指向自身)并没有使用 list_add 向这个链表中添加后续的节点,由此导致当我们调用list_for_each_entry 时没有遍历链表。
综上:我希望能通过遍历链表得到想要的phy,但是代码中没有建立相应的双向链表导致遍历失败,返回了错误值。
终于搞完了这个分享,这csdn不行啊,在网易笔记中先写好了,直接复制过来图片全部都没好,还需要一个个图重新截图,太难了我。。。。