-
Notifications
You must be signed in to change notification settings - Fork 2.9k
上海电信中兴B860AV2.1高安版,成功安装armbian,成功写入EMMC #3463
Description
背景:
2017年的盒子,905l(有时候查出来还是905lb),1G+4G,无wifi,无蓝牙,高安版本,在网上可以找到的资料比较少,靠着跟Claude背靠背的合作,成功写入了EMMC。
这个盒子有几个地方很讨厌:
1、无法通过reboot update进入U盘引导
2、TTL无法输入
3、bootloader和boot等分区全部增加了RSA2048算法校验码,只要已修改立刻就死给你看
4、开机后会从内置存储中读取预制的加载程序,修改后后只要重启立刻被重置。
在这样的背景下开始折腾。
这篇分享主要帮助解决三个问题:
1、怎么能让它通过reboot update进入U盘引导
2、即使成功从U盘引导,仍然无法进入U盘中的armbian
3、armbian-install后,系统无限重启,无法进入armbian
问题1、怎么能让它通过reboot update进入U盘引导
在晚上找到了两个可以刷高安版本的固件(针对B860AV2.1T的,但其实好像无所谓),其中一个能进入root安卓,我最开始刷的就是这版(称为A版本),但U盘无法启动。后来找到很多固件都是基于这个固件改的,绝望之际在一个微信公众号中有一篇分享,给出的网盘中发现另一个版本可以刷高安的固件(称为B版本)。尝试后在TTL串口打印里会读取U盘,但安卓没有root,没办法reboot []([](update。于是通过MIK工具,将A版本中的boot.PARTITION文件和system.PARTITION文件替换进了版本B,并重新打包。刷机后即可U盘引导,并进入安卓。通过切换卡载系统V1.0.apk即可进入U盘。我的固件分享在这,在此感谢两位无名的提供者:https://pan.quark.cn/s/7055dd514eda
问题2、即使成功从U盘引导,仍然无法进入U盘中的armbian
从U盘进入后,提示如下错误:
__39 > do_get_rebootmode reboot_mode(0xc810023c)=0x3
Try USB burn
card out
emmc/sd response timeout, cmd = 8
emmc/sd response timeout, cmd = 55
emmc/sd response timeout, cmd = 1
[MSG]mmcinfo failed!
emmc/sd response timeout, cmd = 8
emmc/sd response timeout, cmd = 55
emmc/sd response timeout, cmd = 1
(Re)start USB...
USB0: USB3.0 XHCI init start
Register 2000140 NbrPorts 2
Starting the controller
USB XHCI 1.00
scanning bus 0 for devices... 3 USB Device(s) found
scanning usb for storage devices... 1 Storage Device(s) found
reading aml_autoscript
709 bytes read in 22 ms (31.3 KiB/s)
Executing script at 01080000
Error: "bootfromsd" not defined
Saving Environment to aml-storage...
mmc env offset: 0x38000000
Writing to MMC(1)... reboot use default
从这里可以发现,从U盘执行aml_autoscript失败,原因是bootfromsd没有定义。aml_autoscript 脚本里引用了 ${bootfromsd} 这个环境变量,但这个 ZTE U-Boot 环境里没有定义这个变量,脚本执行到这里就中断退出了。这可能是中兴这个版本固件比较老,对环境变量读取不到就直接报错的原因。
解决方法
找到 U 盘上的 aml_autoscript.cmd(源文件,文本格式),把其中引用 ${bootfromsd} 的逻辑找出来,改成直接执行对应命令,不在通过if判断这个变量。对于只用U盘作为引导的作用来讲,修改后影响不大。修改后需要再编译成二进制。编译好的如下,可以直接解压后覆盖U盘里的同名文件
问题3、armbian-install后,系统无限重启,无法进入armbian
现象是:安装完成后,会出现zte的logo,过一会就自动重启,反复上述过程。串口打印会发现进入kernel后直接崩溃。
原因是每次重启preboot 都会被重置为如下顺序:run init_display; run storeargs; run irremote_update; run switch_bootmode
其中 run storeargs 就是罪魁祸首,这个命令会从 eMMC 的 ZTE 配置分区读取参数,强制把 bootcmd 覆盖回 jpeg ${outputmode};bootsys,因此都会去启动安卓。而安卓的数据区已经被armbian覆盖,自然无法自动。
这里的耗费了很久的时间,尝试了很多绕过的办法都没有成功,最终Claude想到可以通过修改env分区中run switch_bootmode这条命令具体执行的指令来解决。
解决思路大概就是在switch_bootmode中插入run start_emmc_autoscript指令。正是因为ZTE在这里没有做限制,才使得这种方式得以实现。
预期的冷启动流程
preboot(硬编码)
→ run init_display
→ run storeargs (覆盖 bootcmd,无所谓)
→ run irremote_update (等 IR 遥控,超时返回)
→ run switch_bootmode
→ get_rebootmode = cold_boot
→ run try_auto_burn (700ms USB 超时,无 USB 则返回)
→ run start_emmc_autoscript ← 新增,从 eMMC FAT 加载 emmc_autoscript
→ 引导 Armbian ✅
插入这段指令需要直接操作env分区,因此可以在U盘引导并执行完install后,在命令行中执行下面这段代码。
注意:
①DEV = '/dev/mmcblk2'这一段,要根据你的实际设备分区来修改。可以执行lsblk -f,根据结果来改。
②ENV_OFFSET = 0x38000000---要根据你的分区表偏移来改,具体要看你在串口打印。
python3 << 'EOF'
import struct, binascii, os
DEV = '/dev/mmcblk2'
ENV_OFFSET = 0x38000000
ENV_SIZE = 0x10000
with open(DEV, 'rb') as f:
f.seek(ENV_OFFSET)
env_raw = bytearray(f.read(ENV_SIZE))
crc_stored = struct.unpack_from('<I', env_raw, 0)[0]
payload = bytes(env_raw[4:])
crc_calc = binascii.crc32(payload) & 0xFFFFFFFF
assert crc_stored == crc_calc, 'CRC mismatch!'
print(f'CRC valid: {crc_calc:08x}')
entries = payload.split(b'\x00')
real_entries = [e for e in entries if e]
OLD = b'switch_bootmode=get_rebootmode;if test ${reboot_mode} = factory_reset; then run recovery_from_flash;else if test ${reboot_mode} = update; then run update;else if test ${reboot_mode} = cold_boot; then run try_auto_burn; fi;fi;fi;'
NEW = OLD + b'run start_emmc_autoscript;'
modified = False
for i, e in enumerate(real_entries):
if e.startswith(b'switch_bootmode='):
print(f'Before: {e.decode()}')
if e == OLD:
real_entries[i] = NEW
print(f'After: {real_entries[i].decode()}')
modified = True
else:
print('WARNING: switch_bootmode content differs from expected!')
print('Actual:', e)
break
assert modified, 'switch_bootmode not found or not matched!'
new_payload_core = b'\x00'.join(real_entries) + b'\x00\x00'
assert len(new_payload_core) <= len(payload), \
f'Too long! need {len(new_payload_core)}, have {len(payload)}'
new_payload = new_payload_core + b'\x00' * (len(payload) - len(new_payload_core))
new_crc = binascii.crc32(new_payload) & 0xFFFFFFFF
new_env = struct.pack('<I', new_crc) + new_payload
with open(DEV, 'r+b') as f:
f.seek(ENV_OFFSET)
f.write(new_env)
f.flush()
os.fsync(f.fileno())
print(f'Done. New CRC: {new_crc:08x}')
EOF
执行后,如果出现 WARNING: content differs,说明你设备的 switch_bootmode 内容与我略有不同。请手动调整 OLD 变量的值使其与你在串口中看到的对应行的实际内容完全一致(包括末尾的空格),比如我的串口会有这一段:preboot=run init_display;run storeargs;run irremote_update;run switch_bootmode
然后验证写入结果:
python3 << 'EOF'
import struct, binascii, os
DEV = '/dev/mmcblk2'
ENV_OFFSET = 0x38000000
ENV_SIZE = 0x10000
with open(DEV, 'rb') as f:
f.seek(ENV_OFFSET)
env_raw = bytearray(f.read(ENV_SIZE))
# 验证 CRC
crc_stored = struct.unpack_from('<I', env_raw, 0)[0]
payload = bytes(env_raw[4:])
crc_calc = binascii.crc32(payload) & 0xFFFFFFFF
assert crc_stored == crc_calc, 'CRC mismatch!'
print(f'CRC valid: {crc_calc:08x}')
# 把 payload 拆成 entry 列表(去掉末尾的零填充)
entries = payload.split(b'\x00')
# 找到真正有内容的 entries
real_entries = [e for e in entries if e]
# 找到并修改 preboot
OLD_VAL = b'run init_display;run storeargs;run irremote_update;run switch_bootmode;'
NEW_VAL = b'run init_display;run storeargs;run irremote_update;run switch_bootmode;run start_autoscript;'
modified = False
for i, e in enumerate(real_entries):
if e.startswith(b'preboot='):
print(f'Before: {e.decode()}')
real_entries[i] = e.replace(OLD_VAL, NEW_VAL, 1)
print(f'After: {real_entries[i].decode()}')
modified = True
break
assert modified, 'preboot not found!'
# 重新拼装 payload:每个 entry 后接 \x00,末尾用 \x00 填充到原始大小
new_payload_core = b'\x00'.join(real_entries) + b'\x00\x00'
assert len(new_payload_core) <= len(payload), \
f'Too long! need {len(new_payload_core)}, have {len(payload)}'
# 用零填充到原始长度
new_payload = new_payload_core + b'\x00' * (len(payload) - len(new_payload_core))
assert len(new_payload) == len(payload), 'padding error!'
# 计算新 CRC 并写入
new_crc = binascii.crc32(new_payload) & 0xFFFFFFFF
new_env = struct.pack('<I', new_crc) + new_payload
with open(DEV, 'r+b') as f:
f.seek(ENV_OFFSET)
f.write(new_env)
f.flush()
os.fsync(f.fileno())
print(f'Done. New CRC: {new_crc:08x}')
EOF
成功后会输出:
CRC valid: XXXXXXXX
Before: preboot=run init_display;run storeargs;run irremote_update;run switch_bootmode;
After: preboot=run init_display;run storeargs;run irremote_update;run switch_bootmode;run start_autoscript;
Done. New CRC: xxxxxxxx
最后总结一下过程:
1、用UBT更新我上面分享的固件
2、U盘中的aml_autoscript文件替换成我上面提供的
3、armbian-install -a no方式写入EMMC
4、不要重启,立刻执行上面的代码
5、poweroff,拔盘,上电
感慨:
Claude的代码能力超强,能够不停去找解决问题的思路,能够分析你上传给他的文件,能够直接把生成好的文件发给你,能够通过命令行不断帮助你解决问题。上面的文件和代码都是由它提供的。我只不过是搬运工而已。