qemu和vscode调试uboot及设备模型数据流分析
  2Nv1H5BMjysw 2023年11月02日 28 0

一,前言

对于通用的流程,我需要形成闭环的代码理解,验证我理解的准确性。于是我选择用调试的方法来看数据流,用qemu来仿真,vsocde来调试,但是不能仿真am335。所以用了qemu支持仿真的v9。

二,过程记录

1,编译uboot

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
rm -rf ./v9
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=v9 vexpress_ca9x4_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=v9

2,编译qemu的arm版本,进入qemu目录

mkdir build && cd build
../configure --target-list=arm-softmmu --audio-drv-list= --enable-debug
make

3,仿真uboot 前提

export PATH=/work/tools/qemu-2.8.0/build/arm-softmmu:$PATH

进入路径/work/v9/u-boot-2023.10/v9,输入命令仿真uboot的命令

qemu-system-arm -M vexpress-a9 -kernel u-boot -nographic -m 512M

qemu退出仿真的方法:先按ctrl+a,然后松口按一个x。

4,通过gdb调试qemu开发板 前提

export PATH=/work/tools/qemu-2.8.0/build/arm-softmmu:$PATH

进入uboot文件所在目录,通过启动参数来启动gdb server

qemu-system-arm -M vexpress-a9 -kernel u-boot -nographic -m 512M --gdb tcp::1234 -S

Client

arm-linux-gnueabihf-gdb /work/v9/u-boot-2023.10/v9/u-boot
然后输入
target remote localhost:1234

5,通过vscode调试qemu开发板 前提

export PATH=/work/tools/qemu-2.8.0/build/arm-softmmu:$PATH

进入uboot文件所在目录,通过启动参数来启动gdb server

qemu-system-arm -M vexpress-a9 -kernel u-boot -nographic -m 512M --gdb tcp::1234 -S

客户端创建.vscode文件夹再创建launch.json文件,最后按F5运行客户端调试即可。

{
    "version":"0.2.0",
    "configurations":[
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "/work/v9/u-boot-2023.10/v9/u-boot",
            "args": [],
            "stopAtEntry": true,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "miDebuggerPath": "/work/tools/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gdb",
            "miDebuggerServerAddress":"localhost:1234",
            /*"preLaunchTask":"build",*/
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

6,uboot中会重新搬运,所以符号表就会不匹配,若要打断点就不起作用了。需要调试搬运后的代码,就需用在gdb中重新加载符号表。这个重定向地址编译后会变化的

-exec b relocate_code   //进入此断点后,就可以重新加载uboot了
-exec add-symbol-file /work/v9/u-boot-2023.10/v9/u-boot 0x7ff60000 //重新加载后,则可打正常断点了
-exec b board_init_r

qemu和vscode调试uboot及设备模型数据流分析_uboot

7,关于bind路径 我调试的设备对象为mmc,bind的路径

board_init_r(\common\board_r.c:818)
initcall_run_list(\include\initcall.h:46)
dm_init_and_scan(\drivers\core\root.c:433)
dm_scan(\drivers\core\root.c:409)
dm_extended_scan(\drivers\core\root.c:330)
dm_scan_fdt(\drivers\core\root.c:309)
dm_scan_fdt_node(\drivers\core\root.c:288) ----1
lists_bind_fdt(\drivers\core\lists.c:253)
device_bind_with_driver_data(\drivers\core\device.c:244)
device_bind_common(\drivers\core\device.c:178)
simple_bus_post_bind(\drivers\core\simple-bus.c:57)
dm_scan_fdt_node(\drivers\core\root.c:288) ----2
lists_bind_fdt(\drivers\core\lists.c:253)
device_bind_with_driver_data(\drivers\core\device.c:244)
device_bind_common(\drivers\core\device.c:178)
simple_bus_post_bind(\drivers\core\simple-bus.c:57)
dm_scan_fdt_node(\drivers\core\root.c:288) ----3
lists_bind_fdt(\drivers\core\lists.c:253)
device_bind_with_driver_data(\drivers\core\device.c:244)
device_bind_common(\drivers\core\device.c:178)
simple_bus_post_bind(\drivers\core\simple-bus.c:57)
dm_scan_fdt_node(\drivers\core\root.c:288) ----4
lists_bind_fdt(\drivers\core\lists.c:253)
device_bind_with_driver_data(\drivers\core\device.c:244)
device_bind_common(\drivers\core\device.c:168)
arm_pl180_mmc_bind(\drivers\mmc\arm_pl180_mmci.c:499)

lists_bind_fdt是匹配的关键函数,首先在driver中遍历,然后和node的compatible字段进行比较

for (i = 0; i < compat_length; i += strlen(compat) + 1) {
        ...
        
        id = NULL;
        for (entry = driver; entry != driver + n_ents; entry++) {
            if (drv) {
                if (drv != entry)
                    continue;
                if (!entry->of_match)
                    break;
            }
            ret = driver_check_compatible(entry->of_match, &id, compat);
            if (!ret)
                break;
        }
        if (entry == driver + n_ents)
            continue;  
        ...

        ret = device_bind_with_driver_data(parent, entry, name,
                           id ? id->data : 0, node,
                           &dev);
         ...                    
    }

比较是通过driver_check_compatible函数,若strcmp为0说明找到了,然后返回0,就break,退出driver的遍历了。若driver全部扫描完了还没找到,最后退出时候判断entry等于最后一个driver,那么就continue,不会向下再走到device_bind_with_driver_data了,重新开始循环compat_length的node字符串内容。否则会进入device_bind_common来创建device设备且绑定driver。

static int driver_check_compatible(const struct udevice_id *of_match,
                   const struct udevice_id **of_idp,
                   const char *compat)
{
    if (!of_match)
        return -ENOENT;

    while (of_match->compatible) {
        if (!strcmp(of_match->compatible, compat)) {
            *of_idp = of_match;
            return 0;
        }
        of_match++;
    }
    return -ENOENT;
}

我由于是打断点进行子类bind函数,所以父类都是compatible mach的。所以会看到4个相同的调用路径。 分析主要函数,发现dm_scan_fdt_node会被重复调用4次后进入bind函数,原因是node的父类和子类关系图可以看出node mmc在第4层。

smb@4000000
	motherboard
		iofpga@7,00000000
			mmci@5000

dm_scan_fdt_node主要就是在父类node中从第一个子类node开始扫描,所以第一个scan的node是smb对象,然后是motherboard依次遍历扫描到了mmci@5000进行了bind调用。

for (node = ofnode_first_subnode(parent_node);
         ofnode_valid(node);
         node = ofnode_next_subnode(node)) {
        const char *node_name = ofnode_get_name(node);
......
        err = lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only);
......
    }

只要是UCLASS_SIMPLE_BUS类的,没有bind函数,但是都有post_bind

UCLASS_DRIVER(simple_bus) = {
    .id     = UCLASS_SIMPLE_BUS,
    .name       = "simple_bus",
    .post_bind  = simple_bus_post_bind,
    .per_device_plat_auto   = sizeof(struct simple_bus_plat),
};

而post_bind的函数simple_bus_post_bind主要就是继续遍历下层node进行驱动绑定,调用的函数依然是dm_scan_fdt_node,所以看上去像是归递的效果来寻找子类。

static int simple_bus_post_bind(struct udevice *dev)
{
	return dm_scan_fdt_dev(dev);
}
int dm_scan_fdt_dev(struct udevice *dev)
{
    return dm_scan_fdt_node(dev, dev_ofnode(dev),gd->flags & GD_FLG_RELOC ? false : true);
}

8,关于probe的流程 这个看起来比较少,扫描uclass中的device,通过seq来匹配。device_probe完成后设置DM_FLAG_ACTIVATED标志,device_probe先会处理父类设备进行probe,device_probe(dev->parent)。然后才是自己probe,ret = drv->probe(dev)。probe就是真正的通过driver来初始化设备了。

board_init_r
initcall_run_list
initr_mmc
mmc_initialize
mmc_probe
uclass_get_device_by_seq
uclass_get_device_tail
device_probe
arm_pl180_mmc_probe

三,小结

本次主要学习了gdb的symbol文件加载命令,深入学习了基于dm的数据流,这样等于形成一个代码理解的闭环,之前我看着就点乱,感觉会重复probe,原来有DM_FLAG_ACTIVATED标志。之前node的遍历方式不太清楚,现在知道是先按node来遍历compatible,然后在driver list中搜进行字符串匹配,至于匹配后就会创建device对象,然后调用prebind,bind,postbind这类钩子函数。而probe是初始化device当然要在bind后面,而且probe先进行父类设备再进行子类设备。今天看的这个system_bus的回调函数,学习到一招,就是post_bind的用途,这样可以看出bus和mmc虽然都匹配了,但是后面调用的路径不同。通过system_bus还形成了post_bind的子node归递查询,非常棒的设计。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
2Nv1H5BMjysw