前言
一加6T几乎是国内容易买到的、价格和主线支持以及性能之间的最佳平衡,用来装Linux再合适不过。
然而购买一台二手机的成本还是太高了。
如何做到更便宜?
买主板/碎屏机
注意
本文所提到的boot镜像、initrd以及根文件系统,均在以下基础之上
一加6t新Linux刷机包,你的下一台服务器何必是服务器 - bilibili
解锁Bootloader
一般来说,不管你从哪里搞到的主板,上面安装的肯定都是一加的原版系统,这个时候也不用急着把主板完全安装到手机上,只需要把三条排线和电源(电池/直供电)接上就行。

没有开机键的情况下怎么开机? 你先别急,主板背面有两坨引脚,如下图,右边两个是开机键,短接实现按下开机键效果,左边三个是音量键,短接上面两个相当于音量+,下面两个相当于音量-。

按照正常流程解锁BL之后,确认Bootloader页面的DEVICE STATE显示为“unlocked”之后,就可以先把手机放在一边,开始下一步了。

重新编译内核
由于我们的目标只是让主板跑起来,所以实际上有很多驱动我们不再需要了,比如解码器(声音)、触屏、面板、电池等等。
电池
一加6T的电池驱动名称为bq27xxx,这玩意我们得优先关掉,因为在没有电池的情况下,它会在内核缓冲区拉屎。
在menuconfig里一路找到bq27xxx,按N将其关闭。

配置修改完毕之后,重新编译内核。同时需要修改rootfs中的内核模块,不过这一部分我们放到后面。
修改Boot镜像
如果我们打算不使用屏幕和声音以及振动马达、也不打算徒手摸SoC温度、也不打算在主板上安装任何温度传感器、更不打算在主板上焊接UART调试串口,那么我们将面临一个棘手的问题:如何确定主板是否已经开机?
你先别急,主板上其实还剩一个输出设备☝🏻☝🏻☝🏻。

如你所见,主板上还有俩LED灯 (由pmi8998控制),完全可以用来指示主板的运行状态。
不过值得注意的是,虽然实际上有两个LED灯,但它们的距离实在是太近了,不带个墨镜恐怕是很难在十厘米之外分清楚它们两个,所以我们姑且将其视为同一个LED灯。
我们计划:
- 内核启动完毕,进入
initrd-> 常亮 initrd结束,系统被systemd接管 -> 心跳闪烁,三秒一次- Kernel panic :( -> 快速闪烁
找到我们的initrd中的init脚本,首先为其添加两个辅助函数。
# 状态1:启动中 -> 常亮 (最大亮度)
led_solid_on() {
if [ -d /sys/class/leds ]; then
for led in /sys/class/leds/*; do
[ -e "$led" ] || continue
echo none > "$led/trigger" 2>/dev/null
# 获取最大亮度,默认为 255
max=$(cat "$led/max_brightness" 2>/dev/null || echo 255)
echo "$max" > "$led/brightness" 2>/dev/null
done
fi
}
# 状态3:严重错误 -> 全部交替/快速闪烁
# 注意:这是一个死循环,一旦调用脚本将不再继续执行
led_panic_blink() {
echo "PANIC: LED blinking loop started." > /dev/kmsg
# 尝试设置所有 LED 为无触发器
if [ -d /sys/class/leds ]; then
for led in /sys/class/leds/*; do echo none > "$led/trigger" 2>/dev/null; done
fi
# 死循环闪烁
while true; do
# 全部亮
for led in /sys/class/leds/*; do
max=$(cat "$led/max_brightness" 2>/dev/null || echo 255)
echo "$max" > "$led/brightness" 2>/dev/null
done
sleep 0.1
# 全部灭
for led in /sys/class/leds/*; do echo 0 > "$led/brightness" 2>/dev/null; done
sleep 0.1
done
}
在init脚本将关键目录挂载完毕后的尽量靠前位置调用第一个函数
setup_mdev
# 手动加载模块
if [ -x /sbin/modprobe ]; then
echo "Loading LED driver..." > /dev/kmsg
modprobe leds-qcom-flash
else
insmod /lib/modules/*/kernel/drivers/leds/flash/leds-qcom-flash.ko
fi
led_solid_on
ROOT="$(get_cmdline_param rootfs_path)"
在所有引发内核恐慌的位置调用第二个函数
...
if [ $timeout -eq 10 ]; then
echo "$PART never was probed/brought up" > /dev/kmsg
#exit 1
led_panic_blink
fi
...
if [ ! -f "/mnt/$ROOT" ]; then
echo "Rootfs isn't /mnt/$ROOT" > /dev/kmsg
echo "Couldn't find $ORIG_ROOT" > /dev/kmsg
echo "Contents of '/mnt/$(dirname "$ORIG_ROOT")':" > /dev/kmsg
ls -la "/mnt/$(dirname "$ORIG_ROOT")/" > /dev/kmsg
echo "Halting" > /dev/kmsg
#while true; do sleep 300; done
led_panic_blink
fi
...
echo "Failed to switch root" > /dev/kmsg
led_panic_blink
# Now we kernel panic :(
重新打包initrd,捞出备用。
注意:打包initrd时,应该把模块leds-qcom-flash放进去,否则led节点不会出现
修改根文件系统
对于根文件系统,我们主要需要做两个修改:
- 重新安装内核模块
- 实现LED指示灯服务
内核模块
这个其实比较简单,把编译好的modules覆盖到根文件系统的/lib/modules。
# 挂载镜像
# ...
➜ fajita-mainline sudo rm -rf rootfs/ubuntu/lib/modules
➜ fajita-mainline sudo mv modules rootfs/ubuntu/lib/
LED灯服务
也不麻烦,来一个one-shot服务设置LED的触发器就够了。
创建/etc/systemd/system/sys-led-indicator.service
[Unit]
Description=Configure System LED Status Indicator
After=local-fs.target systemd-udev-settle.service
DefaultDependencies=no
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-system-led.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
激活服务
# chroot进入挂载目录
# chroot ...
systemctl enable sys-led-indicator.service
创建脚本/usr/local/bin/setup-system-led.sh
#!/bin/bash
LED_PATH=$(find /sys/class/leds/ -maxdepth 1 -type l | head -n 1)
if [ -z "$LED_PATH" ]; then
echo "No LEDs found."
exit 0
fi
if grep -q "timer" "$LED_PATH/trigger"; then
echo timer > "$LED_PATH/trigger"
echo 100 > "$LED_PATH/delay_on"
echo 2900 > "$LED_PATH/delay_off"
else
echo heartbeat > "$LED_PATH/trigger"
fi
MAX=$(cat "$LED_PATH/max_brightness")
LOW_BRIGHTNESS=$(( MAX / 10 ))
if [ "$LOW_BRIGHTNESS" -lt 1 ]; then LOW_BRIGHTNESS=1; fi
echo "$LOW_BRIGHTNESS" > "$LED_PATH/brightness"
记得加上可执行权限
chmod +x /usr/local/bin/setup-system-led.sh
FIX
之前发布的无桌面版根文件系统镜像内没有安装wpasupplicant导致nmcli无法控制WIFI,这次顺手给补上。
apt update
apt install wpasupplicant
因为涉及到
chroot,这里推荐使用arm64架构的设备进行操作,否则跨架构chroot的巨大开销会严重拖累性能。
刷机
终于到了这一激动人心的时刻,冲刺冲刺冲!♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️
制作boot镜像
与之前基本相同,不同之处在于,由于不使用屏幕,我们将所有earlycon、earlyprintk等命令移除。
mkbootimg \
--base 0x00000000 \
--kernel_offset 0x00008000 \
--ramdisk_offset 0x01000000 \
--tags_offset 0x00000100 \
--pagesize 4096 \
--second_offset 0x00f00000 \
--ramdisk initrd.cpio.gz \
--cmdline "root=/dev/sda17 rootfstype=ext4 rootwait=10 loglevel=3 rw" \
--kernel kernel-dtb \
-o boot.img
制作rootfs
不是这也要单独写个标题???
img2simg ubuntu-24.04.3.img rootfs-ubuntu-24.04.3-phj.img
(真)刷机
没啥好说的,我们橄榄Android,冲刺冲刺冲!♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️♿️
➜ fajita-mainline fastboot devices
d83621d1 fastboot
➜ fajita-mainline fastboot erase boot_a
Erasing 'boot_a' OKAY [ 0.014s]
Finished. Total time: 0.017s
➜ fajita-mainline fastboot erase boot_b
Erasing 'boot_b' OKAY [ 0.004s]
Finished. Total time: 0.006s
➜ fajita-mainline fastboot erase dtbo_a
Erasing 'dtbo_a' OKAY [ 0.003s]
Finished. Total time: 0.006s
➜ fajita-mainline fastboot erase dtbo_b
Erasing 'dtbo_b' OKAY [ 0.003s]
Finished. Total time: 0.006s
➜ fajita-mainline fastboot erase userdata
******** Did you mean to fastboot format this ext4 partition?
Erasing 'userdata' OKAY [ 1.035s]
Finished. Total time: 1.039s
➜ fajita-mainline fastboot flash boot boot/boot.img
Sending 'boot_b' (23024 KB) OKAY [ 0.563s]
Writing 'boot_b' OKAY [ 0.128s]
Finished. Total time: 0.706s
➜ fajita-mainline fastboot flash userdata rootfs/release/rootfs-ubuntu-24.04.3-phj.img
Warning: skip copying userdata image avb footer (userdata partition size: 118112366592, userdata image size: 140730380310200).
Sending sparse 'userdata' 1/2 (783720 KB) OKAY [ 18.884s]
Writing 'userdata' OKAY [ 0.001s]
Sending sparse 'userdata' 2/2 (626348 KB) OKAY [668.649s]
Writing 'userdata' OKAY [ 0.001s]
Finished. Total time: 687.705s
开机测试
关机,拔掉左边两根排线,只留下供电和尾插,然后开机
✔️ 闪光灯正常

注意: 上面的心跳灯脚本设置闪光亮度为MAX / 10,也就是25,有点过于亮了,实际上1就足够了。
✔️ 内核缓冲区没有驱动在拉屎
athbe@OnePlus6T:~$ dmesg | tail -n 30
[ 4.990213] Bluetooth: hci0: QCA Downloading qca/oneplus6/crnv21.bin
[ 4.992936] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux RX INT7 MIX2 INP has no paths
[ 5.006594] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX9 MUX has no paths
[ 5.017016] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX10 MUX has no paths
[ 5.025137] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX11 MUX has no paths
[ 5.033251] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX11 INP1 MUX has no paths
[ 5.039610] ipa 1e40000.ipa: received modem starting event
[ 5.041754] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX13 MUX has no paths
[ 5.049873] wcd934x-codec wcd934x-codec.1.auto: ASoC: mux CDC_IF TX13 INP1 MUX has no paths
[ 5.065968] input: OnePlus 6T Headset Jack as /devices/platform/sound/sound/card0/input5
[ 5.131016] Bluetooth: hci0: QCA setup on UART is completed
[ 5.138205] qcom-q6v5-mss 4080000.remoteproc: MBA booted without debug policy, loading mpss
[ 5.863241] qcom,slim-ngd-ctrl 171c0000.slim-ngd: QMI wait timeout
[ 6.789129] ipa 1e40000.ipa: received modem running event
[ 6.793602] remoteproc remoteproc2: remote processor 4080000.remoteproc is now up
[ 7.606340] qcom,slim-ngd-ctrl 171c0000.slim-ngd: SLIM SAT: Rcvd master capability
[ 7.742443] qcom,slim-ngd-ctrl 171c0000.slim-ngd: SLIM SAT: Rcvd master capability
[ 7.842858] ath10k_snoc 18800000.wifi: qmi chip_id 0x30214 chip_family 0x4001 board_id 0xff soc_id 0x40030001
[ 7.842907] ath10k_snoc 18800000.wifi: qmi fw_version 0x20050032 fw_build_timestamp 2019-09-10 17:42 fw_build_id QC_IMAGE_VERSION_STRING=WLAN.HL.2.0.c8-00050-QCAHLSWMTPLZ-1
[ 11.139553] ath10k_snoc 18800000.wifi: wcn3990 hw1.0 target 0x00000008 chip_id 0x00000000 sub 0000:0000
[ 11.139581] ath10k_snoc 18800000.wifi: kconfig debug 1 debugfs 1 tracing 0 dfs 0 testmode 0
[ 11.139591] ath10k_snoc 18800000.wifi: firmware ver api 5 features wowlan,mgmt-tx-by-reference,non-bmi crc32 b3d4b790
[ 11.186797] ath10k_snoc 18800000.wifi: htt-ver 3.56 wmi-op 4 htt-op 3 cal file max-sta 32 raw 0 hwcrypto 1
[ 11.285464] ath10k_snoc 18800000.wifi: invalid MAC address; choosing random
[ 11.285849] ath: EEPROM regdomain: 0x0
[ 11.285863] ath: EEPROM indicates default country code should be used
[ 11.285872] ath: doing EEPROM country->regdmn map search
[ 11.285885] ath: country maps to regdmn code: 0x3a
[ 11.285895] ath: Country alpha2 being used: US
[ 11.285904] ath: Regpair used: 0x3a
✔️ WiFi 连接测试
athbe@OnePlus6T:~$ nmcli device wifi list
IN-USE BSSID SSID MODE CHAN RATE SIGNAL BARS SECURITY
72:77:24:52:53:6C -- Infra 11 270 Mbit/s 99 ▂▄▆█ WPA1 WPA2
68:77:24:52:53:6C 啊米浴_2.4G Infra 11 270 Mbit/s 97 ▂▄▆█ WPA1 WPA2
50:4F:3B:DE:DC:D8 说的道理_5G Infra 36 270 Mbit/s 92 ▂▄▆█ WPA2 WPA3
athbe@OnePlus6T:~$ nmcli device
DEVICE TYPE STATE CONNECTION
wlan0 wifi connected 说的道理_5G
usb0 ethernet unmanaged --
lo loopback unmanaged --
p2p-dev-wlan0 wifi-p2p unmanaged --
至此,软件部分就可以告一段落了,下面我们开始折腾硬件。
直供电模块
既然要作为服务器使用,那么长时间的运行是避免不了的。在这种情况下,使用电池供电在稳定性和安全性上都无法接受。因此必须为主板接入直供电模块。
在测试时,我使用的是5V直供电,也就是直接插在电脑USB口上。
- 优点:简单,不需要拆解电池,咸鱼上几块前一条买来插电脑上就能用
- 缺点:功率太低了,5V2A算起来只有10W,一旦开始跑一些吃性能的进程,比如多线程编译,主板会因为供电不足立即重启
所以我们正常使用的直供电模块必须要有降压模块以实现稳压输出。
对于像我一样的手残党,为了尽量减少焊接次数,推荐上淘宝买一块这种板子

它可以直接使用typec接口供电,适配pd协议,并可以将输出稳定在4.3V左右,支持通电自启,甚至还有一个5V0.1A的额外输出(这玩意后面可能会有大用😏)
那么话不多说,开始焊接
这部分省略,毕竟我并不擅长焊接😭
成品

- Type-C (上) pd协议供电
- Type-C (下) 原手机尾插,可以开usb网络进行调试
- 通电自启需要从主板左下角的小焊盘上飞一根线,此焊盘接地=按下开机键(好悬没给我飞死😭)

后续计划
- 如你所见,现在两块板子完全没有固定,拿来拿去的过程中很容易把漆包线折断,所以应该固定一下
- 直供电模块上的5V输出似乎可以用来带一把小风扇,顺便把散热问题解决了

Comments 1 条评论
可以做一下archlinux arm的镜像吗?
虽然ubuntu LTS确实很稳定,但是里面的包也忒老旧了
btw,希望把手机tty的字弄大一点,眼睛快炸了