本文主要接着上文的问题进行叙述,探究为什么会发生错误,该如何去寻求这个解决方案。
上文说到,由于中断线未连接,导致内核启动不了,访问了空地址,如下图:
图中的具体信息都可以在CSDN中找到对应的解释说明,我们主要关注三个数据:
Unable to handle kernel NULL pointer dereference at virtual address
PC at ...
LR at ...
其中基本上所有CPU都会有的PC寄存器(Program counter register),它保存最后出问题的地址。LR保存着函数返回地址。这里就比较容易看出是谁出问题。
从第二行可以看出内核已经指出来问题:程序访问了一个空指针。
继续往下看,PC和LR都指向了同样的问题:uart。那基本上就可以定位到uart的问题了,因为之前是加入了串口,而且官方的驱动本身是不可能出现错误的,所以错误只能出现在PL端配置 上,PL端做了什么?* 只在vivado中加入了串口*。
从上面的信息可以大胆推论:
出错的是在:uart_unregister_driver。这个函数
LR显示:ulite_probe()函数,这里存放的是异常的时候,程序中对应的函数,那就意味着是在ulite_probe函数中调用uart_unregister_driver出了问题。那么后面就相对于比较简单了。
当然这两句话后面有偏移量,可以进一步的使用反汇编去分析,这里我们不这样做,那样太麻烦了,我们这里采用逻辑推导来做,一步一步排查问题。
首先使用vscode打开内核文件夹,是总目录,不要进入任何的目录。
这里的study是我对内核目录重新的命名,可以不用管。按照你自己的路径来。
直接在vscode中查找,LR显示的函数:ulite_probe();异常抛出的时候是这个函数调用是出了问题的。那为什么不找uart_unregister_driver() ?当你找了过后会发现很多地方都有这个函数,同时,从名字都可以看出来,这是串口注销的函数,所以很多驱动都会调用这个函数。
当ctrl + f 后会惊喜的发现,只有几个地方出现了,而且只有一个函数,那就减小了工作量。
打开函数所在文件:这个时候又迷茫了,该如何下手去找呢?那么多行函数。。。。
不要急,前面说过,是在这个函数中调用了PC寄存器的函数,所以接下来。
直接查找该文件下的:uart_unregister_driver()。
会惊奇的发现,只有两个函数,那说明离成功只差一步了,马上就可以找到答案了。
接下来反向查找,再来分析一波,异常显示的是在ulite_probe()函数中,而后面的ulite_remove()这个函数从名字都能看出,这是正常的删除掉某个设备,显示这个函数是不符合情况的。明显目标是第一次出现的地方。
这时可以看出第一次出现uart_unregister_driver的地方是goto语句的调用,err_out_unregister_driver,那么反推,往上找,看看是在哪里出现的。
这个时候发现,这四个函数都会调用,那么问题来了,是哪一个呢??
此时有两种方案:
1、调试输出:
dev_err(&pdev->dev, "Failed to register driver\n");
2、逻辑推理
毫无疑问:
依然选择的是逻辑推理。因为调试输出成本太高了,时间太长,同时我也笃定内核驱动不会出问题,当然可能存在莫名的bug,这是小概率事件。
首先,回过头来分析一下,整个流程。
vivado加入IP核 --> 连线 --> 生成bit流 --> 生成hdf文件 --> linux下生成设备树文件 --> 编译进内核 --> 启动 --> 出错。
整个过程中新增的一部分就是串口IP核,那问题大概率就发生在生成bit流之前了。之后都是未做修改的,所以问题不大。那既然定位出错误,进一步分析,添加的IP核和linux之间的关系,会惊喜的发现他们之间只有唯一的交集:设备树文件。那所有的矛头都指向设备树,这就是分析的重点。
分析完后回到内核源码目录,依次查看四个会发生错误调用的函数。
1、platform_get_resource
这个函数字面意思应该是获取内存资源,和本文的错误关系不太大,内存只和linux资源相关,本文的错误明显是PL端扩展串口IP出了问题。不妨大胆一点,这个不会出现问题。往下看。。。
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENODEV;
goto err_out_unregister_driver;
}
2、platform_get_irq
顾名思义该文件和中断相关联。那如何判断呢?目前还无法判断,先往后看。
irq = platform_get_irq(pdev, 0);
if (irq <= 0) {
ret = -ENXIO;
goto err_out_unregister_driver;
}
3、devm_clk_get
获取时钟,同样也不好判断,继续看最后一个。
pdata->clk = devm_clk_get(&pdev->dev, "s_axi_aclk");
if (IS_ERR(pdata->clk)) {
if (PTR_ERR(pdata->clk) != -ENOENT) {
ret = PTR_ERR(pdata->clk);
goto err_out_unregister_driver;
}
4、clk_prepare_enable
使能时钟,这个可以和上一个合并,因为都是时钟相关函数。
ret = clk_prepare_enable(pdata->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to prepare clock\n");
goto err_out_unregister_driver;
}
分析到这个地方,问题大概就定位到了两个点:时钟和中断,内存可以排除。这两个量都是和PL端的资源相关联的,同时考虑到PL和PS之间的联系–>设备树。接下来尝试着看一下设备树。
1、打开未连接中断的设备树:
/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
axi_uartlite_0: serial@42c00000 {
clock-names = "s_axi_aclk";
clocks = <&clkc 15>;
compatible = "xlnx,axi-uartlite-2.0", "xlnx,xps-uartlite-1.00.a";
current-speed = <9600>;
device_type = "serial";
port-number = <0>;
reg = <0x42c00000 0x10000>;
xlnx,baudrate = <0x2580>;
xlnx,data-bits = <0x8>;
xlnx,odd-parity = <0x0>;
xlnx,s-axi-aclk-freq-hz-d = "100.0";
xlnx,use-parity = <0x0>;
};
};
};
看设备树中是否有中断和时钟相关联的配置,你会惊喜的发现,时钟发现了,但是irq却没有。那基本上问题都定位了,设备树并未生成中断相关的配置,但是驱动却用到了,因此发生了NULL异常。
因此回到vivado中查看相关联的IP核配置,这时候你会发现,中断引脚竟然悬空了。。。。赶紧连上,连上后重新生成hdf,制作设备树。完成后查看一下设备树相关文件。。。。
2、链接中断的设备树文件
axi_uartlite_0: serial@82c00000 {
clock-names = "s_axi_aclk";
clocks = <&misc_clk_0>;
compatible = "xlnx,axi-uartlite-2.0", "xlnx,xps-uartlite-1.00.a";
current-speed = <115200>;
device_type = "serial";
interrupt-names = "interrupt";
interrupt-parent = <&intc>;
interrupts = <0 35 1>;
port-number = <0>;
reg = <0x82c00000 0x10000>;
xlnx,baudrate = <0x1c200>;
xlnx,data-bits = <0x8>;
xlnx,odd-parity = <0x0>;
xlnx,s-axi-aclk-freq-hz-d = "150.0";
xlnx,use-parity = <0x0>;
};
设备树中有中断相关联的配置。
至此问题找到了,使用串口必须要在vivado中将串口IP的中断连接到PS的中断上。。。。。。重新制作系统完美启动。