跟着源码和qemu模拟过程研究下KGSL模块的加载流程。

环境配置

  • 真机xiaomi 14 pro设备树(pineapple 6.1.75)
  • qcom-kernel 源码:KERNEL.PLATFORM.4.0.r12-00400-kernel.0.xml
  • graphic-kernel 源码

模拟流程

  • 获取目标模块依赖树以及设备树;
  • 加载目标模块所有依赖模块,先满足目标模块能够insmod的条件;
  • 通过源码或者IDA反编译分析目标模块以及依赖模块需要的硬件属性,添加设备树设置;
    • 记录硬件属性的数据流
    • 硬件属性分为必要/非必要,均可以存储到数据库中
    • 设备树应该为dts格式,而非dtb反编译
    • 设备树每一个节点 qcom 有官方说明 txt
    • 注意设备树地址是否与virt本身冲突,冲突部分需要进行地址迁移,同时在 QEMU 侧注册相应 MMIO 地址区域
  • 查看目标模块是否加载成功,进行插桩动态循环测试。
    • 反馈信息:日志,跟踪执行路径作插桩(bpftrace)
    • 为不兼容设备模拟qemu硬件设备环境,包括初始化内存,…
    • 实在不行则patch
  • 当模拟复杂,递归进行2模拟

设备树配置

在virt.dtb中添加vienna.dtb中的配置项(注意需要保留原virt项);
在virt.dtb中添加以下配置,以便qemu虚拟机能够在msm-kgsl.ko注册设备时正常返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
soc {
#address-cells = <0x01>;
#size-cells = <0x01>;
ranges = <0x00 0x00 0x00 0xffffffff>;
compatible = "simple-bus";
phandle = <0x29d>;

qcom,kgsl-3d0@3d00000 {
nvmem-cell-names = "speed_bin";
nvmem-cells = <0x3f3>;
qcom,bus-table-ddr = <0x00 0x209a8e 0x2dc6c0 0x5caf6a 0x65ce03 0x7cb163 0xa3140c 0xbdf5c2 0xdbb3e5 0xfbc520>;
qcom,bus-table-cnoc = <0x00 0x64>;
interconnect-names = "gpu_icc_path";
interconnects = <0x53 0x11 0x41 0x200>;
qcom,tzone-names = "gpuss-0", "gpuss-1", "gpuss-2", "gpuss-3", "gpuss-4", "gpuss-5", "gpuss-6", "gpuss-7";
qcom,gpu-qdss-stm = <0x161c0000 0x40000>;
qcom,ubwc-mode = <0x04>;
qcom,min-access-length = <0x20>;
qcom,chipid = <0x43051401>;
qcom,gpu-model = "Adreno740";
clock-names = "gcc_gpu_memnoc_gfx", "gcc_gpu_snoc_dvm_gfx", "gpu_cc_ahb", "apb_pclk";
clocks = <0x2c 0x25 0x2c 0x26 0x4e 0x00 0x3f 0x00>;
interrupt-names = "kgsl_3d0_irq";
interrupts = <0x00 0x12c 0x04>;
reg-names = "kgsl_3d0_reg_memory", "cx_dbgc", "rscc", "cx_misc", "qdss_gfx", "qdss_etr", "qdss_tmc";
reg = <0x3d00000 0x40000 0x3d61000 0x3000 0x3d50000 0x10000 0x3d9e000 0x2000 0x10900000 0x80000 0x10048000 0x8000 0x10b05000 0x1000>;
status = "ok";
compatible = "qcom,adreno-gpu-gen7-6-0", "qcom,kgsl-3d0";
#cooling-cells = <0x02>;
phandle = <0x92>;
};
};

这样的话在进行modprobe时,内核才会真正去尝试去probe设备。当然,由于大部分设备都没有进行模拟,导致肯定会失败。

KGSL 模块依赖

构建了打印模块依赖树的脚本,输出模块依赖树如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
msm-kgsl
├── socinfo
│ └── smem
├── llcc-qcom
│ └── socinfo
│ └── smem
├── qcom-dcvs
│ └── dcvs-fp
│ ├── qcom-rpmh
│ │ ├── qcom-ipc-logging
│ │ │ └── minidump
│ │ │ ├── debug-symbol
│ │ │ └── smem
│ │ └── cmd-db
│ └── cmd-db
├── governor-msm-adreno-tz
│ └── qcom-scm
├── qcom-scm
├── cmd-db
├── msm-performance
│ ├── qcom-pmu-lib
│ │ ├── qcom-scmi-client
│ │ └── qcom-llcc-pmu
│ └── sched-walt
│ └── socinfo
│ └── smem
├── qcom-aoss
│ └── qcom-ipc-logging
│ └── minidump
│ ├── debug-symbol
│ └── smem
├── mem-buf-dev
│ └── secure-buffer
│ └── qcom-scm
├── qcom-iommu-util
│ └── qcom-scm
├── clk-qcom
│ ├── gdsc-regulator
│ │ ├── proxy-consumer
│ │ └── debug-regulator
│ └── icc-clk
├── secure-buffer
│ └── qcom-scm
├── mdt-loader
│ └── qcom-scm
├── minidump
│ ├── debug-symbol
│ └── smem
├── qcom-va-minidump
│ └── minidump
│ ├── debug-symbol
│ └── smem
└── coresight

模块依赖项加载

这里目前只看加载日志是否报错以及是否会成功lsmod查看到装载,目标对象是叶子节点的模块。

  • debug_symbol
  • coresight
  • icc_clk
  • proxy_consumer
  • debug_regulator
  • gdsc_regulator
  • clk_qcom
  • qcom_scmi_client
    其他的叶子模块则由于qemu probe时存在问题无法加载。

模块设备模拟

cmd-db

该模块主要用于 rpmh 等模块查看 resource 的对应存放值的地址。
qemu/hw/misc/qcom-cmd-db.c 创建设备,在对应地址初始化内存,主要是设置 magic header 以及添加了最小的 rsc_header 以及 entry。可以使其加载成功。

smem

该模块用于各模块之间 share_memory 的共享数据。
qemu/hw/misc/qcom-smem.c 创建设备,在对应地址初始化内存

qcom-scm

qcom-aoss

解决依赖后可直接加载的模块:

  • socinfo
  • minidump
  • qcom-ipc-logging
  • qcom-rpmh
  • dcvs-fp
  • msm-performance
  • qcom-va-minidump

KGSL Init

内核模块启动时,会先调用 module_init 函数进行初始化,msm_kernel 也是如此。其初始化函数为 kgsl_3d_init。在直接 insmod 时(已经事先加载其他依赖模块),其会正常调用该函数并正常返回。
下面是添加日志函数后打印结果,并没有进入错误分支。

1
2
3
4
[   19.256512][   T83] boot_kpi: M - DRIVER KGSL Init
[ 19.261323][ T83] boot_kpi: M - DRIVER KGSL Ready
[ 19.261681][ T83] boot_kpi: M - DRIVER KGSL MMU Init
[ 19.262443][ T83] boot_kpi: M - DRIVER KGSL GMU Core Register

初始化后查看bind情况:

1
2
3
4
5
6
7
8
9
# platform driver register success
ls /sys/bus/platform/devices/ | grep -i kgsl
3d00000.qcom,kgsl-3d0
# device tree is valid
cat /proc/device-tree/soc/qcom,kgsl-3d0@00000/status
ok
# There is no device link file.
ls /sys/bus/platform/drivers/kgsl-3d
bind modules uevent unbind

但是发现/dev目录下并没有创建 /dev/kgsl-3d0,猜测没有执行 adreno_dev->probe函数,或者中途退出。

GPU Init

arm-smmu

需要内核编译开启 CONFIG_ARM_SMMU_V3 相关配置。并在启动时加载 arm-smmu 模块
arm-smmu 模块加载会报错:gpu_qtb, anoc_1_qtb TBU failed probe.
说明在 probe 时 TBU 加载失败。
其问题代码函数调用链为:

1
2
3
4
5
arm_smmu_device_probe
-> arm_smmu_impl_init
-> qssmuv500_impl_init
-> qsmmuv500_create
-> device_for_each_child

将TBU相关内容先注释掉,继续执行,发现kgsl_smmu 卡在