MD-UV380 / MD-UV390: hosting the AMBE+2 codec
Status: format description, against the reference build (codec-placement.md). The V19.26 CSV codec was run in a host instruction set emulator (measured); nothing has run on a radio.
Rule: the radio runs exactly the codec its own TYT firmware shipped. The user's machine extracts it from a firmware file the user supplies. No codec bytes go in this repository or in released binaries.
Stock files
Published by TYT and mirrored by dealers. Older 18.11 and 18.16 packages exist; they are not described here.
| version | file | size | sha256 | radios |
|---|---|---|---|---|
| V19.26 CSV (profiled) | MD-UV380(CSV)-V19.26.bin |
1303040 | 75e4d470b451cb5d191a647c0836c71801433b826cc4a7fe2c48d750036bd98e |
non-GPS, large contact list |
| D19.26 REC | MD-UV380(REC)-D19.26.bin |
1315328 | 7dec4b751e8a0f44cf7d54d0ebc3b439a9f49899eadd7c8ff14d2804edbe8ac2 |
non-GPS, recording build |
| P19.26 CSV-GPS | MD-UV390(CSV-GPS)-P19.26.bin |
1312256 | 84331dbc9bccf5021122b715dcb77d7589dba0745694bf3a249aae6e7c383ef1 |
GPS, large contact list |
| S19.26 GPS-REC | MD-UV390(GPS-REC)-S19.26.bin |
1324544 | 77923d67aa75a07f7601a1c53ae1d5f5af37bd4367d52a50c8aeff89b834eceb |
GPS, recording build |
The four files are for the 5 W radios. A "Plus 10 W" file is not described (?). The header carries no model string, so a user must pick the file for their radio; for RadioFoundry any of the four is only a codec donor and works on any MD-UV380 family radio, because the codec is the same object code (below).
Container and encryption
The "OutSecurityBin" two-segment form, as for the reference build:
| offset | contents |
|---|---|
| 0x00 | OutSecurityBin, two zero bytes |
| 0x10 | model field, blank (0xFF) |
| 0x30 | 02 17 0C 03 ... in the non-GPS files, 02 16 0C 03 ... in the GPS files (meaning unknown, ?) |
| 0x7C | u32 segment count, 2 |
| 0x80 | (0x00060000, 0x5D400): SPI flash resources; (0x0800C000, length): the application |
| 0x100 | payload |
Application lengths: V19.26 0xE0C00, D19.26 0xE3C00, P19.26 0xE3000, S19.26 0xE6000. The application starts at file
offset 0x5D500. The payload is XORed with the MD-UV380 bootloader key (SHA-256
739ce35cc8db6e72f95598effac1a01ae71dbe2a5792c1a69acaee5dbf634991, first bytes 00 AA 89 89, the mduv380 model of
tools/stm32_dfu.py):
plain[i] = file[0x100 + i] ^ key[i % 1024]
For an application address A: file offset = A - 0x0800C000 + 0x5D500, key index = (file offset - 0x100) % 1024.
Key recovery (measured). rfoundry/stockfw.derive_key(stock, <the hash above>) recovers the key from each of
the four files and checks it by that hash: in about 0.2 s for V19.26, D19.26 and S19.26, and about 5 s for P19.26.
No key file is needed.
Decrypted application vector tables: SP 0x20002848 in all four; reset 0x080E8A09 (V19.26), 0x080EB999 (D19.26), 0x080EAE69 (P19.26), 0x080EDE39 (S19.26).
Codec in the vendor image (V19.26 CSV)
| window | flash | length | file offset | key phase | sha256 (decrypted) |
|---|---|---|---|---|---|
| code | 0x0807B830..0x0808ACEA | 0xF4BA | 0xCCD30 | 0x030 | 5de390664c831412f37ae9ec1f8aadaf77d93112bded424e5bfc9d6b77a64aa1 |
| tables | 0x080D8C94..0x080EB580 | 0x128EC | 0x12A194 | 0x094 | a5c80b51b638d38c714b3040526d89ab8ed1c193ed5524fa09558f560e7d35fa |
First word of the code window: 0x4FF0E92D, as in the reference build.
- Code window. Execution covers 0x0807B830..0x0807C872 and 0x0807DD4C..0x0808ACEA; the gap between holds other code that the codec never executes, carried to keep the window contiguous.
- Table window. The constant tables at the reference table window's layout shifted by +0x72A4 (start) to +0x7340 (most tables), plus one codec code island at 0x080DFF3C..0x080E0024 (the reference build's is at 0x080D8BFC). Starting it at 0x080D8C98 changes the output. Its end, 0x080EB580, is the reference window end + 0x7340 (not minimized). No small extra windows were needed.
Other files: the head is at 0x0807E3C0 (D19.26), 0x0807DBF0 (P19.26), 0x080807B4 (S19.26). Their windows have the same shape at the same offsets from the head (not cut or checked).
Entry points and calling convention
All AAPCS, Thumb, the reference build's argument lists and semantics ("Per-frame calls" in its codec page). The offsets from the code start are the reference build's (another hosted family has different ones):
| call | offset | V19.26 CSV | D19.26 | P19.26 | S19.26 |
|---|---|---|---|---|---|
| encode | +0x130 | 0x0807B960 | 0x0807E4F0 | 0x0807DD20 | 0x080808E4 |
| encoder set | +0x1C0 | 0x0807B9F0 | 0x0807E580 | 0x0807DDB0 | 0x08080974 |
| encoder init | +0x284 | 0x0807BAB4 | 0x0807E644 | 0x0807DE74 | 0x08080A38 |
| FEC init | +0x4CC | 0x0807BCFC | 0x0807E88C | 0x0807E0BC | 0x08080C80 |
| encode ECC | +0x4E8 | 0x0807BD18 | 0x0807E8A8 | 0x0807E0D8 | 0x08080C9C |
| decode | +0x5D8 | 0x0807BE08 | 0x0807E998 | 0x0807E1C8 | 0x08080D8C |
| decoder set | +0x688 | 0x0807BEB8 | 0x0807EA48 | 0x0807E278 | 0x08080E3C |
| decoder init | +0x6D0 | 0x0807BF00 | 0x0807EA90 | 0x0807E2C0 | 0x08080E84 |
| encoder parameters | +0x848 | 0x0807C078 | 0x0807EC08 | 0x0807E438 | 0x08080FFC |
| FEC decode | +0xB54 | 0x0807C384 | 0x0807EF14 | 0x0807E744 | 0x08081308 |
Only the V19.26 CSV column has been executed. The init sequence is the hosted builds': encoder init (E, 1, 1), FEC init (each FEC block, 1), decoder init (D, 1), encoder parameters (E, -22, 20, -10, stack: -40, 0x4000, 3276, 3276, 1310, 10, 20), encoder set (E, 128, -20), decoder set (D, 128, -20), one decode of an all-zero frame with flags 8. The sequence is the reference build's, and the output matches it.
State sizes
With every block ending at an unmapped page: encoder state 0x16E0, decoder state 0x7EC, FEC state 0x44 each run without a fault (the sizes of the hosted builds; smaller sizes were not tried). Stack peak 3822 bytes from the entry. No fixed RAM address is touched.
Checks (measured)
In a host emulator (Thumb, Cortex-M4) with only the two windows, the state blocks, the I/O buffers and a stack mapped. For a 1 kHz tone (30 frames) and a 300 frame stress run (noise, silence, full scale sweeps, injected bit errors, random frames, flags 8), the V19.26 CSV codec's air bits and decoded PCM are bit-identical to the reference codec's, and the FEC decode results agree (0 and 4).
Hosting
Run in place at the vendor addresses, as for the reference build:
- The code window spans flash sectors 7 and 8 (0x08060000..0x0809FFFF) and the table window sectors 10 and 11 (0x080C0000..0x080FFFFF), exactly the reference build's sector use. So its linker plan applies unchanged: the application gets sectors 3-6 (0x0800C000..0x0805FFFF, 336 KB), sector 9 is free, and an update that writes only sectors 3-6 keeps the codec ("keep codec" works).
- Boot check: first word 0x4FF0E92D at 0x0807B830, then the CRC or hash of both windows; otherwise FM only.
- One profile per firmware file, keyed by its SHA-256 (suggested ids
mduv380-v1926-csv,mduv380-d1926,mduv390-p1926,mduv390-s1926). The windows move between files, so the application's sector budget is the same for all four (every head is in sector 7, every table window in sectors 10 and 11).
Extraction recipe (V19.26 CSV)
- Check the file's SHA-256 (
75e4d470...) and the two-segment header. - Get the key: derive it from the file (above) or a key file checked by its SHA-256.
- Decrypt file[0xCCD30 .. +0xF4BA] and file[0x12A194 .. +0x128EC]. Check the first word and both SHA-256 values.
- Write both unmodified at 0x0807B830 and 0x080D8C94 in the image. The merge happens on the user's machine, never in a release.
Open items
- Minimize the V19.26 window ends and the state sizes.
- Cut, hash and check D19.26, P19.26 and S19.26; add their profiles.
- The 18.11 / 18.16 packages and any Plus 10 W file.
- On a radio: TX and RX voice through the codec at its vendor addresses.