前言

一加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灯。

我们计划:

  1. 内核启动完毕,进入initrd -> 常亮
  2. initrd结束,系统被systemd接管 -> 心跳闪烁,三秒一次
  3. 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节点不会出现

修改根文件系统

对于根文件系统,我们主要需要做两个修改:

  1. 重新安装内核模块
  2. 实现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镜像

与之前基本相同,不同之处在于,由于不使用屏幕,我们将所有earlyconearlyprintk等命令移除。

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网络进行调试
  • 通电自启需要从主板左下角的小焊盘上飞一根线,此焊盘接地=按下开机键(好悬没给我飞死😭)

后续计划

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

链接

  1. 将Ubuntu安装到安卓手机(OnePlus6T)中
  2. OnePlus 6T (oneplus-fajita) - postmarketOS wiki
  3. sdm845-mainline/linux - GitLab
  4. 一加6t新Linux刷机包,你的下一台服务器何必是服务器 - bilibili
  5. 直供电+通电自启模块 - 淘宝