Document where Moto/DFU entry is, and let a boot key hold PTT plus a key

The bootloader's DFU handler is reachable only when SRAM[0x20000020] is 3, which only a program that then resets can write. The application's 0x05DD takes that path only with ENABLE_OVERLAY; this build resets straight back into the application instead. Ruled out by measurement: PTT alone, PTT+SIDE1/SIDE2, MENU, a host byte in the boot window including 0x0530, and 0x05DD.

boot-key now reads a + separated list, because the firmware's own BOOT_GetMode() needs PTT and a matrix key together for every special boot mode. Tested against all four documented combinations.
This commit is contained in:
mckero committed 2026-10-01 15:06:20 +08:00
1 parent 2667e046e8
commit 1308c98769
5 files changed
+139 -3

No files matched your search

+31
View File
@@ -430,6 +430,37 @@ Two lessons, both general:
The right move was to ask what changed between those two runs, not to distrust the
earlier note.
### Moto/DFU: the entry is a build flag, not a key
The factory bootloader in the first 10 KB does contain a Moto DFU handler at 38400 baud, and
the emulator runs the bootloader correctly. It is nevertheless unreachable from outside, and
the reason is in the bootloader's own code:
0x13f2 ldrb r0, [r4, #0] ; r4 = 0x20000020, a byte in SRAM
0x13f4 cmp r0, #1
0x13f6 beq ...
0x13f8 cmp r0, #2
0x13fa beq ...
0x13fc cmp r0, #3
0x13fe bne ... ; anything else keeps waiting
0x140e bl 0x06f0 ; only mode 3 gets here: the DFU handler
SRAM survives a soft reset and a power cycle does not, so that byte can only be set by a
program that then resets. In the application that is `overlay_FLASH_RebootToBootloader()`,
reached from the serial command `0x05DD` **only when the build defines `ENABLE_OVERLAY`**;
without it the same command is a plain `NVIC_SystemReset()`. Confirmed by sending `0x05DD` to
a running radio: no `0x0518` follows, and the PC never leaves the application.
Four ways in were ruled out by measurement, not by reading: PTT alone (the firmware's own
`BOOT_GetMode()` needs a second key), PTT+SIDE1/SIDE2 and MENU (the application's special
modes), a host byte inside the boot window including the `0x0530` handshake, and `0x05DD`.
Run alone with no valid application the bootloader does not enter DFU either: it stops in one
of the six self-branches at `0x080000dc`, which are hang slots, not a wait for input.
The general lesson: when a firmware's mode is chosen from a byte in RAM, the trigger is not an
input pin -- it is whatever wrote that byte before resetting. Find the writer in the source
(`0x05DD` here) and the `#ifdef` around it, and you have the whole condition.
## The keypad: two real bugs, both fixed
The old note here said "keys reach the firmware but the UI does not react" and
+28
View File
@@ -348,6 +348,34 @@ NUL,`startswith("LCDW")` 永远匹配不上。把同一个文件按 UTF-16 解
- **"以前是好的"就是一条二分指令。** 引导的 Moto 洪水在 FLASH 控制器建模**之前**被观察到,
之后就不再复现。正确的动作是问"这两次之间改了什么",而不是怀疑早先的记录。
### MOTO/DFU:入口是一个编译开关,不是一个按键
前 10 KB 里的出厂引导**确实**含有 38400 波特的 Moto DFU 处理器,模拟器也把引导跑对了。
但它从外部**进不去**,原因就在引导自己的代码里:
0x13f2 ldrb r0, [r4, #0] ; r4 = 0x20000020,SRAM 里的一个字节
0x13f4 cmp r0, #1
0x13f6 beq ...
0x13f8 cmp r0, #2
0x13fa beq ...
0x13fc cmp r0, #3
0x13fe bne ... ; 其它值继续等
0x140e bl 0x06f0 ; 只有 mode 3 走到这里:DFU 处理器
SRAM 能挺过软复位、挺不过断电,所以这个字节只能由「写完就复位」的程序设置。在应用里那是
`overlay_FLASH_RebootToBootloader()`,由串口命令 `0x05DD` 触发——**但只在构建定义了
`ENABLE_OVERLAY` 时**;没有它,同一条命令就是普通的 `NVIC_SystemReset()`。实测:向运行中的
电台发 `0x05DD`,之后**没有** `0x0518`,PC 也从未离开应用区。
四种入口都**用测量**而不是阅读排除了:单独按 PTT(固件自己的 `BOOT_GetMode()` 需要第二个键)、
PTT+SIDE1/SIDE2 与 MENU(那是应用的几个特殊模式)、开机窗口内主机发字节(含 `0x0530` 握手)、
以及 `0x05DD`。只跑引导、没有有效应用时它同样不进 DFU:它停在 `0x080000dc` 那六个自跳转之一,
那些是**挂起槽**,不是在等输入。
普适的教训:当一个固件的模式由 **RAM 里的一个字节**决定时,触发它的一般不是某个输入引脚,而是
复位前写这个字节的那个程序。去源码里找到写入者(这里是 `0x05DD`)和它外面的 `#ifdef`,整个条件
就到手了。
## 键盘:两个真 bug,都已修复
这里原来的笔记写的是"按键到达了固件但界面不反应",并且归咎于机器模型。结果发现有**两个
+30
View File
@@ -381,6 +381,36 @@ slot and resets. Both halves are reachable from the page.
- `tools/uvk5_slots.py` does the same offline: write a slot into a flash image, and print
what each slot holds.
### Moto/DFU flashing, and the flag this build does not set
The factory bootloader is on the machine and it does speak the flashing protocol: a real
`0x0518` / `0x0530` / `0x0519` exchange at 38400 baud, in the 10 KB before the application.
What it will not do is enter that mode from the outside. Measured, not assumed:
| an attempt at entering DFU | what actually happened |
| --- | --- |
| PTT held from reset | an ordinary boot. The firmware's own `BOOT_GetMode()` returns `BOOT_MODE_NORMAL` without a second key |
| PTT+SIDE1, PTT+SIDE2, MENU | the application's special modes (F_LOCK, AIRCOPY, MULTIBOOT), never the bootloader |
| a host byte during the boot window, `0x0530` included | ignored; the PC never leaves the application region |
| the firmware's own `0x05DD` reset command | a plain reset, straight back into the application |
The bootloader's decision is one byte: `ldrb r0,[r4]` with `r4 = 0x20000020`, compared against
1, 2 and 3, where only **3** reaches the DFU handler. That byte is in SRAM, so it survives a
*soft* reset and nothing else: the program already running has to write it and reset. In the
firmware that is `overlay_FLASH_RebootToBootloader()`, and the `0x05DD` path takes it only when
the build defines `ENABLE_OVERLAY`:
case 0x05DD: // reset
#if defined(ENABLE_OVERLAY)
overlay_FLASH_RebootToBootloader();
#else
NVIC_SystemReset(); <-- what this build does
#endif
**So MOTO flashing is not waiting on the emulator.** The bootloader runs, its DFU handler is
present, and the entry condition is known and reproducible; this build is simply not compiled
with the one flag that reaches it. The multi-system release has the same property for the same
reason -- compare the note the page prints for a build with no boot menu.
The layout is the firmware's, from `App/driver/mb_flash.h`: slot 0 at `0x020000` backs up
the internal image, slots 1..4 follow at `0x040000` in 128 KiB steps, the image starts one
4 KiB sector into the slot, and the 64-byte header carries magic `FMB1`, the image size and
+27
View File
@@ -339,6 +339,33 @@ v6.0.0 版把开机菜单和四个固件槽放在外部 flash 里:开机按住
决定(默认 8 秒:开机路径可能花 20 秒把当前固件"采纳"进槽 0,之后才会去采样键盘)。
- `tools/uvk5_slots.py` 做同样的事但离线:把槽写进 flash 镜像,并打印每个槽的内容。
### MOTO/DFU 刷机,以及这份构建没有打开的开关
出厂引导就在机器上,而且它**确实会说刷机协议**:真正的 `0x0518` / `0x0530` / `0x0519` 交互,
38400 波特,在应用区之前的 10 KB 里。它不肯做的,是**从外部**进入那个模式。下面是量出来的,不是猜的:
| 尝试进入 DFU 的方式 | 实际发生了什么 |
| --- | --- |
| 复位起按住 PTT | 普通启动。固件自己的 `BOOT_GetMode()` 在没有第二个键时返回 `BOOT_MODE_NORMAL` |
| PTT+SIDE1、PTT+SIDE2、MENU | 进了应用的几个特殊模式(F_LOCK、AIRCOPY、MULTIBOOT),从不是引导 |
| 开机窗口内主机发字节(含 `0x0530`) | 被忽略;PC 从未离开应用区 |
| 固件自带的 `0x05DD`(reset)命令 | 普通复位,直接回到应用 |
引导的判据只有一个字节:`ldrb r0,[r4]`,`r4 = 0x20000020`,与 1、2、3 比较,只有 **3** 才
会进入 DFU 处理器。这个字节在 SRAM 里,所以它只能挺过**软**复位:必须是**正在运行的程序**写它
然后复位。在固件里那就是 `overlay_FLASH_RebootToBootloader()`,而 `0x05DD` 只有在构建定义了
`ENABLE_OVERLAY` 时才走那条路:
case 0x05DD: // reset
#if defined(ENABLE_OVERLAY)
overlay_FLASH_RebootToBootloader();
#else
NVIC_SystemReset(); <-- 这份构建走的是这里
#endif
**所以 MOTO 刷机不是卡在模拟器上。** 引导能跑、DFU 处理器存在、进入条件已知且可复现;这份固件
构建只是没有打开唯一能走到那里的那个开关。多系统版出于同样的原因有同样的性质——可以参考网页在
没有开机菜单的构建上打印的那句提示。
布局来自固件源码 `App/driver/mb_flash.h`:槽 0 在 `0x020000`,是内部镜像的备份;槽 1..4
从 `0x040000` 起、每 128 KiB 一个;镜像从槽内偏移 4 KiB 开始;64 字节头部含魔数
`FMB1`、镜像大小和 CRC-32。固件自带的 `0x0720`..`0x0727` 串口命令也按同样方式写槽,
+23 -3
View File
@@ -3595,12 +3595,32 @@ static void uvk5_arm_boot_key(UVK5MachineState *s)
if (key == NULL || *key == '\0') {
return;
}
s->boot_key_ptt = (g_ascii_strcasecmp(key, "PTT") == 0);
if (s->boot_key_ptt) {
/*
* "+" holds more than one: the firmware's own BOOT_GetMode() reads PTT and a
* matrix key, and returns F_LOCK for PTT+SIDE1, AIRCOPY for PTT+SIDE2 and
* RESCUE_OPS for PTT plus the key named by the SET_KEY setting. A boot key that
* can only be one of those cannot reproduce any of the special boot modes, which
* is what made the bootloader look unreachable from here.
*/
char **parts = g_strsplit(key, "+", -1);
for (char **part = parts; part != NULL && *part != NULL; part++) {
const char *one = g_strstrip(*part);
if (*one == '\0') {
continue;
}
if (g_ascii_strcasecmp(one, "PTT") == 0) {
s->boot_key_ptt = true;
object_property_set_bool(OBJECT(&s->keypad), "ptt", true, &err);
} else {
object_property_set_str(OBJECT(&s->keypad), "press", key, &err);
object_property_set_str(OBJECT(&s->keypad), "press", one, &err);
}
if (err != NULL) {
error_report("boot-key: %s", error_get_pretty(err));
error_free(err);
err = NULL;
}
}
g_strfreev(parts);
if (err) {
warn_report("boot-key %s: %s", key, error_get_pretty(err));
error_free(err);