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
+141 -5

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 The right move was to ask what changed between those two runs, not to distrust the
earlier note. 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 keypad: two real bugs, both fixed
The old note here said "keys reach the firmware but the UI does not react" and 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 洪水在 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,都已修复 ## 键盘:两个真 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 - `tools/uvk5_slots.py` does the same offline: write a slot into a flash image, and print
what each slot holds. 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 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 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 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,之后才会去采样键盘)。 决定(默认 8 秒:开机路径可能花 20 秒把当前固件"采纳"进槽 0,之后才会去采样键盘)。
- `tools/uvk5_slots.py` 做同样的事但离线:把槽写进 flash 镜像,并打印每个槽的内容。 - `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 布局来自固件源码 `App/driver/mb_flash.h`:槽 0 在 `0x020000`,是内部镜像的备份;槽 1..4
从 `0x040000` 起、每 128 KiB 一个;镜像从槽内偏移 4 KiB 开始;64 字节头部含魔数 从 `0x040000` 起、每 128 KiB 一个;镜像从槽内偏移 4 KiB 开始;64 字节头部含魔数
`FMB1`、镜像大小和 CRC-32。固件自带的 `0x0720`..`0x0727` 串口命令也按同样方式写槽, `FMB1`、镜像大小和 CRC-32。固件自带的 `0x0720`..`0x0727` 串口命令也按同样方式写槽,
+25 -5
View File
@@ -3595,12 +3595,32 @@ static void uvk5_arm_boot_key(UVK5MachineState *s)
if (key == NULL || *key == '\0') { if (key == NULL || *key == '\0') {
return; 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
object_property_set_bool(OBJECT(&s->keypad), "ptt", true, &err); * matrix key, and returns F_LOCK for PTT+SIDE1, AIRCOPY for PTT+SIDE2 and
} else { * RESCUE_OPS for PTT plus the key named by the SET_KEY setting. A boot key that
object_property_set_str(OBJECT(&s->keypad), "press", key, &err); * 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", one, &err);
}
if (err != NULL) {
error_report("boot-key: %s", error_get_pretty(err));
error_free(err);
err = NULL;
}
} }
g_strfreev(parts);
if (err) { if (err) {
warn_report("boot-key %s: %s", key, error_get_pretty(err)); warn_report("boot-key %s: %s", key, error_get_pretty(err));
error_free(err); error_free(err);