第2阶段:启用eMMC/SD卡设备

目标

上一章节只有 initramfs 的临时文件系统(RAM),断电就没了。手机有 16GB 的 eMMC 芯片焊在主板上,我们需要内核识别它,为后续挂载持久化 rootfs 做准备。

DTS 改动分析

需要写多少 DTS?

答案:两行

1
2
&sdhc_1 { status = "okay"; };   // eMMC — 焊死在主板上的内部存储
&sdhc_2 { status = "okay"; }; // SD 卡 — 外部卡槽(Y927 有 TF 卡槽)

不需要写 GPIO、不需要写 pinctrl、不需要写寄存器地址。原因和阶段0的骨架 DTS 一样——这一切早在 msm8916.dtsimsm8916-pm8916.dtsi 里定义好了。

msm8916.dtsi(SoC 级)第 2170-2190 行为 sdhc_1 写了:

属性 含义
reg 0x07824900 / 0x07824000 eMMC 控制器寄存器物理地址
interrupts GIC_SPI 123 / GIC_SPI 138 中断线
clocks GCC_SDCC1_* eMMC 专用时钟
pinctrl-0 sdc1_default 引脚配置(sdc1_clk/sdc1_cmd/sdc1_data,专用 SDC 功能引脚,在 msm8916.dtsi 第 1466-1500 行定义)
bus-width 8 8 位数据总线
non-removable 标记不可插拔(焊死的)
status "disabled" 板级 DTS 唯一要改的

msm8916-pm8916.dtsi(PMIC 级)第 37-40 行给 sdhc_1 配好了供电:

1
2
3
4
&sdhc_1 {
vmmc-supply = <&pm8916_l8>; // 2.9V 核心电压
vqmmc-supply = <&pm8916_l5>; // 1.8V I/O 电压
};

SD 卡(sdhc_2)同理——SoC dtsi 已定义 4 位总线 + pinctrl,PMIC dtsi 已配好 pm8916_l11/pm8916_l12 供电。唯一多出来的板级信息是 card detect GPIO(SD 卡槽的物理检测脚),这个确实是板子决定的:

1
2
3
4
&sdhc_2 {
status = "okay";
cd-gpios = <&tlmm 38 GPIO_ACTIVE_LOW>; // Y927 TF 卡槽检测脚
};

msm8916-mtp.dts 参考板也配了 cd-gpios = <&tlmm 38 ...>,Y927 复用了同样的 GPIO。

DTS

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
54
// SPDX-License-Identifier: GPL-2.0-only

/dts-v1/;

#include "msm8916-pm8916.dtsi"
#include <dt-bindings/gpio/gpio.h>

/ {
model = "vivo Y927 (PD1410V)";
compatible = "vivo,y927", "qcom,msm8916";
chassis-type = "handset";

aliases {
mmc0 = &sdhc_1; // eMMC 固化为 /dev/mmcblk0
mmc1 = &sdhc_2; // SD 卡 固化为 /dev/mmcblk1
};

usb_id: usb-id {
compatible = "linux,extcon-usb-gpio";
id-gpios = <&tlmm 110 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&usb_id_default>;
};
};

&usb {
dr_mode = "peripheral";
extcon = <&usb_id>, <&usb_id>;
status = "okay";
};

&usb_hs_phy {
extcon = <&usb_id>;
status = "okay";
};

&tlmm {
usb_id_default: usb-id-default {
pins = "gpio110";
function = "gpio";
drive-strength = <8>;
bias-pull-up;
};
};

&sdhc_1 {
status = "okay"; // eMMC(内部存储)
};

&sdhc_2 {
status = "okay"; // SD 卡(TF 卡槽)
cd-gpios = <&tlmm 38 GPIO_ACTIVE_LOW>;
};

内核配置

不需要改动。msm8916_defconfig 已包含:

1
2
3
4
CONFIG_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_MSM=y # MSM8916 的 SDHC 控制器驱动

initramfs

沿用第1阶段的 initramfs(USB serial 交互 shell),无需修改。第2阶段只是在 DTS 中启用了 eMMC/SD 控制器,内核启动后 /dev/mmcblk0* 会自然出现,不需要 init 脚本做任何事。

编译 Linux 内核

1
2
cd ~/y927/linux
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image.gz dtbs

构建 boot.img 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd ~/y927/linux

# 拼接内核 + DTB
cat arch/arm64/boot/Image.gz \
arch/arm64/boot/dts/qcom/msm8916-vivo-y927.dtb \
> vmlinuz-dtb

~/y927/lk2nd/lk2nd/scripts/mkbootimg \
--kernel vmlinuz-dtb \
--ramdisk $HOME/y927/initramfs/initramfs-phase-1.cpio.gz \
--cmdline "console=ttyGS0,115200 panic=1" \
--base 0x80000000 \
--kernel_offset 0x00008000 \
--ramdisk_offset 0x01000000 \
--second_offset 0x00f00000 \
--tags_offset 0x00000100 \
--pagesize 2048 \
--header_version 0 \
--output boot-phase2.img

刷入 boot.img 文件

1
2
fastboot flash boot boot-phase2.img
fastboot reboot

测试

1
sudo picocom -b 115200 /dev/ttyACM0

输入busybox fdisk -l可以识别到16GB eMMC和8GB SD卡了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Disk /dev/mmcblk0: 14.5G, 15634268160 bytes, 30535680 sectors
1893 cylinders, 256 heads, 63 sectors/track
Units: cylinders of 16128 * 512 = 8257536 bytes

Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/mmcblk0p1 0,0,1 1023,255,63 1 4294967295 4294967295 2047G ee EFI GPT
Disk /dev/mmcblk1: 7619M, 7989100544 bytes, 15603712 sectors
3504 cylinders, 73 heads, 61 sectors/track
Units: cylinders of 4453 * 512 = 2279936 bytes

Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/mmcblk1p1 0,130,3 971,72,61 8192 15603711 15595520 7615M b Win95 FAT32
Disk /dev/mmcblk0boot0: 4096K, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: cylinders of 64 * 512 = 32768 bytes

Disk /dev/mmcblk0boot0 doesn't contain a valid partition table
Disk /dev/mmcblk0boot1: 4096K, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: cylinders of 64 * 512 = 32768 bytes

Disk /dev/mmcblk0boot1 doesn't contain a valid partition table

第3阶段:真正的DRM显示(HX8394A 面板驱动移植)

目标

在lk1st的移植过程中,已经用lmdpdg.py工具反编译生成了完整的 Linux DRM panel 驱动,驱动源码在
~/y927/linux-mdss-dsi-panel-driver-generator/workdir/dtb/plat_ce-var_1-sub_0/hx8394a_720p_video
这一阶段将这一驱动移植到主线 Linux 内核中。

内核驱动移植

复制驱动源码

1
2
cp ~/y927/linux-mdss-dsi-panel-driver-generator/workdir/dtb/plat_ce-var_1-sub_0/hx8394a_720p_video/panel-hx8394a.c \
~/y927/linux/drivers/gpu/drm/panel/msm8916-generated/panel-vivo-y927-hx8394a.c

修改驱动 compatible 字符串

lmdpdg 生成的 .c 文件用的是下游 compatible "mdss,hx8394a",需要改成与 DTS 一致的 "vivo,y927-hx8394a",否则 panel 永远不 probe,dmesg 里看不到任何 panel/dsi/backlight 字样。

1
2
sed -i 's|"mdss,hx8394a"|"vivo,y927-hx8394a"|' \
~/y927/linux/drivers/gpu/drm/panel/msm8916-generated/panel-vivo-y927-hx8394a.c

对应源码改动(约 238 行):

1
2
3
4
static const struct of_device_id hx8394a_of_match[] = {
{ .compatible = "vivo,y927-hx8394a" }, // 改自 "mdss,hx8394a"
{ /* sentinel */ }
};

注册到 Kconfig / Makefile

drivers/gpu/drm/panel/msm8916-generated/Kconfig

1
2
3
config DRM_PANEL_VIVO_Y927_HX8394A
tristate "vivo Y927 HX8394A"
default DRM_PANEL_MSM8916_GENERATED

drivers/gpu/drm/panel/msm8916-generated/Makefile

1
obj-$(CONFIG_DRM_PANEL_VIVO_Y927_HX8394A) += panel-vivo-y927-hx8394a.o

内核配置

1
2
3
4
5
6
7
8
9
sudo apt install -y libncurses-dev
cd ~/y927/linux
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# Device Drivers → Graphics support → Direct Rendering Manager (XFree86 4.1.0 and higher DRI support) → Display Panels →
# MSM8916 panel drivers generated with linux-mdss-dsi-panel-driver-generator →
# <*> vivo Y927 HX8394A

# Device Drivers → Graphics support → Backlight & LCD device support →
# <*> Generic GPIO based Backlight Driver

等价于直接编辑.config文件,添加以下内容:

1
2
3
CONFIG_DRM_PANEL_VIVO_Y927_HX8394A=y
CONFIG_BACKLIGHT_CLASS_DEVICE=y
CONFIG_BACKLIGHT_GPIO=y

或者是使用./scripts/config命令:

1
2
3
4
5
6
7
8
9
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- msm8916_defconfig
./scripts/config --enable DRM_PANEL_VIVO_Y927_HX8394A
./scripts/config --enable BACKLIGHT_CLASS_DEVICE
./scripts/config --enable BACKLIGHT_GPIO
./scripts/config --enable CONFIG_REGULATOR_FIXED_VOLTAGE
./scripts/config --enable DRM_MSM
./scripts/config --enable USB_G_SERIAL
./scripts/config --enable U_SERIAL_CONSOLE
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig

⚠️ CONFIG_BACKLIGHT_GPIO=y 不能漏。缺了它 panel 驱动里 drm_panel_of_backlight() 永远返回 -EPROBE_DEFER,并且 dev_err_probe 不打印 EPROBE_DEFER —— 整条 DSI 链路会"静默卡死",dmesg 里看不到任何 panel/dsi/backlight 字样,看起来像是什么都没发生。
⚠️ olddefconfig 必须带 ARCH=arm64。不带 ARCH 跑会按 x86 树读 Kconfig,新加的 ARM 平台 symbol 不会被识别,编 Image.gz 时 kbuild 会反复弹"Restart config…"交互问每个未识别项。

DTS 完善

lmdpdg 生成的 panel-hx8394a.dtsi 模板内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&mdss_dsi0 {
panel@0 {
compatible = "mdss,hx8394a";
reg = <0>;

backlight = <&backlight>;
reset-gpios = <&tlmm XY GPIO_ACTIVE_LOW>;

port {
panel_in: endpoint {
remote-endpoint = <&mdss_dsi0_out>;
};
};
};
};

&mdss_dsi0_out {
data-lanes = <0 1 2 3>;
remote-endpoint = <&panel_in>;
};

最后的 DTS 如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: GPL-2.0-only

/dts-v1/;

#include "msm8916-pm8916.dtsi"
#include <dt-bindings/gpio/gpio.h>

/ {
model = "vivo Y927 (PD1410V)";
compatible = "vivo,y927", "qcom,msm8916";
chassis-type = "handset";

aliases {
mmc0 = &sdhc_1; // eMMC 固化为 /dev/mmcblk0
mmc1 = &sdhc_2; // SD 卡 固化为 /dev/mmcblk1
};

usb_id: usb-id {
compatible = "linux,extcon-usb-gpio";
id-gpios = <&tlmm 110 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&usb_id_default>;
};

/* 背光(GPIO8 控制) */
backlight: backlight {
compatible = "gpio-backlight";
gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>;
default-on;
};

/* LCD 偏压电源 VSP */
reg_lcd_enp: regulator-lcd-enp {
compatible = "regulator-fixed";
regulator-name = "lcd_enp";
gpio = <&tlmm 97 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-always-on; /* lmdpdg 生成的 panel 驱动不读 vsp/vsn-supply */
regulator-boot-on; /* 必须 always-on,否则 fixed regulator 启动时被拉低,屏黑 */
};

/* LCD 偏压电源 VSN */
reg_lcd_enn: regulator-lcd-enn {
compatible = "regulator-fixed";
regulator-name = "lcd_enn";
gpio = <&tlmm 98 GPIO_ACTIVE_HIGH>;
enable-active-high;
regulator-always-on;
regulator-boot-on;
};

/* 预留 lk1st splash framebuffer 不被内核踩踏 */
reserved-memory {
cont-splash@83200000 {
reg = <0 0x83200000 0 0x800000>;
no-map;
};
};
};

&usb {
dr_mode = "peripheral";
extcon = <&usb_id>, <&usb_id>;
status = "okay";
};

&usb_hs_phy {
extcon = <&usb_id>;
status = "okay";
};

&tlmm {
usb_id_default: usb-id-default {
pins = "gpio110";
function = "gpio";
drive-strength = <8>;
bias-pull-up;
};

/* DSI reset pin (GPIO25) */
mdss_default: mdss-default-state {
pins = "gpio25";
function = "gpio";
drive-strength = <8>;
bias-disable;
};
mdss_sleep: mdss-sleep-state {
pins = "gpio25";
function = "gpio";
drive-strength = <2>;
bias-pull-down;
};
};

&sdhc_1 {
status = "okay"; // eMMC(内部存储)
};

&sdhc_2 {
status = "okay"; // SD 卡(TF 卡槽)
cd-gpios = <&tlmm 38 GPIO_ACTIVE_LOW>;
};

/* ====== DSI 主机 + 面板 ====== */
&mdss {
status = "okay";
};

&mdss_dsi0 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&mdss_default>;
pinctrl-1 = <&mdss_sleep>;
status = "okay";

panel@0 {
compatible = "vivo,y927-hx8394a";
reg = <0>;
reset-gpios = <&tlmm 25 GPIO_ACTIVE_LOW>;
backlight = <&backlight>;
vsp-supply = <&reg_lcd_enp>;
vsn-supply = <&reg_lcd_enn>;

port {
panel_in: endpoint {
remote-endpoint = <&mdss_dsi0_out>;
};
};
};
};

/* DSI host 输出端:data-lanes + remote-endpoint 必须放在这里 */
&mdss_dsi0_out {
data-lanes = <0 1 2 3>;
remote-endpoint = <&panel_in>;
};

⚠️ 不要把 data-lanes 放在 panel 的 endpoint 里,也不要省略 &mdss_dsi0_out { remote-endpoint = <&panel_in>; }。DSI host 在 dsi_bind() 里通过 devm_drm_of_get_bridge(np, port=1, ep=0) 沿自己的 port@1 endpoint 找下游 panel —— host 这一侧少了 remote-endpoint 会让 component_bind 返回 -ENODEV,dmesg 里出现:

1
2
msm_mdp 1a01000.display-controller: failed to bind 1a98000.dsi (-19)
panel-hx8394a 1a98000.dsi.0: error -ENODEV: Failed to attach to DSI host

dmesg 里启动早期会有几行 Fixed dependency cycle(s) with /soc@0/display-subsystem@1a00000/dsi@1a98000 —— 这只是 driver_deferred_probe 的常规告警,所有 msm8916 mainline 设备都有这一行,不是循环依赖问题。社区参考写法见 arch/arm64/boot/dts/qcom/msm8916-acer-a1-724.dts

GPIO 对照(引自 lk2nd DTS msm8916-vivo-y927.dts lk1st 移植时已验证的实机配置):

GPIO 功能 方向 lk2nd 中的写法
8 背光 enable 输出高有效 regulator-backlight { gpios = <&tlmm 8 ...>; }
25 面板 reset 输出低有效 (lk2nd 由 panel driver 内部管理)
97 VSP 正偏压 输出高有效 regulator-lcd-enp { gpios = <&tlmm 97 ...>; }
98 VSN 负偏压 输出高有效 regulator-lcd-enn { gpios = <&tlmm 98 ...>; }

initramfs

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
BUSYBOX_BINARY=~/y927/busybox-1.38.0/busybox
cd ~/y927/initramfs
rm -rf phase-3
mkdir -p phase-3 && cd phase-3

# 创建 initramfs 目录结构
mkdir -p bin dev proc sys lib/modules

# 复制 BusyBox 到 initramfs
cp $BUSYBOX_BINARY bin/

# 创建基础命令的软链接
cd bin
for cmd in sh mount ls cat sleep exec echo; do
ln -sf busybox $cmd
done
cd ..

# 创建 /init 脚本
cat > init << 'EOF'
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sys /sys
mount -t devtmpfs dev /dev
echo "===== Phase 1: kernel booted! ====="
echo "Serial: $(cat /proc/cmdline)"
sleep 5
exec sh </dev/ttyGS0 >/dev/ttyGS0 2>&1
EOF
chmod +x init

# 打包
find . | cpio -o -H newc | gzip > ../initramfs-phase-3.cpio.gz

编译 Linux 内核

1
2
cd ~/y927/linux
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image.gz dtbs

构建 boot.img 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd ~/y927/linux

# 拼接内核 + DTB
cat arch/arm64/boot/Image.gz \
arch/arm64/boot/dts/qcom/msm8916-vivo-y927.dtb \
> vmlinuz-dtb

~/y927/lk2nd/lk2nd/scripts/mkbootimg \
--kernel vmlinuz-dtb \
--ramdisk $HOME/y927/initramfs/initramfs-phase-3.cpio.gz \
--cmdline "console=tty0 console=ttyMSM0,115200 panic=1 initcall_blacklist=simpledrm_platform_driver_init" \
--base 0x80000000 \
--kernel_offset 0x00008000 \
--ramdisk_offset 0x01000000 \
--second_offset 0x00f00000 \
--tags_offset 0x00000100 \
--pagesize 2048 \
--header_version 0 \
--output boot-phase3.img

⚠️ cmdline 必须含 console=tty0,否则屏幕亮但内核 console 不往 fbdev 写,启动时屏上一片空白。initcall_blacklist=simpledrm_platform_driver_init 阻止 simpledrm 抢先绑定 framebuffer,让 MSM DRM 接管。

刷入 boot.img 文件

1
2
fastboot flash boot boot-phase3.img
fastboot reboot

测试

2026-06-14 01:50 屏幕亮起,console 正常滚动内核日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~ # busybox dmesg | busybox grep -iE "panel|hx8394|mdss|drm|dsi|backlight"
[ 0.000000] Kernel command line: console=tty0 console=ttyMSM0,115200 panic=1 initcall_blacklist=simpledrm_platform_driver_init
[ 0.000000] blacklisting initcall simpledrm_platform_driver_init
[ 0.000230] printk: legacy console [tty0] enabled
[ 0.367747] msm_mdp 1a01000.display-controller: bound 1a98000.dsi (ops 0xffff800080c19a38)
[ 0.370379] msm_mdp 1a01000.display-controller: [drm:mdp5_kms_init] MDP5 version v1.6
[ 0.406922] [drm] Initialized msm 1.13.0 for 1a01000.display-controller on minor 0
[ 0.690698] [drm:mdp5_irq_error_handler] *ERROR* errors: 04000000 ← 仅一次,初始 modeset 正常
[ 0.746648] Console: switching to colour frame buffer device 90x80 ← fbcon 接管屏幕
[ 0.826530] msm_mdp 1a01000.display-controller: [drm] fb0: msmdrmfb frame buffer device

~ # cat /sys/class/drm/card0-DSI-1/status
connected
~ # cat /sys/class/drm/card0-DSI-1/enabled
enabled
~ # cat /sys/class/drm/card0-DSI-1/modes
720x1280
~ # cat /proc/fb
0 msmdrmfb