diskOS installer: initial public beta
Flashes the diskOS custom UI onto the FiiO Snowsky Disc over Ingenic mask-ROM USB, building the image from your own stock firmware. Runs from source via install.sh.
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
# Snowsky Disc V2.09 - mq_player full command map
|
||||
|
||||
Dispatch entry = `{tag_str_ptr, handler_thunk_ptr}` (8B); thunks tail-jump via GOT to the real handler.
|
||||
Confidence: **V**=string-verified · **I**=inferred from family · **U**=unknown (runtime-registered, GOT slot 0).
|
||||
⚠ = static reading conflicts with our 1.95-era LIVE tests (trust live; V2.09 meanings need live re-verify).
|
||||
|
||||
## SOURCE / WORK-MODE SWITCH - 0657 (corrected; supersedes "close player" reading)
|
||||
`0657` = switch audio SOURCE, payload = integer mode index (jump table @0x6736b0). Frame = `0657000C000<hex>`:
|
||||
- `0657000C0001` = **LOCALPLAYER** (safe recover) · `0657000C0009` = **USB-DAC (UAC)** · `0657000C000C` = **BTSINK**
|
||||
- **Network receivers (AirPlay/DLNA/Roon):** `0657000C0008` PLUS companion `0642000C000X` (NETWORK_MODE selector, X in {0,1,2,5}; exact AirPlay value UNMAPPED). Prereq: WLAN up.
|
||||
- ✅ **PRE-STOP PINNED + CONFIRMED (2026-08-03, live strace of stock mq_ui + fixed on device):** the pre-stop is **`0666000C0006`** (out_dev=6, local). Stock always sends it BEFORE `0666000C0002` (route to BT). Skipping it = the **g_fiio_local trap** → mq_player SIGSEGV → SD freed → MCU reboot. Mechanism: direct→2 can leave shadow-out=2 with DAC-flag=1 (split state); 6→2 normalizes DAC-flag=0. **This was THE cause of every BT-route reboot.** See "BT AUDIO OUTPUT" section below.
|
||||
- ⚠ AirPlay discovery CANNOT be tested on an iPhone Personal Hotspot (client/mDNS isolation) - needs a normal router. PARKED pending router + 0666 pre-stop.
|
||||
NB: this 0657 is the V2.09 SOURCE switch; the 1.95 "0657=play-mode" was a different binary/table. Our earlier play-mode toggle using 0657 on V2.09 was therefore WRONG (it sent source-switch values) - FIXED 2026-06-25 (now uses 0102, below).
|
||||
|
||||
## PLAY-MODE - 0102 (GROUND TRUTH, captured from stock UI 2026-06-25)
|
||||
`0102` = LOCAL play-mode setter. Frame = `0102000C000<v>`. Captured by strace'ing the stock
|
||||
`/usr/bin/mq_ui`'s `mq_timedsend` while tapping its play-mode control (the loop icon, NP
|
||||
transport page) - the stock UI cycles these 5 values 0→1→2→3→4→0:
|
||||
| value | frame | stock icon | mode |
|
||||
|---|---|---|---|
|
||||
| 0 | `0102000C0000` | →→ (two arrows) | Sequential (play in order) |
|
||||
| 1 | `0102000C0001` | ⇄ (crossed) | Shuffle (random) |
|
||||
| 2 | `0102000C0002` | ↻ with "1" | Repeat One (single loop) |
|
||||
| 3 | `0102000C0003` | ↻ (loop) | Repeat All (list loop) |
|
||||
| 4 | `0102000C0004` | "1" + arrow | Single (play one track, stop) |
|
||||
NB: this SUPERSEDES the old "0102 = Roon-only no-op" reading - stock uses 0102 for LOCAL
|
||||
play-mode live. diskOS sends this via ui_set_workmode (main.c). The cycle order above is
|
||||
the stock order; diskOS's prior 4-mode icons (seq/shuffle/repeat-one/repeat-all) already
|
||||
match values 0-3.
|
||||
|
||||
## LIVE-tested (ground truth)
|
||||
| tag | meaning |
|
||||
|---|---|
|
||||
| 0100 | Play by list (list_type + start index) ✅ |
|
||||
| 0102 | **Play-mode** 0102000C000<0..4> (seq/shuffle/rep-one/rep-all/single) ✅ stock-captured 2026-06-25 |
|
||||
| 0103 | Seek (ms) ✅ verified live 2026-06-25 (pos jumped to target) |
|
||||
| 0104 | Favorite toggle (current song) |
|
||||
| 0201 | Transport play/pause toggle (generic, VALUE1-driven) - verified NOT Roon-specific |
|
||||
| 0622 | Rescan SD / rebuild DB |
|
||||
| 0657 | **SOURCE switch** (NOT play-mode) - see section above |
|
||||
| 0666 | Output route (→set_out_device 0x461e74): 2=BTSRC 4=SPDIF 6=local-DAC. V2.09-confirmed; the "close_player" sighting was a shared teardown preamble, not this cmd's meaning. |
|
||||
| 0715 | Volume absolute 0-120 ✅ verified 2026-06-25 (set 20 via 0715000C0014, persisted+displayed) |
|
||||
|
||||
## BT AUDIO OUTPUT (transmit to a BT speaker, a2dp-source) - ✅ WORKING (2026-08-03)
|
||||
Captured from a live strace of stock mq_ui doing a working transmit, then replicated + fixed in diskOS `ui_route_bt()`. **Plays stereo, no stutter, no reboot.** The device IS designed to transmit (not only the "Bluetooth Receiving Mode"/a2dp-sink); stock transmit works but STUTTERS because its default-quality stereo SBC exceeds the X2000 CPU.
|
||||
|
||||
Working route-to-BT sequence (diskOS, MAC = the connected speaker, uppercased):
|
||||
```
|
||||
0666000C0006 PRE-STOP: switch output to LOCAL first (MANDATORY - skip = g_fiio_local crash → MCU reboot)
|
||||
0642000C0000 reset network/output mode
|
||||
0657000C0008 work-mode 8
|
||||
06c1000C0000 start player BT-init thread (06b3 no-ops until this completes; async on cold start)
|
||||
0666000C0002 route: out_dev = BT source
|
||||
0657000C0008 work-mode 8 (again, as stock does)
|
||||
06b3001D0000<MAC> codec select: 0=SBC (our bluealsa is sbc-only) + MAC payload (stock frame-shape; worker ignores it)
|
||||
0715000C<vol> volume
|
||||
```
|
||||
Then play normally (`0100…`). Reverse (BT→local) = `ui_route_analog()`: `0666000C0006` then `0657000C0008`.
|
||||
**No-stutter requires bluealsa `--sbc-quality=medium`** (bit-pool ~33): stock default-quality stutters; medium plays clean stereo (~86% CPU idle on device). Set in `bt.c` bt_enable/bt_ensure_services. `--a2dp-force-mono` also works but is NOT needed (stereo is fine at medium quality).
|
||||
|
||||
## Table A @0x7c9d30 - 131 entries (terminator 0x7ca148)
|
||||
| tag | thunk | meaning | conf |
|
||||
|---|---|---|---|
|
||||
| 0802 | 0x411790 | get total song count (SELECT count(*) FROM SONG) | V |
|
||||
| 0620 | 0x4117b0 | get WIFI MAC (/usr/data/fiio/nb.txt) | V |
|
||||
| 0710 | 0x4117d0 | network/wifi getter | I |
|
||||
| 0711 | 0x4117f0 | network/wifi getter | I |
|
||||
| 0712 | 0x411810 | network/wifi op | I |
|
||||
| 0713 | 0x411830 | network/wifi op | I |
|
||||
| 0714 | 0x411850 | network/wifi op | I |
|
||||
| 0715 | 0x411870 | network/wifi op (NB: vs live 0715=volume - table-A 0715 differs) | I |
|
||||
| 0716 | 0x411890 | network/wifi op | U |
|
||||
| 0601 | 0x4118b0 | media getter | U |
|
||||
| 0651 | 0x4118d0 | media getter/setter | U |
|
||||
| 0602 | 0x4118f0 | media getter | U |
|
||||
| 0652 | 0x411910 | media getter/setter | U |
|
||||
| 0606 | 0x411930 | media DB getter | I |
|
||||
| 0656 | 0x411950 | media DB getter/setter | I |
|
||||
| 0603 | 0x411970 | media DB getter | I |
|
||||
| 0653 | 0x411990 | media DB getter/setter | I |
|
||||
| 0604 | 0x4119b0 | media DB getter | I |
|
||||
| 0654 | 0x4119d0 | media DB getter/setter | I |
|
||||
| 0605 | 0x4119f0 | media query -> reply ([PLAY] send) | V |
|
||||
| 0655 | 0x411a10 | media DB getter/setter | I |
|
||||
| 0608 | 0x411a30 | media DB getter | I |
|
||||
| 0658 | 0x411a50 | media DB getter/setter | I |
|
||||
| 0611 | 0x411a70 | media DB getter | I |
|
||||
| 0661 | 0x411a90 | media DB getter/setter | I |
|
||||
| 0612 | 0x411ab0 | media DB getter | I |
|
||||
| 0662 | 0x411ad0 | media DB getter/setter | I |
|
||||
| 0613 | 0x411af0 | media DB getter | I |
|
||||
| 0663 | 0x411b10 | media DB getter/setter | I |
|
||||
| 0609 | 0x411b30 | media DB getter | I |
|
||||
| 0659 | 0x411b50 | media DB getter/setter | I |
|
||||
| 0615 | 0x411b70 | media DB getter | I |
|
||||
| 0665 | 0x411b90 | media DB getter/setter | I |
|
||||
| 0614 | 0x411bb0 | media DB getter | I |
|
||||
| 0664 | 0x411bd0 | media DB getter/setter | I |
|
||||
| 0607 | 0x411bf0 | media query -> reply ([PLAY] send) | V |
|
||||
| 0657 | 0x411c10 | reads "close_player"/killall avahi-publish (⚠ 1.95=play-mode) | V⚠ |
|
||||
| 0801 | 0x411c30 | playback/system getter | I |
|
||||
| 0702 | 0x411c50 | wifi/network getter | I |
|
||||
| 0703 | 0x411c70 | wifi/network getter | I |
|
||||
| 0704 | 0x411c90 | get wifi scan list | V |
|
||||
| 0705 | 0x411cb0 | connect wifi (psid/passwd) / SET_POWER_DOWN_TO_MCU | V |
|
||||
| 0706 | 0x411cd0 | wifi/network op | I |
|
||||
| 0707 | 0x411cf0 | wifi/network op | U |
|
||||
| 0708 | 0x411cf0 | wifi/network op | U |
|
||||
| 0639 | 0x411d30 | media-info query -> reply a639 | V |
|
||||
| 0689 | 0x411d50 | **EQ PRESET SELECT** - invokes 21-way preset engine 0x449544 (via 0x4961bc), updates rate caps, replies a639. NOT a media-info query. | V |
|
||||
| 0690 | 0x411d70 | media-info query -> play-send | V |
|
||||
| 0675 | 0x411d90 | get EQ/PEQ (gain/filterType/frequency/loading) | V |
|
||||
| 0626 | 0x411db0 | EQ getter (family) | I |
|
||||
| 0627 | 0x411dd0 | EQ getter | I |
|
||||
| 0677 | 0x411df0 | EQ getter/setter | I |
|
||||
| 0628 | 0x411e10 | EQ getter/setter (PEQ) | I |
|
||||
| 0678 | 0x411e30 | SET PEQ band (filterType/frequency/gain/qValue) | V |
|
||||
| 0629 | 0x411e50 | EQ getter/setter | I |
|
||||
| 0630 | 0x411e70 | get EQ (gain/filterType/frequency/loading) | V |
|
||||
| 0803 | 0x411e90 | playback/system getter | I |
|
||||
| 0724 | 0x411eb0 | system/OTA op | I |
|
||||
| 0720 | 0x411ed0 | OTA/network (killall wget, ip route, wlan0) | V |
|
||||
| 0624 | 0x411ef0 | mount/storage path (mnt/) | V |
|
||||
| 0622 | 0x411f10 | system getter (NB: live 0622=rescan; table-A differs) | I |
|
||||
| 0800 | 0x411f30 | write wpa_supplicant.conf (country=%s) | V |
|
||||
| 0623 | 0x411f50 | system getter | I |
|
||||
| 0616 | 0x411f70 | media/system getter | I |
|
||||
| 0a51 | 0x411f90 | extended command (only 0aXX tag) | I |
|
||||
| 0634 | 0x411fb0 | media-info query -> play-send | V |
|
||||
| 0684 | 0x411fd0 | media getter | I |
|
||||
| 0725 | 0x411ff0 | system/OTA op | I |
|
||||
| 0617 | 0x412010 | media/system getter | I |
|
||||
| 0667 | 0x412030 | media getter/setter | I |
|
||||
| 0621 | 0x412050 | DROP DB tables (SONG/MY_LOVE/PLAY_LIST) | V |
|
||||
| 06a0 | 0x412070 | BT/media getter | I |
|
||||
| 06a1 | 0x412090 | BT/media getter | I |
|
||||
| 06a3 | 0x4120b0 | BT/media getter | I |
|
||||
| 0640 | 0x4120d0 | media getter/setter | I |
|
||||
| 0644 | 0x4120f0 | media getter/setter | I |
|
||||
| 0643 | 0x412110 | media op | U |
|
||||
| 06a2 | 0x412130 | BT/media getter | I |
|
||||
| 06b1 | 0x412150 | BT sample-rate param (→0x496ac4→change_rate_set_params 0x40d4d8; VALUE1 selects 44100/48000/82000?/96000) | V |
|
||||
| 06b2 | 0x412170 | BT bit-depth param (→0x496b58→change_format_set_param 0x40d66c; 16/24/32-bit) | V |
|
||||
| 06b3 | 0x412190 | **BT CODEC SELECT** (→0x496bb8→worker 0x40eaf4): VALUE1 0=SBC 1=AAC 2=LDAC-mob 3=LDAC-std 4=LDAC-high. Stock's connect callback sends `06b3<len>000X<MAC>` (X=persisted BT_CODEC, MAC as ignored-by-worker payload for frame-shape). **diskOS sends `06b3001D0000<MAC>` (X=0 SBC, our bluealsa is `--codec=sbc` only) - value 3 only "works" on an SBC sink via a fragile `/usr/data/bt_codec` fallback, so pick the codec our bluealsa actually enables.** Worker requires `06c1` BT-init done first (else no-ops). | V (live) |
|
||||
| 06b4 | 0x4121b0 | LDAC **quality** only (→0x496c94→0x40dbe8 via /usr/data/bt_pipe_recv): VALUE1 0=mobile 1=standard 2=high. Does NOT select SBC or set rate. | V |
|
||||
| 06b6 | 0x4121d0 | BT op | I |
|
||||
| 06b7 | 0x4121f0 | BT get paired addr+name | V |
|
||||
| 06b8 | 0x412210 | BT send connected device list | V |
|
||||
| 06c3 | 0x412230 | BT op | I |
|
||||
| 06c2 | 0x412250 | BT op | I |
|
||||
| 06c0 | 0x412270 | BT trust/power (trust/power off/hci down) | V |
|
||||
| 06c4 | 0x412290 | BT disconnect/remove (bluetooth.db) | V |
|
||||
| 06c5 | 0x4122b0 | BT op | I |
|
||||
| 06c1 | 0x4122d0 | BT set device alias | V |
|
||||
| 0679 | 0x4122f0 | media getter/setter | I |
|
||||
| 0666 | 0x412310 | reads close_player (⚠ 1.95=output route) | V⚠ |
|
||||
| 06b5 | 0x412330 | BT op | I |
|
||||
| 0804 | 0x412350 | playback/system getter | I |
|
||||
| 0805 | 0x412370 | playback/system op | U |
|
||||
| 0701 | 0x412390 | wifi init/restart (init_wifi; killall udhcpc/wpa) | V |
|
||||
| 0700 | 0x4123b0 | close network card (network.c) | V |
|
||||
| 0808 | 0x4123d0 | playback/system getter | I |
|
||||
| 0813 | 0x4123f0 | playback/system getter | I |
|
||||
| 0816 | 0x412410 | playback/system getter | I |
|
||||
| 0818 | 0x412430 | playback/system getter | I |
|
||||
| 0817 | 0x412450 | playback/system getter | I |
|
||||
| 0811 | 0x412470 | playback/system getter | I |
|
||||
| 0809 | 0x412490 | playback/system getter | I |
|
||||
| 0815 | 0x4124b0 | set MEMORY_PLAY position (UPDATE ... POSITION) | V |
|
||||
| 0810 | 0x4124d0 | playback/system getter/setter | I |
|
||||
| 0812 | 0x4124f0 | playback/system getter/setter | I |
|
||||
| 0806 | 0x412510 | playback/system getter | I |
|
||||
| 0645 | 0x412530 | media getter/setter | I |
|
||||
| 0646 | 0x412550 | media getter/setter | I |
|
||||
| 0807 | 0x412570 | playback/system getter | I |
|
||||
| 0660 | 0x412590 | media op | U |
|
||||
| 0610 | 0x4125b0 | media op | U |
|
||||
| 0687 | 0x4125d0 | media getter | I |
|
||||
| 0637 | 0x4125f0 | media op | U |
|
||||
| 0814 | 0x412610 | playback/system getter/setter | I |
|
||||
| 0642 | 0x412630 | media getter/setter | I |
|
||||
| 0641 | 0x412650 | media-info query -> play-send | V |
|
||||
| 0618 | 0x412670 | media op | U |
|
||||
| 0668 | 0x412690 | media op | U |
|
||||
| 0619 | 0x4126b0 | media op | U |
|
||||
| 0669 | 0x4126d0 | media op | U |
|
||||
| 0722 | 0x412710 | OTA update (wget ota_patch_user.json) | V |
|
||||
| 0820 | 0x412730 | set key SINGLE-click action | V |
|
||||
| 0821 | 0x412750 | set key DOUBLE-click action | V |
|
||||
| 0822 | 0x412770 | set key LONG-press action | V |
|
||||
| 06b9 | 0x4126f0 | BT op | U |
|
||||
| 0647 | 0x412790 | set gapless | V |
|
||||
| 0648 | 0x4127b0 | set artist_class_type | V |
|
||||
| 0649 | 0x4127d0 | system-config op | U |
|
||||
|
||||
## Table B @0x7ca150 - 75 entries (terminator 0x7ca3a8); many NULL=unimplemented
|
||||
| tag | handler | meaning | conf |
|
||||
|---|---|---|---|
|
||||
| 0425 0435 0445 | NULL | unimplemented | V |
|
||||
| 0501 | 0x4130f0 | media/thumb type (png/gif/mp4) -> a501/a60a | I |
|
||||
| 0105 | 0x413110 | emits a102 (status/ack) | I |
|
||||
| 0202 | 0x413130 | NAS getter/setter | I |
|
||||
| 0599 | 0x413150 | system/version (-> a599) | I |
|
||||
| 0401 | 0x413170 | boolean setter -> a401 | I |
|
||||
| 0411 0450 0451 0452 0453 | NULL | unimplemented (04xx settings) | V |
|
||||
| 0402 | 0x413190 | boolean setter -> a402 | I |
|
||||
| 0416 0460 0461 0462 | NULL | unimplemented | V |
|
||||
| 0403 | 0x4131b0 | boolean setter -> a403 | I |
|
||||
| 0410 0470 0471 0473 0474 | NULL | unimplemented | V |
|
||||
| 0404 | 0x4131d0 | setter -> a404 | I |
|
||||
| 0417 0480 0481 0482 | NULL | unimplemented | V |
|
||||
| 0405 0418 0419 0420 0421 0422 | NULL | unimplemented (replies declared) | V |
|
||||
| 0406 | 0x4131f0 | setter -> a406 | I |
|
||||
| 0426 0407 | NULL | unimplemented | V |
|
||||
| 0502 | 0x413210 | 05xx misc -> a502 | I |
|
||||
| 0201 | 0x413230 | Transport play/pause toggle (generic, VALUE1-driven → 0x419ff4) - verified NOT Roon-specific | V |
|
||||
| 0102 | 0x413250 | playback control (transport) | I |
|
||||
| 0104 | 0x413270 | playback control (72-byte frame; live: favorite) | I |
|
||||
| 0111 | 0x413290 (GOT0) | unimplemented stub | V |
|
||||
| 0112 | 0x4132b0 | playlist: add song (playlist idx, song_path) | V |
|
||||
| 0115 | 0x4132d0 | add to custom list | V |
|
||||
| 0113 | 0x4132f0 | love-list delete | V |
|
||||
| 0114 | 0x413310 | custom-list delete | V |
|
||||
| 0116 | 0x413330 | playlist op (list mutation) | I |
|
||||
| 0203 | 0x413350 | NAS op (shares worker w/0412) | I |
|
||||
| 0412 | 0x413370 | -> reply a412 | I |
|
||||
| 0413 | 0x413390 | album query (unknown_album) -> a413 | V |
|
||||
| 0414 | 0x4133b0 | style/genre query (unknown_style) -> a414 | V |
|
||||
| 0415 | 0x4133d0 | query w/ strcmp -> a415 | I |
|
||||
| 0465 | 0x4133f0 | -> a465 | I |
|
||||
| 0495 0496 0497 0498 | NULL | unimplemented | V |
|
||||
| 0408 | 0x413410 | file/folder browse (mnt/, path build) -> a408 | V |
|
||||
| 0490 0491 0409 | NULL | unimplemented | V |
|
||||
| 0117 | 0x413490 | playlist reorder/move (src_list/dst_list) | V |
|
||||
| 0463 0464 0483 0484 | GOT0 | unimplemented stubs | V |
|
||||
| 0210 | 0x4134b0 (GOT0) | NAS stub | V |
|
||||
| 0211 | 0x4134d0 (GOT0) | NAS (json_data, /tmp/nas/) stub | V |
|
||||
| 0212 0213 0214 | GOT0 | NAS folder op stubs | V |
|
||||
| 0103 | 0x413430 | song-info/get op | I |
|
||||
| 0100 | 0x413450 | MAIN PLAY (jumptable @0x650e2c by list_type; unknown_artist) | V |
|
||||
| 0101 | 0x413470 (GOT0) | unimplemented stub (a101 declared) | V |
|
||||
|
||||
## MCU / SPI name-commands (hw_ctrl.c @0x48d0b8, over internal SPI to MCU)
|
||||
GET_FIRMWARE_VERSION · GET/SET_DEVICE_MAX_VOL · GET/SET_BALANCED_VOL · REPORT/READ_DEVICE_VOL · SET_DEVICE_VOL ·
|
||||
REPORT_LCD_ACTION · GET/SET_INPUT_MODE · SET_OUTPUT_MODE · SET_GAIN · GET/SET_DAC_FILTER · SET_USB_MODE ·
|
||||
GET/SET_EQ_PRE · GET/SET_EQ_PARAMETER · SET_EQ_RESET · SAVE_EQ/RESAVE_EQ · SET/REPORT_AUDIO_FORMAT ·
|
||||
MCU/ARM_REPORT_STATUS · SET_FACTORY · ENTER_MCU_UPDATE_MODE/REPORT_UPDATE_STATUS ·
|
||||
GET/SET_ZERO_DATA_DETECT_TIME + ZERO_DATA_STATUS · SET_MCU_POWER · SET_MUTE ·
|
||||
SET_STATUS_TO_MCU/SET_POWER_DOWN_TO_MCU (shutdown, 63 call sites) · BT_REPORT_RATE/STATE/CODEC_TO_MCU
|
||||
|
||||
## sysconfig-key setters (system_c... @0x488000)
|
||||
RGB_COLOUR · TRIGGER_IN · SYS_THEME · LANGUAGE · USB_MODE · NETWORK_MODE · EQ_TYPE · MAX_VOL · BALANCE_VOL ·
|
||||
POWER_SAVE · FILTER_TYPE · PLAY_MODE · FOLDER_JUMP · PLAY_GAP · INPUT_MODE · VOL_KNOB_MODE · OTA_CFG ·
|
||||
PO_PRE_VOL · PO_VOL · PRE_VOL · TREBLE · BASS · LO_DISABLE · OS_MODE · DSD_DECODE
|
||||
|
||||
## Reply frames
|
||||
~102 `axxx` player->UI frames. Known: a639=media-info, a706=net status, a714=volume, aa1b=UAC srate, a644=now-playing JSON, a704/a705=wifi status, a6c*=BT. Most undocumented.
|
||||
@@ -0,0 +1,298 @@
|
||||
# Snowsky Disc - Hardware Capability Map
|
||||
|
||||
Live-probed from the device root shell (serial `/dev/ttyACM0`) on 2026-06-25, while
|
||||
running **stock firmware** (V2.09 family, kernel built 2026-06-03). This documents what
|
||||
the *hardware* can do, independent of what stock software chooses to use - to scope
|
||||
diskOS enhancements and the "write our own mq_player/mq_ui" question.
|
||||
|
||||
Probing was read-only (`/proc`, `/sys`, `aplay --dump-hw-params`, `i2cdetect`-equivalent
|
||||
via sysfs names, `dmesg`). Nothing was written to the device during this survey.
|
||||
|
||||
---
|
||||
|
||||
## 1. SoC & Compute
|
||||
|
||||
| Item | Value | Source |
|
||||
|---|---|---|
|
||||
| SoC | Ingenic **X2000** (xburst2), board `ingenic,x2000_halley5_module_base` | `/proc/cpuinfo` |
|
||||
| Cores | **2× XBurst II V2**, SMP | `/proc/cpuinfo` (processor 0,1) |
|
||||
| Clock | ~1.2 GHz (BogoMIPS ≈ 2390) | `/proc/cpuinfo` |
|
||||
| FPU | Yes (per-core) | `/proc/cpuinfo` |
|
||||
| SIMD | **MSA** (MIPS SIMD Architecture, 128-bit) - `ASEs implemented: msa` | `/proc/cpuinfo` |
|
||||
| ISA | mips1 / mips2 / mips32r2 + MSA | `/proc/cpuinfo` |
|
||||
| TLB | 288 entries; 1 HW watchpoint; 6 kscratch regs | `/proc/cpuinfo` |
|
||||
| DVFS | **None exposed** - no `cpufreq/scaling_available_frequencies` | `/sys/.../cpufreq` |
|
||||
| Thermal | **No thermal zones** - `/sys/class/thermal` empty | sysfs |
|
||||
| GPU | **None** (X2000 has no GPU) | `/dev` has no gpu node |
|
||||
| VPU / video decode | **None** - no `vpu`/`video`/`mem2mem` dev nodes | `/dev` |
|
||||
| Hardware JPEG | **None** as a dev node (stock `jpg_to_png.c` is software) | `/dev`, RE |
|
||||
|
||||
**Implication:** all audio DSP and all UI rendering are CPU-bound. The single biggest
|
||||
untapped compute lever is **MSA SIMD** - diskOS's LVGL is almost certainly built without
|
||||
`-mmsa`, so blends/scales/rotations run scalar. Rebuilding LVGL (and any DSP/resampler)
|
||||
with MSA is the highest-leverage perf win available.
|
||||
|
||||
---
|
||||
|
||||
## 2. Memory
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| RAM total | **120 MB** (`MemTotal: 120308 kB`) |
|
||||
| Free / available (stock running) | ~19 MB free / ~64 MB available |
|
||||
|
||||
Tight but workable. diskOS already runs in this budget. Big in-RAM buffers (e.g. a
|
||||
full-res rotating album-art layer) must be sized carefully.
|
||||
|
||||
---
|
||||
|
||||
## 3. Storage
|
||||
|
||||
NAND (MTD) with **A/B dual-boot** for OTA, plus the user microSD:
|
||||
|
||||
| mtd | size | name | notes |
|
||||
|---|---|---|---|
|
||||
| mtd0 | 2 MB | uboot | bootloader |
|
||||
| mtd1 | 8 MB | kernel | slot A |
|
||||
| mtd2 | 128 MB | rootfs | slot A - **squashfs, read-only** (why `fiio_init.sh` can't be edited in place) |
|
||||
| mtd3 | 8 MB | kernel2 | slot B |
|
||||
| mtd4 | 25 MB | rootfs2 | slot B (smaller - recovery/fallback) |
|
||||
| mtd5 | 1 MB | ota | OTA state |
|
||||
| mtd6 | 1 MB | mac | MAC/calibration |
|
||||
| mtd7 | 83 MB | **userdata** | writable - mounted as `/usr/data`, where **diskOS lives** |
|
||||
| mmcblk0 | ~238 GB | microSD | single partition `mmcblk0p1` - the music card |
|
||||
|
||||
**Implication:** the **boot hook** is a small edit patched into the read-only rootfs's
|
||||
`usr/project/fiio_init.sh` (which is why enabling it requires rewriting the rootfs partition - the
|
||||
flash). The **payload it launches** - the `mq_ui` binary and diskOS's runtime state - lives in the
|
||||
writable `/usr/data` (mtd7), the safe persistent target. So: the hook is baked into the RO rootfs;
|
||||
only the UI/state are on `/usr/data`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Audio - the headline subsystem
|
||||
|
||||
### 4.1 DACs - quad CS43131, fully balanced
|
||||
Four Cirrus **CS43131** chips on I²C bus 3:
|
||||
|
||||
| I²C addr | sysfs name | /dev node (major) |
|
||||
|---|---|---|
|
||||
| 3-0030 | cs43131 | `/dev/cs43131` (248) |
|
||||
| 3-0031 | cs43131b | `/dev/cs43131b` (247) |
|
||||
| 3-0032 | cs43131c | `/dev/cs43131c` (246) |
|
||||
| 3-0033 | cs43131d | `/dev/cs43131d` (245) |
|
||||
|
||||
dmesg tags include `cs43131_left_negetive` and `cs43131b_open` → the four DACs are wired
|
||||
as **L+/L−/R+/R− (fully differential / balanced)**, two CS43131 per channel. This is an
|
||||
unusually serious analog design for the form factor. *(Whether the physical jack exposes
|
||||
balanced (4.4 mm) or sums to single-ended (3.5 mm) is a board question - confirm against
|
||||
the unit's connectors.)*
|
||||
|
||||
Each CS43131 is a stereo DAC + integrated headphone amp; the family supports PCM to
|
||||
384 kHz and **DSD64/128/256**. Control is via a **custom FiiO `cs43131` kernel driver**
|
||||
exposing the four char devices above; stock `mq_player` drives them with **ioctl** (not
|
||||
ALSA controls). Raw `/dev/i2c-3` is also present as a fallback.
|
||||
|
||||
### 4.2 SoC I²S/DMA path - the streaming ceiling
|
||||
The Ingenic audio controller (ALSA card 0 `x2000`) exposes **5 playback + 5 capture DMA
|
||||
channels** (`hw:0,0`-`hw:0,4` playback). `aplay --dump-hw-params` on an idle channel:
|
||||
|
||||
```
|
||||
FORMAT: ALL
|
||||
SAMPLE_BITS: [3 64]
|
||||
CHANNELS: [1 8]
|
||||
RATE: [8000 768000]
|
||||
```
|
||||
|
||||
So the **kernel/I²S link can stream up to 768 kHz / 64-bit / 8-channel** - far beyond the
|
||||
current track (S32_LE / 48 kHz / 2ch on DMA3). The real output ceiling is set by the
|
||||
CS43131 (~384 kHz PCM, DSD256), not the SoC.
|
||||
|
||||
### 4.3 ALSA mixer surface
|
||||
`amixer controls` shows only the **SoC internal codec** (`ICODEC HPOUTL/MIC GAIN`,
|
||||
`MICBIAS`), digital mic (`DMIC ...`), line-out muxes (`LO0_MUX`…`LO11_MUX`), and the five
|
||||
audio-interface formatters (`baic0_fmt`…`baic4_fmt`). There is **no CS43131 control, no
|
||||
DSD switch, and no digital-filter control in ALSA** - all of that is the kernel driver +
|
||||
mq_player ioctl path. `BAIC: baic start/stop` in dmesg marks I²S on/off per track.
|
||||
|
||||
### 4.4 Decode & format support (from mq_player RE)
|
||||
- Decoder backend: **libavcodec.so.58 (ffmpeg)** - string `decoder_ffmpeg`.
|
||||
- DSD: `DSD_MODE_NONE` / `DSD_MODE_NATIVE` / `DSD_MODE_DOP` - native DSD **and** DoP.
|
||||
- MQA: `is_mqa` flag (detection/passthrough).
|
||||
- Containers: FLAC, APE, DSF/DFF, **SACD ISO**, CUE sheets; ffmpeg covers ALAC/OPUS/
|
||||
WavPack/etc.
|
||||
- Param validation: `check_sample_param`, `reset hw params`, `AudioCodecOpen dsd`,
|
||||
`no support sample! dsd_mode/out_dev/...` - the player gates rate/format per DAC mode.
|
||||
|
||||
**Tools present:** `aplay`, `amixer`, `tinyplay`, `tinymix` - raw PCM playback is possible
|
||||
today (the DAC just has to already be configured for the rate).
|
||||
|
||||
---
|
||||
|
||||
## 5. Display
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Driver | `ingenicfb` |
|
||||
| Visible | 360×360, 32 bpp (XRGB8888/BGRA, panel mounted 180°-rotated) |
|
||||
| Framebuffer virtual | 360×**1080** = triple-buffered 360×360 |
|
||||
| **Overlay layers** | `fb0`-`fb3` = **4 hardware LCDC planes** (`/dev/fb0..fb3`) |
|
||||
| Backlight | standard `backlight` class, **41 levels (0-40)** |
|
||||
|
||||
**Layer control interface (confirmed via sysfs):** `ingenicfb` exposes `layer0`-`layer3`
|
||||
under `/sys/class/graphics/fb0/device/`, each with `enable`, `src_fmt`, `src_size`,
|
||||
`target_pos`, and **`target_size`**. `target_size` ≠ `src_size` ⇒ the LCDC has a
|
||||
**per-layer hardware scaler** (notable, since there's otherwise no GPU/VPU). layer0 is the
|
||||
active UI plane (`enable: 1`, src 360×360). Layers position + scale in hardware but **do
|
||||
not rotate**.
|
||||
|
||||
**Implication:** static scaled art/backdrops *could* be HW-composited on fb1-3 (e.g. a
|
||||
scaled cover or a dim backdrop under the LVGL UI) with no CPU blend. BUT:
|
||||
- A **spinning** cover still needs software rotation (layers don't rotate) - LVGL already
|
||||
does this fine, so the overlay is an optimization, not a requirement.
|
||||
- **Alpha/blend mode is NOT in sysfs** (no alpha/zorder/colorkey node) - likely an FBIO
|
||||
ioctl in the ingenicfb driver; blending behavior is **unverified**.
|
||||
- **Visual confirmation needs a camera:** `fbshot` reads fb0's memory, but overlays
|
||||
composite at *scanout*, so an fb1 test pattern won't appear in an fb0 capture. Defer the
|
||||
live overlay test until we're ready to pursue HW-layer art and can eyeball the panel.
|
||||
|
||||
---
|
||||
|
||||
## 6. Input
|
||||
|
||||
| Device | node | notes |
|
||||
|---|---|---|
|
||||
| Touch | `/dev/input/event1` - **cst816t** | single-finger panel, but speaks **MT type-B** protocol (slot/tracking-id/ABS_MT_POSITION_X/Y/TOUCH_MAJOR/PRESSURE; no plain ABS_X/Y). Caps `EV=0xb`, `ABS=0x6618000` (high word). |
|
||||
| Keys | `/dev/input/event0` - **x2000_key** | **physical buttons** (GPIO keys) |
|
||||
|
||||
We can synthesize input by writing the 32-bit-ABI `input_event` (16-byte) MT-B sequence to
|
||||
`/dev/input/event1` - verified working (used to drive stock's UI pages over serial during RE). diskOS can also read the hardware keys via event0.
|
||||
|
||||
---
|
||||
|
||||
## 7. Power
|
||||
|
||||
| IC | I²C | role |
|
||||
|---|---|---|
|
||||
| **SGM41513** | 2-001a | battery charger (Li-ion, ~3A class) |
|
||||
| **CW221X** (Cellwise) | 2-0064 | battery **fuel gauge** → `/sys/class/power_supply/cw221X-bat` |
|
||||
| **AW35615** | 2-0022 | **USB-C PD / CC** controller (Type-C orientation + power delivery) |
|
||||
|
||||
Live read: capacity 100%, 4.32 V, source "Mains". The fuel gauge gives real %/voltage;
|
||||
`/dev/usbcc_ioctl` (from RE) is the PD/CC control path. **PD negotiation hardware exists**,
|
||||
so faster charging / power-role awareness is at least theoretically addressable.
|
||||
|
||||
---
|
||||
|
||||
## 8. Connectivity (wireless)
|
||||
|
||||
| Item | Value | Source |
|
||||
|---|---|---|
|
||||
| Module | **AP6212** = Broadcom **BCM43438 / 4343A1** | dmesg: `chip:0xa9a6`, `fw_bcm43438a1.bin`, `nvram_ap6212a.txt` |
|
||||
| WiFi | **2.4 GHz only**, 802.11 b/g/n (single-band) | BCM43438 spec |
|
||||
| WiFi attach | SDIO (`[dhd]` driver v101.10.591.91.40, 512 KB dongle RAM) | dmesg |
|
||||
| Bluetooth | Broadcom BT over **UART `/dev/ttyS0`** @3Mbaud (`hci0`, BD address (device-specific, redacted)) | `hciconfig` |
|
||||
| BT firmware | `BCM4343A1_001.002.009.1026.1055.hcd` (the `BCM4345C5/C0` `.hcd` files are leftovers for other FiiO models) | `/lib/firmware/bt_bcm` |
|
||||
| BT bring-up | **hci0 is NOT attached at boot** - `bcmdhd.ko` only loads the driver/GPIOs. The HCI iface is created on-demand by `brcm_patchram_plus --enable_lpm --enable_hci --baudrate 3000000 --patchram <.hcd> /dev/ttyS0`, then `/usr/project/bluetoothd` (a2dp,avrcp,source) + `bluealsa --device=hci0` (SBC/LDAC). **Stock = bluez; `bsa_server`/`bt_enable_bsa*.sh` = dead code for the wrong chip.** Decoded from `mq_player` strings. | mq_player RE |
|
||||
|
||||
> **Corrects prior note:** earlier memory said "BCM4345C5". The actual silicon is
|
||||
> **BCM43438 (AP6212)** - single-band 2.4 GHz + BT 4.x.
|
||||
|
||||
**Implication:** no 5 GHz → WiFi music transfer / streaming is capped at 2.4 GHz real
|
||||
throughput (tens of Mbps, congestion-sensitive). BT codec quality (LDAC/aptX) is a
|
||||
userspace-stack question, not a chip blocker for A2DP.
|
||||
|
||||
---
|
||||
|
||||
## 9. USB
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Controller | **DWC2 OTG** (`13500000.otg_new`) - **dual-role** (host *or* device) |
|
||||
| Current mode | device; gadget `serial_demo` exposing **ACM** only (VID 0x0525 / PID 0xa4a7) → this *is* our serial shell |
|
||||
| USB-DAC mode | stock "UAC" work-mode reconfigures the gadget to **USB Audio Class** (device-as-DAC for a host PC) |
|
||||
|
||||
**Untapped:** DWC2 is OTG, so **USB host mode is physically possible** - mounting USB
|
||||
storage, or driving an *external* USB DAC (device as a pure transport). Stock only ships
|
||||
device-mode (serial + UAC). Host-mode would need role switch + the right gadget/host
|
||||
config and likely a USB-C OTG adapter.
|
||||
|
||||
---
|
||||
|
||||
## 10. Misc
|
||||
|
||||
- **RTC:** `rtc0` present and correct - real hardware clock.
|
||||
- **ADC:** SoC SAR ADC, 6 aux channels (`/dev/jz_adc_aux_0..5`) - analog reads (e.g.
|
||||
jack/line detect, if wired).
|
||||
- **Watchdog:** hardware `/dev/jz_watchdog`.
|
||||
- **LED:** **none.** `/sys/class/leds` is empty and there is **no physical LED** on the
|
||||
unit (confirmed visually). The `RGB_LEVEL`/`RGB_STATUS` fields in mq_player's config blob
|
||||
are vestigial, inherited from the halley5 reference design / other FiiO products.
|
||||
→ diskOS should plan **no LED features**.
|
||||
- **Motion sensors:** none on I²C (no accel/gyro) - no tilt/gesture input despite the
|
||||
round watch-like shape.
|
||||
|
||||
---
|
||||
|
||||
## 11. Stock-uses vs hardware-can-do (gap list)
|
||||
|
||||
| Capability | HW supports | Stock uses | diskOS opportunity |
|
||||
|---|---|---|---|
|
||||
| MSA SIMD | Yes (128-bit) | UI not built for it (unknown) | Rebuild LVGL/DSP `-mmsa` → faster render/effects |
|
||||
| LCDC overlay planes | 4 (fb0-3) | fb0 only | HW-composited art/video layer (spinning cover, backdrops) |
|
||||
| I²S rate | 768k/64b/8ch | ≤384k/DSD256 (DAC-limited) | none beyond DAC; already maxes the DAC |
|
||||
| DSD native + DoP | Yes | Yes | parity - reuse driver ioctls |
|
||||
| Quad balanced DACs | Yes | Yes | direct ioctl control for bit-perfect / HW volume |
|
||||
| USB host (OTG) | Yes | No (device-only) | USB storage / external USB-DAC transport |
|
||||
| UAC2 gadget | Yes | Yes (UAC mode) | expose/control USB-DAC from diskOS |
|
||||
| USB-C PD | Yes (AW35615) | basic charge | PD-aware fast charge / power-role UI |
|
||||
| Physical keys | Yes (x2000_key) | Yes | map HW buttons in diskOS |
|
||||
| WiFi 5 GHz | **No** | - | hard ceiling: 2.4 GHz only |
|
||||
| GPU / VPU / HW JPEG | **No** | - | hard ceiling: video is CPU-only (MSA-assisted at best) |
|
||||
| Thermal / DVFS | **Not exposed** | - | no power/thermal tuning via standard sysfs |
|
||||
| LED | **None** | vestigial config | none - drop from plans |
|
||||
|
||||
---
|
||||
|
||||
## 12. "Write our own mq_player / mq_ui?" - assessment
|
||||
|
||||
**mq_ui: already done.** diskOS *is* our own UI; it reuses stock `mq_player` purely as an
|
||||
audio engine over mqueue IPC. No reason to change that split for UI work.
|
||||
|
||||
**mq_player: a full rewrite is high-effort but the local-playback core is feasible.**
|
||||
What a from-scratch local player needs, and how hard each piece is:
|
||||
|
||||
| Piece | Feasibility | Notes |
|
||||
|---|---|---|
|
||||
| Decode | **Easy** | reuse on-device `libavcodec.so.58` (same as stock) |
|
||||
| PCM out | **Easy** | tinyalsa/libasound to card 0; up to 768k/64b |
|
||||
| DAC control (rate/DSD/filter/volume/mute) | **Medium** | open `/dev/cs43131*` and replay the kernel-driver **ioctls** - must RE the ioctl numbers + sequences from `mq_player.asm` (bounded but real work). This is the **critical enabler**. |
|
||||
| Native DSD / DoP | **Medium** | once DAC ioctls are known, feed the right format |
|
||||
| MCU glue (keys/charge/USB-detect/power) | **Medium** | stock player talks to the FiiO MCU; our player must too (MCU command surface mapped during RE) |
|
||||
| Streaming receivers (AirPlay/DLNA/Roon/QPlay/BT-sink) | **Hard** | large independent stacks - **do not rewrite**; keep stock or graft open-source (e.g. shairport-sync) |
|
||||
|
||||
**Recommendation (hybrid, incremental):**
|
||||
1. **Keep stock mq_player** as the engine for now - it already handles DAC/DSD/streaming/
|
||||
MCU and diskOS drives it fine over IPC.
|
||||
2. **RE the `cs43131` ioctl interface regardless** - it's the key that lets diskOS (or a
|
||||
future thin player) control bit-perfect output, hardware volume, DSD, and filters
|
||||
directly. Highest-value RE next step.
|
||||
3. **Only build our own *local* player** if we need something stock won't expose - e.g.
|
||||
software parametric EQ, ReplayGain, custom gapless/crossfade, or a DSP chain - using
|
||||
ffmpeg + tinyalsa + the RE'd DAC ioctls. Leave streaming to stock.
|
||||
|
||||
The streaming receivers and MCU dependency are what make a *complete* replacement
|
||||
expensive; the audiophile local-playback path is the tractable, high-value slice.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: probe commands (reproducible)
|
||||
- SoC/mem: `cat /proc/cpuinfo /proc/meminfo`, `uname -a`
|
||||
- Audio: `cat /proc/asound/{cards,pcm}`, `aplay -l`, `aplay -D hw:0,0 --dump-hw-params /dev/zero`, `amixer -c0 controls`
|
||||
- I²C map: `for d in /sys/bus/i2c/devices/*/name; do echo "$d: $(cat $d)"; done`
|
||||
- Power: `cat /sys/class/power_supply/*/{type,capacity,voltage_now,status}`
|
||||
- Storage: `cat /proc/partitions /proc/mtd`
|
||||
- USB: `ls /sys/class/udc`, `cat /sys/class/udc/*/state`, `ls /sys/kernel/config/usb_gadget/*`
|
||||
- Wireless: `dmesg | grep -iE 'dhd|bcm|chip'`, `hciconfig -a`, `ls /lib/firmware/{wifi_bcm,bt_bcm}`
|
||||
- Display/input: `cat /sys/class/graphics/fb0/{name,virtual_size,bits_per_pixel}`, `ls /dev/fb*`, `cat /proc/bus/input/devices`
|
||||
- Engines: `ls /dev | grep -iE 'ipu|vpu|jpeg|adc|watchdog'`
|
||||
@@ -0,0 +1,61 @@
|
||||
# diskOS privacy and network disclosure
|
||||
|
||||
This lists everything the installer and the diskOS UI (`mq_ui`) send over the network. The UI ships
|
||||
as a binary-only component (source not yet published), so this is a good-faith disclosure based on the
|
||||
code and observed behaviour, not a guarantee; it will be tightened when the UI source is published.
|
||||
|
||||
## The installer (this repo)
|
||||
|
||||
- **`./install.sh` uses `pip`** to upgrade `pip` itself and download two packages - `pyusb` and
|
||||
`pycryptodome` - from **PyPI** into a local `.venv`. That `pip`/PyPI traffic is the only network
|
||||
activity in setup. Point `pip` at your own mirror if you prefer, or pre-install the packages offline.
|
||||
- **The installer app itself makes no network requests.** Building the image, decrypting/extracting
|
||||
your firmware, saving the recovery image, and flashing over USB are all fully local. It does not
|
||||
phone home and sends no telemetry.
|
||||
|
||||
## The diskOS UI (`mq_ui`) on the device
|
||||
|
||||
diskOS is a local music player. The online features below reach out **only when you use them**; each
|
||||
request unavoidably reveals your device's public IP to the service it contacts.
|
||||
|
||||
| Feature | Endpoint | Protocol | What is sent |
|
||||
|---|---|---|---|
|
||||
| **Weather** | `wttr.in` | **plain HTTP** | A location string if you set one, else nothing - wttr.in then **auto-geolocates you by IP** (approximate). Over HTTP, so treat it as visible on your network. |
|
||||
| **Lyrics** | `lrclib.net` | HTTPS | The current track's **title and artist**, to find matching lyrics. |
|
||||
| **Scrobbling (Last.fm)** | `ws.audioscrobbler.com` | HTTPS | If enabled and connected: the tracks you play (artist/title/album/timestamp) are reported to **your** Last.fm account. |
|
||||
| **Last.fm sign-in** | `www.last.fm/api/auth/` | HTTPS | Only a QR code containing the approval URL is shown; **your phone**, not the device, opens it. `www.last.fm/api/account/create` is shown as text so you know where to get an API key. |
|
||||
|
||||
### Last.fm credential setup happens over your LOCAL network in plaintext
|
||||
|
||||
To connect Last.fm, diskOS runs a **temporary web server on the device** at
|
||||
`http://<device-wifi-ip>:8080/<random-token>/`, shows a QR for it, and your phone posts your Last.fm
|
||||
**API key and shared secret** to it. Important properties:
|
||||
- It is **plain HTTP on your Wi-Fi LAN** - your API key/secret cross your local network unencrypted.
|
||||
Do this on a network you trust.
|
||||
- The server is **transient** (runs only during setup), bound to the Wi-Fi interface, and gated by a
|
||||
random URL token.
|
||||
- Your Last.fm **API key, secret, and session key are then stored on the device** under `/usr/data`
|
||||
(device config); **offline scrobbles are queued** in `/usr/data/lastfm.queue` until they can be sent.
|
||||
Nothing Last.fm-related is sent anywhere except your device, your phone (during setup), and Last.fm.
|
||||
|
||||
> **Last.fm is BETA and unverified end-to-end** - see the README's Known Issues. The transport and
|
||||
> signing are tested, but a full connect-and-scrobble round-trip to a live account has not been.
|
||||
|
||||
### The stock FiiO player still runs alongside diskOS
|
||||
|
||||
diskOS replaces only the UI; FiiO's original player process still runs underneath it. That stock
|
||||
component - not diskOS - is responsible for any Bluetooth, firmware-update checks, and LAN media
|
||||
endpoints (DLNA/AirPlay/Roon/QPlay-style discovery) the device may present on your local network. Those
|
||||
behaviours are inherited from the stock firmware and are outside diskOS's code.
|
||||
|
||||
## What diskOS does NOT do
|
||||
|
||||
- No analytics, telemetry, or usage tracking.
|
||||
- It does not upload your music library, your listening history (beyond Last.fm scrobbles if you turn
|
||||
them on), or any personal files.
|
||||
- If you never enable weather, lyrics, or Last.fm, diskOS itself makes no outbound requests.
|
||||
|
||||
## Reporting
|
||||
|
||||
Report anything that looks like unexpected data leaving the device via the process in
|
||||
[`SECURITY.md`](../SECURITY.md).
|
||||
@@ -0,0 +1,427 @@
|
||||
# Snowsky Disc - mq_player / mq_ui Reverse-Engineering Catalogue
|
||||
|
||||
Teardown of the stock V2.09 firmware binaries to document everything the player
|
||||
and UI are capable of, so diskOS (our LVGL UI) can drive every feature.
|
||||
|
||||
- Targets: stock `usr/bin/mq_player` (4126620 B) and `usr/bin/mq_ui` (4418300 B).
|
||||
MIPS32 little-endian, **glibc dynamically linked**
|
||||
(interp `/lib/ld-linux-mipsn8.so.1`; 28 DT_NEEDED incl libc.so.6/libsqlite3/FFmpeg - NOT
|
||||
static musl; only *our* diskOS binary is static-musl).
|
||||
- Disasm cache (regenerate with `mipsel-linux-gnu-objdump`):
|
||||
disassembly, data, and string dumps of the two binaries.
|
||||
- Verification: addresses below were spot-checked against the cache on 2026-06-25.
|
||||
Items marked **(unverified)** have not yet been individually confirmed at the
|
||||
instruction level.
|
||||
|
||||
---
|
||||
|
||||
## ⚑ CORRECTIONS - full verification pass (2026-07)
|
||||
Handler-level RE of both binaries; corrections spot-checked against the binary and applied inline below.
|
||||
- **Binaries are glibc dynamic, not static musl** (interp `/lib/ld-linux-mipsn8.so.1`, 28 DT_NEEDED). Affects any replacement-binary / preload work.
|
||||
- **IPC frame = `TAG(4) + TOTAL_LEN(4hex) + VALUE1(4hex) + optional payload`** - LEN is *total*, not payload length; class-2 cmds add VALUE2. Builder `mq_ui 0x43f82c` (`%s%04X%04X%s`). Replies `%s%04X%04X%s` to `/ui`. Queue `/player` maxmsg=20 msgsize=8192.
|
||||
- **`06b3` = BT CODEC SELECT** (0=SBC 1=AAC 2/3/4=LDAC; worker 0x40eaf4 → `set_out_dev_sample_array(2,44100,44100)` for SBC/AAC, 96000 for LDAC). **`06b4` = LDAC quality only** (mob/std/high via /usr/data/bt_pipe_recv). ← the two were swapped in our old notes.
|
||||
- **✅ BT AUDIO OUTPUT NOW WORKS (2026-08-03, live-fixed).** The DAP CAN transmit to a BT speaker (a2dp-source) - it just wasn't exposed and the code is fragile. The crash we hit was NOT the 06b3 codec value and NOT unfinished code: it was **skipping the mandatory `0666000C0006` pre-stop** before `0666000C0002`. Full working route sequence + the `--sbc-quality=medium` (stereo, no stutter) fix → **COMMAND_MAP.md "BT AUDIO OUTPUT" section**. The earlier "SBC fix frame = 06b3000C0000" was wrong/incomplete: diskOS uses `06b3001D0000<MAC>` after the pre-stop + `06c1` init.
|
||||
- **`06b1`/`06b2`** = BT sample-rate / bit-depth params (not list/query).
|
||||
- **`0689`** = EQ preset *apply* (21-way engine 0x449544), not a DB-only/media-info op.
|
||||
- **`0201`** = generic transport toggle (not Roon-specific). **`0657`/`0666`/`06b3` "close_player" = false positive** (shared teardown preamble). `0666`=route (2=BTSRC 4=SPDIF 6=DAC).
|
||||
- **out_dev** enum has no BTSINK (that's an input/work-mode). 48k→44.1k resample is `libfiio_decoder` over FFmpeg `libswresample` - a stock path, so codec-feasible (CPU headroom still needs an on-device xrun benchmark).
|
||||
- **IPC-hardening RISK:** stock parser trusts declared LEN and uses `strlen` over the real `mq_receive` byte count → diskOS must emit strictly-correct total lengths and never forward untrusted frames.
|
||||
- Still UNVERIFIED / deep-RE TODO: exact `0642` receiver-mode values + AirPlay/DLNA/Roon/QPlay trigger sequences; `0715` runtime volume callback @0x822b14; full `SET_USB_MODE` wire encoding; `0722` OTA worker chain; CS43131 ioctl ABI; the remaining ~190 family-inferred tag meanings.
|
||||
|
||||
---
|
||||
|
||||
## 1. How mq_player is driven - the command dispatch
|
||||
|
||||
mq_player is a background command server. The UI never touches audio; it mails
|
||||
text commands to a POSIX message queue **`/player`**, and the player mails status
|
||||
frames back on **`/ui`**.
|
||||
|
||||
### Wire format
|
||||
- Command frame: `TAG(4 ASCII) + LEN(4 ASCII hex) + DATA`.
|
||||
- Response builder @ **0x488d64** uses format `%s%04X%04X%s`
|
||||
(tag, then two 16-bit hex fields, then payload). **(verified: prologue @0x488d64)**
|
||||
|
||||
### Dispatch tables (verified structure)
|
||||
Two arrays of 8-byte `{descriptor_ptr, handler_ptr}` entries:
|
||||
|
||||
- **Table A @ 0x7c9d30** - primary handlers. Entry 0 = `{0x64f6e0, 0x411790}`.
|
||||
~131 entries. **(count unverified; structure verified)**
|
||||
- **Table B @ 0x7ca150** - secondary/extended. Many entries have
|
||||
`handler = 0x00000000` = **declared but unimplemented** (e.g. `{0x64fca8, NULL}`).
|
||||
~76 entries. **(count unverified; NULL-handler structure verified)**
|
||||
|
||||
Note: 0x7b7870 / 0x7b7c90 (noted in older sessions) are **PLT stubs**, not the
|
||||
real tables. ~194 tag-like ASCII strings exist in `.str` total; ~30 of the table
|
||||
slots are NULL / reloc-only / unimplemented (NAS / qplay family). **(unverified)**
|
||||
|
||||
A command arrives → tag matched against table → handler thunk → real handler.
|
||||
Most handlers are tiny getters/setters over an in-memory settings struct.
|
||||
|
||||
### Confirmed action commands (tag strings verified present in `.str`)
|
||||
| Tag | Action |
|
||||
|------|------------------------------------------|
|
||||
| 0100 | play by list (list_type + index) |
|
||||
| 0103 | seek |
|
||||
| 0104 | favorite / unfavorite |
|
||||
| 0201 | transport (play/pause/next/prev) |
|
||||
| 0202 | request current state |
|
||||
| 0622 | rescan SD / rebuild DB |
|
||||
| 0657 | source/work-mode transition (NOT play-mode) |
|
||||
| 0666 | set output route (2=BTSRC 4=SPDIF 6=local-DAC) |
|
||||
| 0689 | select+APPLY EQ preset (21-way engine 0x449544; NOT DB-only) |
|
||||
| 0715 | set volume |
|
||||
|
||||
(0100/0103/0104/0201/0202/0622/0657/0666/0689/0715 all confirmed as ASCII tag
|
||||
strings in `mq_player.str`. Full 207-tag enumeration is in the raw table dump and
|
||||
should be transcribed here in a follow-up pass.)
|
||||
|
||||
---
|
||||
|
||||
## 2. Audio pipeline
|
||||
|
||||
Decode → process → output.
|
||||
|
||||
### Decode
|
||||
- **FFmpeg** stack for mp3/flac/wav/aac/aif/m4a/ape/ogg/wma. **(unverified)**
|
||||
- **libfiio_decoder** for DSD / DFF / DSF / SACD-ISO; DTS. **(unverified)**
|
||||
- libsndfile present. **(unverified)**
|
||||
|
||||
### DSD / DoP
|
||||
- Modes: NATIVE / DOP / D2P. DoP carrier up to 768k. Roon `configure_*` files in
|
||||
`usr/project/config/roon/` corroborate (configure_768_dop, _384_d2p, etc.).
|
||||
**(config files verified on disk; mode enum unverified)**
|
||||
|
||||
### Output routing (`out_dev`, tag 0666)
|
||||
LOCAL_ANALOG (CS43131 headphone DAC) / BTSRC / USB_HOST / SPDIF / I2S3.
|
||||
(BTSINK is NOT an out_dev route - it is an input/work-mode; see §1 and the work-mode enum in §7.)
|
||||
**(unverified)**
|
||||
|
||||
### Volume - set_volume @ 0x489558 **(verified: prologue @0x489558)**
|
||||
- Applied by shelling out to an amixer control named **`ICODEC HPOUTL GAIN`**
|
||||
(string verified @ 0x264dd4 in `.str`).
|
||||
- Uses inotify to react to external volume changes. **(unverified)**
|
||||
- On-board MCU over SPI also carries gain/filter/mute opcodes
|
||||
(SET_EQ_PARAMETER 0x100B etc.). **(unverified)**
|
||||
|
||||
### Bluetooth
|
||||
- bluealsa-based; source + sink; codecs sbc / aac / ldac. **(unverified)**
|
||||
- **Stock BT stack = BLUEZ, decoded from `mq_player` strings** (NOT BSA - `bsa_server`/`bt_enable_bsa*.sh`
|
||||
are dead code, wrong chip BCM4345C5, no caller; mq_player references bsa_server 0×). Full bring-up embedded
|
||||
in mq_player: `brcm_patchram_plus --enable_lpm --enable_hci --no2bytes --tosleep 200000 --baudrate 3000000
|
||||
--patchram /lib/firmware/bt_bcm/BCM4343A1_001.002.009.1026.1055.hcd /dev/ttyS0` (downloads the chip fw patch
|
||||
over UART → creates working hci0; nothing does this at boot) → `/usr/project/bluetoothd --noplugin=sap
|
||||
--plugin=a2dp,avrcp --mode=source` → `bluealsa -S --device=hci0 --profile=a2dp-source --ldac-abr
|
||||
--ldac-quality=standard --codec=sbc --initial-volume=48` → `hciconfig hci0 up/piscan/class 0x200414` →
|
||||
`bluetoothctl agent on/default-agent/pairable on`. Sink mode = no-LPM patchram + `--mode=sink -p hfp-ag
|
||||
--codec=sbc/aac/ldac`. The missing patchram step is why a naive BT enable produces a
|
||||
poor scan.
|
||||
|
||||
### Gapless
|
||||
- `audio_track_update_play_gapless`. **(unverified)**
|
||||
|
||||
### ReplayGain
|
||||
- Stored / read from DB. **(unverified)**
|
||||
|
||||
---
|
||||
|
||||
## 3. EQ - the apply path (the key unlock)
|
||||
|
||||
The EQ is a **software parametric biquad cascade inside mq_player**, NOT a
|
||||
hardware DAC feature. **CORRECTION:** `0689` does NOT merely write
|
||||
to SQLite - its handler (`0x4961bc`) invokes the **21-way preset engine `0x449544`**,
|
||||
updates the rate caps and applies the DSP, then replies `a639`. Custom bands are set
|
||||
separately via `0678` (`0x44a658`). Both are live apply paths, not DB-only.
|
||||
|
||||
### DSP engine (verified prologues exist)
|
||||
- Coefficient calc @ **0x448144** - uses pow/sqrt/sincos to turn
|
||||
{filterType, frequency, gain, qValue} into biquad coefficients.
|
||||
- IIR cascade @ **0x447ed0** and @ **0x4483a4** - per-sample filter, 32-bit
|
||||
saturation. **(verified: both are function prologues)**
|
||||
|
||||
### Storage - PEQ table (SQL strings verified in `.str`)
|
||||
Columns: `STYLE_NAME, MASTER_GAIN (REAL), PARAMS_JSON (text), STYLE_PRESET (int)`.
|
||||
- PARAMS_JSON = array of bands `{filterType, frequency, gain, qValue}` (up to 10)
|
||||
+ a master gain.
|
||||
- Confirmed SQL @ ~0x257cb8 region:
|
||||
- `INSERT INTO PEQ (STYLE_NAME, MASTER_GAIN, PARAMS_JSON, STYLE_PRESET) VALUES ('%s', %.1f, '%s', %d)`
|
||||
- `UPDATE PEQ SET STYLE_NAME = ?, MASTER_GAIN = %.1f, PARAMS_JSON = ? WHERE STYLE_PRESET = ?`
|
||||
- `SELECT 1 FROM PEQ WHERE STYLE_PRESET = ? LIMIT 1`
|
||||
|
||||
### To apply a custom EQ from diskOS
|
||||
1. Write/UPDATE the PEQ row (bands JSON + master gain) for a STYLE_PRESET.
|
||||
2. Send **0689** to select that preset.
|
||||
The player loads bands → computes coeffs @0x448144 → runs the cascade
|
||||
@0x447ed0/0x4483a4 on every sample before output.
|
||||
|
||||
### ✅ WIRED + CONFIRMED in diskOS (2026-06-30)
|
||||
Custom EQ is live: `eqcustom.c` (10-band UI, horizontal scroll) → `mdb_set_peq()` writes PEQ
|
||||
slot 11 in the format below → `ui_apply_eq(11)` sends 0689 → **player applies, sound changes
|
||||
on-device (user-verified).** `ui_apply_eq` clamp raised 0x0A→20 to reach user slots 11-20.
|
||||
|
||||
### CAPTURED PARAMS_JSON format (live, V2.09, 2026-06-30)
|
||||
Saved a stock "User 1" custom EQ (+12 @ 32 Hz, −12 @ 16 kHz) and read it back. Ground truth:
|
||||
- **10 fixed bands**: 32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz, each ±12.0 dB; plus a **master ±12 dB** = the `MASTER_GAIN` REAL column.
|
||||
- PARAMS_JSON = JSON **array of 10 objects**, e.g. one band:
|
||||
`{"filterType":0,"frequency":32,"position":0,"gain":"12.0","qValue":"0.7"}`
|
||||
- `filterType` = 0 (peaking) - **int**; `frequency` Hz - **int**; `position` 0-9 band index - **int**.
|
||||
- **`gain` and `qValue` are JSON STRINGS** (quoted: `"12.0"`, `"-12.0"`, `"0.7"`), NOT numbers. gain is one-decimal dB; default qValue `"0.7"`.
|
||||
- **User slot → STYLE_PRESET**: User 1-10 = **11-20** (User 1 = 11). Built-in presets = 0-10 (off,jazz,rock,r&b,hip-hop,pop,dance,classical,retro,sibilance-atten-1,sibilance-atten-2). Rows 160-169 exist but are unused/placeholder (`PARAMS_JSON` = literal string `"(null)"`).
|
||||
- Stock writes via `UPDATE PEQ SET ... PARAMS_JSON = ? WHERE STYLE_PRESET = ?` then selects with **0689000C<preset hex>** (User 1 = `0689000C000B`).
|
||||
- Other stock audio settings seen in the same menu (candidates for diskOS, map to documented name-cmds): Gain H/L, BT codec, SPDIF on/off, DAC digital filter (Fast/Slow LL, Fast/Slow PC, NOS, Wideband FF), DRE on/off.
|
||||
|
||||
---
|
||||
|
||||
## 4. The `/ui` frames - player → UI
|
||||
|
||||
mq_ui opens the **`/ui`** mqueue (name built @0x41b388, recv thread @0x415584,
|
||||
`mq_receive` wrapper @0x41b5c8). Each frame = `TAG(4) + %x extension + JSON "other"`
|
||||
(parser @0x415674: `strncpy …,4` then `sscanf %x`). Dispatch is a
|
||||
`strncmp(cmd,tag,4)` chain in `ui_ctrl_response` (@0x41bfd0-0x41c098). Recv log
|
||||
`[UI RECV]: %s` @0x272960. **(IPC path + format instruction-verified)**
|
||||
|
||||
Player→UI frames use **`a`-prefixed** tags (replies/reports) + `06d*` (OTA). Tag
|
||||
meaning is taken from the adjacent log string (string-inferred, high confidence).
|
||||
|
||||
| TAG | meaning | payload |
|
||||
|------|---------|---------|
|
||||
| a620 | version reply | version `%s` |
|
||||
| a710 | max-volume reply | `maxVolume` |
|
||||
| a715 | volume / UAC srate change | `currentVolume` |
|
||||
| aa1b | UAC sample-rate change | rate; `usbAudio` |
|
||||
| a704/a705 | wifi connect status | state; `wifi_ssid` |
|
||||
| a706 | net status | `%d` |
|
||||
| a615 | language config | `%d` |
|
||||
| a609 | theme config | `%d` |
|
||||
| aa27 | charge-protect | `charge_protect` |
|
||||
| aa24 | sys config | `%d`; `sys_count` |
|
||||
| a634 | memory-play | `memoryPlay`/`memoryType` |
|
||||
| a639 | EQ type | preset + `filterType/frequency/gain/qValue` |
|
||||
| a6b6 | BT paired devices | count + list |
|
||||
| a6c5/a6c3/a6c2/a6c1/a6c4/a6c0 | BT disc/connect/open replies | state + address |
|
||||
| aa1c | power-key event | `%d` |
|
||||
| aa22 | TF-card (SD) insert/remove | event `%d` |
|
||||
| aa0c/aa0f | screen status | `%X` |
|
||||
| a60a | tip/toast popup | tip code |
|
||||
| a644 | now-playing / UI state update | song JSON (below) |
|
||||
| a622 | SD rescan / DB-rebuild status | scan state |
|
||||
| 06d0/06d1/06d2 | OTA progress / success / file-count | `%d` |
|
||||
|
||||
Verified-present log strings: GET_VERSION_REPLY @0x27255c, GET_WIFI_CONNECT_STATUS
|
||||
@0x272658, UAC_SRATE_CHANGE @0x27252c.
|
||||
|
||||
**Now-playing JSON payload** (fields verified in `.str`): `song_name`, `song_artist_name`
|
||||
(@0x271b90), `song_album_name`, `song_style_name`, `song_track`, `song_channel`,
|
||||
`song_duration_time` (@0x271b34), `duration`, `songposition` (@0x271c2c),
|
||||
`song_sample_rate`, `song_encoding_rate`, `song_bit_rate`, `song_mimetype`,
|
||||
`song_file_path`, `is_sacd/is_cue/is_dsd`, `love` (favorite), `state` (play state),
|
||||
`playerflag` (@0x271c08), `curlistlength`, `pos_id`, `playing_num`,
|
||||
`work_mode` (@0x271b… 0x272b34), `battery` (@0x272c58). Album art → width/height/
|
||||
quality/rgb → `/usr/data/fiio/cover.png`.
|
||||
|
||||
→ For diskOS status indicators (battery/BT/wifi) we read tags a704/a706 (wifi/net),
|
||||
the BT a6c* family, and `battery` inside a644. **This is the data we were missing.**
|
||||
|
||||
### mq_ui feature surface (subsystems)
|
||||
IPC core (`/ui` in; `/player`,`/bt_control` out) · frame dispatch (cJSON) · local
|
||||
browse/play (all-songs/album/artist/style/favorite/custom, `db_song_ctrl.c`) ·
|
||||
now-playing (`class_play_screen.c`, album art `jpg_to_png.c`) · Roon endpoint ·
|
||||
home/menus (app/system/audio/others/popup `.json`) · EQ (`equalizer.json`) ·
|
||||
Bluetooth (`bt.json`) · WiFi/NAS (`wpa_supplicant`, `hostapd`, `wl rssi`) ·
|
||||
system/version/OTA (`ota_update.c`, `set_local_time.c`) · FiiO Link (UDP
|
||||
224.0.0.255 discovery + TCP control, device id "SNOWSKY DISC") · bundled zlog.
|
||||
|
||||
---
|
||||
|
||||
## 5. Config + database schema
|
||||
|
||||
SQLite 3.23.1, opened via `sqlite3_open_v2`. DB files (paths verified in `.str`):
|
||||
|
||||
| File | Holds |
|
||||
|------|-------|
|
||||
| `/usr/data/fiio/db/sysconfig.db` | SYSCONFIG (settings, single row ID=1), CUSTOM_THEME, NAS_CONFIG |
|
||||
| `/usr/data/fiio/db/song.db` | SONG, MY_LOVE, MEMORY_PLAY, PLAY_LIST, LIST_SONG_0/1/2, CUSTOM_PLAYLIST, PLAYLIST_INFO, PEQ |
|
||||
| `/usr/data/fiio/db/dic.db` | HAN_PINYIN (CODE INTEGER, PINYIN char(1)) - CJK pinyin sort (on-disk verified) |
|
||||
| `/usr/data/fiio/db/theme.db` | theme assets |
|
||||
| `/usr/data/fiio/db/ebook.db` | e-book |
|
||||
| `/usr/data/bluetooth.db` | bt_devices |
|
||||
|
||||
Only `dic.db` ships in the rootfs; the rest are created at first boot under
|
||||
`/usr/data/fiio/db/`. db→file binding is from co-located source names + init block
|
||||
(high confidence for SONG-family→song.db, SYSCONFIG/theme/NAS→sysconfig.db).
|
||||
|
||||
### SONG (song.db) - literal CREATE for MY_LOVE verified verbatim; SONG = same + IS_LOVE
|
||||
ID(PK) · PATH · NAME · TITLE · ALBUM · ARTIST · GENRE · DISC · TRACK · IS_CUE ·
|
||||
IS_ISO · IS_DSD · OFFSET(BIGINT) · DURATION(BIGINT) · NAME_CODE · TITLE_CODE ·
|
||||
ALBUM_CODE · ARTIST_CODE · GENRE_CODE(pinyin sort keys) · ADD_TIME(INT8) ·
|
||||
SAMPLE_RATE · BIT_PER_SAMPLE · CHANNELS · BIT_RATE · SONG_MIMETYPE ·
|
||||
SONG_PRODUCTION_YEAR · IS_SELECT · ALBUM_ARTIST · ALBUM_ARTIST_CODE ·
|
||||
**IS_LOVE** (SONG only, added by upgrade ALTER - favorites flag).
|
||||
MY_LOVE = same minus IS_LOVE, `UNIQUE(PATH,TRACK)`. **(CREATE verified @mq_ui.str:9793)**
|
||||
|
||||
### CUSTOM_PLAYLIST (song.db) - literal CREATE verified @mq_ui.str:9790
|
||||
MY_LOVE columns + `PLAYLIST_ID` (after ID), `FOREIGN KEY(PLAYLIST_ID) REFERENCES
|
||||
PLAYLIST_INFO(ID) ON DELETE CASCADE`, `UNIQUE(PLAYLIST_ID,PATH,TRACK)`.
|
||||
|
||||
### PLAYLIST_INFO (song.db) - parent of CUSTOM_PLAYLIST. ID(PK) firm; name/count inferred (LOW confidence).
|
||||
|
||||
### PLAY_LIST (song.db) - queue registry: ID(PK) · LIST_ID · LIST_NAME.
|
||||
|
||||
### LIST_SONG_0/1/2 (song.db) - active play queues, built by `INSERT INTO LIST_SONG_%d … SELECT … FROM SONG` (verified). Cols: ID · LIST_ID · POS_ID(=MUSIC_ID join key) · PATH · NAME · TITLE · ALBUM · ARTIST · GENRE · DISC · TRACK · IS_CUE · IS_ISO · OFFSET · DURATION · ADD_TIME · IS_SELECT · SONG_TYPE · ALBUM_ARTIST.
|
||||
|
||||
### MEMORY_PLAY (song.db) - resume state, single row. ID(=1) · MUSIC_ID · IS_PLAYING · POSITION · IS_CUE · IS_ISO · TRACK. **(UPDATE verified @mq_player.str:13412)**
|
||||
|
||||
### PEQ (song.db) - see §3. ID · STYLE_NAME · MASTER_GAIN(REAL) · PARAMS_JSON · STYLE_PRESET.
|
||||
|
||||
### CUSTOM_THEME (sysconfig.db) - ID · POS_ID · NAME · PATH · ALIAS · TYPE · IS_SYSTEM · ATTR · ALPHA · LOCK_TIME/DATE/BATTERY/ID3/MEM_TYPE · FRONT_COLOR.
|
||||
### NAS_CONFIG (sysconfig.db) - ID · NAS_NAME · NAS_IP · USER · (password) · TYPE · AUTO_LOGIN.
|
||||
### bt_devices (bluetooth.db) - ID · NAME · ADDR(MAC key) · CODEC · SAMPLING · VOLUME.
|
||||
|
||||
### SYSCONFIG (sysconfig.db) - single row, all INT, `UPDATE SYSCONFIG set %s = %d where ID = 1` (verified @0x25a910)
|
||||
Column names verified from descriptor @mq_player.str:13091+. Runtime pak dump
|
||||
(verified @0x25e7a4): `DSD_MODE,OUT_DEV,VOLUME,WORK_MODE,LIGHT_LEVEL,LIGTH_ON_TIME
|
||||
(sic),RGB_LEVEL,MUTE,VOL_MODE,RGB_STATUS,MEM_PLAY`. Full key set (names verified;
|
||||
**enum value mappings NOT proven** - inferred from name + cross-ref to command tags):
|
||||
|
||||
DSD_MODE · OUT_DEV/DEVICE_OUTPUT(→0666) · VOLUME · WORK_MODE · LIGHT_LEVEL ·
|
||||
LIGTH_ON_TIME(backlight timeout, sic) · RGB_LEVEL · MUTE · VOL_MODE · RGB_STATUS ·
|
||||
RGB_COLOUR · TRIGGER_IN · SYS_THEME · LANGUAGE · USB_MODE · NETWORK_MODE ·
|
||||
EQ_TYPE(→0689, =PEQ.STYLE_PRESET) · MAX_VOL · BALANCE_VOL · POWER_SAVE ·
|
||||
FILTER_TYPE(DAC digital filter) · PLAY_MODE(→0102) · MEMORY_PLAY(resume) ·
|
||||
FOLDER_JUMP · PLAY_GAP(gapless) · INPUT_MODE · VOL_KNOB_MODE · OTA_CFG · BATTERY ·
|
||||
BT_CODEC · SCREEN_ROT · PO_PRE_VOL/PO_VOL/PRE_VOL · THEME_MODE · CHARGE_PROTECT ·
|
||||
LOCK_THEME · TREBLE · BASS · WIFI_STATUS · BT_STATUS · LO_DISABLE · OS_MODE ·
|
||||
DSD_DECODE · SPDIF · AUTO_TIME · DRE_STATUS · LOCAL_IMG_ANIM · IMG_ANIM_BRIGHTNESS ·
|
||||
ARM_VERSION · MCU_OTA_FAILED_TIME · KEY_SINGLE/DOUBLE/LONG_CLICK_SLE(gesture→action) ·
|
||||
ARTIST_CLASS_TYPE · AUDIO_VOLUME_SET.
|
||||
Config is committed to flash on write (`now try to save config to flash` @0x25a…).
|
||||
|
||||
---
|
||||
|
||||
## 7. Network streaming + USB modes + COMPLETENESS
|
||||
|
||||
**Major gap found:** the earlier sections missed the network-streaming receivers and several subsystems. The device is far more capable than §1-6 implied.
|
||||
|
||||
### Work-mode / OUT_DEV enum (the master "audio source" list) - names VERIFIED
|
||||
mq_player has ONE work-mode enum (pointer-array @ ~0x7bb0a0 data; names in `.str` @0x25f9e0+). Switching mode is **name-driven**: a supervisor `@0x444604` does `strcmp(name,"AIRPLAY"/"DLNA"/"ROON"/...)` and starts/stops that receiver. **Names verified present; integer indices unverified:**
|
||||
`0 NO_DEFINED · 1 I2S3_OUT · 2 USB_HOST_NULL · 3 NO_WORK_MODE · 4 LOCALPLAYER · 5 BTSINK · 6 ANALOG · 7 UAC(USB-DAC) · 8 DLNA · 9 AIRPLAY · 10 ROON · 11 SPDIF_OPT · 12 SPDIF_RX · 13 DMR · 14 DMC · 15 DMS · 16 MIX · 17 I2S_IN · 18 STREAM_AUDIO` (+ QPLAY). sysconfig keys NETWORK_MODE (cfg+0x5c) and OUT_DEV both feed this.
|
||||
|
||||
### AirPlay receiver - BUILT IN (was completely missed)
|
||||
mq_player embeds a **full shairport-sync fork**: `fiio_airplay.c` + `src/shairport.c, rtsp.c, rtp.c, dacp.c, metadata.c, mdns_avahi.c, player.c` + `libshairplay.so`. Advertises **`_raop._tcp`/`_airplay._tcp`** as **"SNOWSKY DISC"**. Config: `/usr/project/config/airplay2/x2000_airplay2.conf` (Shairport-Sync style, AirPlay 2). Flow: supervisor sees mode "AIRPLAY" → sets `airplay_play=1` (@0x7efbe4) → `start_airplay@0x436008` → pthread `airplay_func@0x435fc0` → shairport loop. Needs **wlan0 up** (`start_switch_network_mode_thread@0x46e634` monitors `/sys/class/net/wlan0/operstate`=="up", then emits `/ui` tag **a706** = GET_NET_STATUS_REPLY - a706 is STATUS, NOT the trigger). Has `airplay_artwork_thread`, DACP remote. Audio → ALSA `snd_pcm_writei` to the DAC after out_dev switch. **TRIGGER COMMAND TAG: NOT yet pinned** (a `06xx` work-mode-switch carrying AIRPLAY=9, sent via `ui_send_cmd@0x43f82c` → mqueue `/player` idx 3, frame `TAG(4)+ext1(4hex)+len(4hex)+data`). Player also has a name-string command dispatch @0x482328 (e.g. `SET_USB_MODE`). **Next step to enable AirPlay from diskOS = read the mq_ui output/work-mode menu handler (callers of 0x43f82c) to capture the literal tag+payload for AIRPLAY/UAC/DLNA/ROON.**
|
||||
|
||||
### Also built-in network receivers (missed):
|
||||
- **DLNA/UPnP renderer** - `dlna_player.c`, UPnP RenderingControl/AVTransport SCPD. (mode DLNA=8)
|
||||
- **Roon Ready endpoint** - `roon_player.c` + `libroon.so` RAAT (`roon_transport_*`, volume/seek/shuffle). (mode ROON=10) UI screen `ui_roon_play.c`.
|
||||
- **QPlay** (Tencent/QQ Music) receiver - `mq_player_server_qplay.c`, MD5(Seed+PSK) auth, SetNetwork/SetLyric.
|
||||
- **BT SINK** (phone→player A2DP in) - `bt_sink_server.c/bt_sink_control.c` + AVRCP. (mode BTSINK=5)
|
||||
|
||||
### USB modes (partially missed)
|
||||
USB_MODE sysconfig + `usb_mode_change_handler@0x48eb80` (named cmd `SET_USB_MODE`@dispatch 0x482328). Gadgets via configfs: **`uac_demo`** (USB-DAC, functions uac1.a/uac2.a, /dev/usb_dac, UAC_SRATE→/ui tag aa1b) = work-mode UAC(7); **`storage_demo`** (card-reader, mass_storage.0, lun.0/file=/dev/mmcblk0). `set_usb_host`@0x24da24. **USB_MODE integer values (charge/DAC/reader/host) NOT yet mapped.**
|
||||
|
||||
### CORRECTIONS to earlier sections
|
||||
- **NAS is FULLY IMPLEMENTED** (`nas_control.c`: `mount -t cifs vers=3.0`, NFS, `smbclient -L`; NAS_CONFIG live) - §1's "NAS/qplay = NULL/unimplemented" was WRONG.
|
||||
- **Tag counts:** ~**221** distinct `/player` `0xxx` command tags + ~**102** `axxx` reply tags (mq_ui emits ~161 cmds, handles ~102 replies). §1/§4 documented ~10 + ~30 → **~95% of the command surface is still undocumented** (entire a4xx reply family untouched).
|
||||
- Other missed subsystems: **lyrics** (`lrc_paser.c`, `/usr/data/fiio/encoder.lrc`!), generic stream/I2S_IN/capture (`stream_audio.c`,`player_capture.c`), animated gif screensaver (LOCAL_IMG_ANIM), serial-number (`sn_nb.c`), image loaders (bmp/jpeg/png/yabmp), SACD/DST internals, MCU OTA (`ENTER_MCU_UPDATE_MODE`), full SPI/MCU handler family.
|
||||
|
||||
## 8. FULL command map - inventory complete, meanings mixed-confidence
|
||||
|
||||
**V2.09 dispatch tables** (entry = `{tag_str_ptr, handler_thunk_ptr}` 8B; thunks tail-jump via GOT to real handler):
|
||||
- **Table A @0x7c9d30 = 131 entries** (terminator @0x7ca148). Tags 06xx/07xx/08xx/0a51.
|
||||
- **Table B @0x7ca150 = 75 entries** (terminator @0x7ca3a8). Tags 00xx/01xx/02xx/04xx/05xx. ~36 in-table NULL (declared-unimplemented), ~11 thunk-but-GOT-null, ~28 live.
|
||||
- **MCU/SPI name-commands** via hw_ctrl.c @0x48d0b8 + sysconfig-key dispatch @0x488000 (NOT 0x482328 = that's the tag↔enum resolver).
|
||||
|
||||
**⚠️ RELIABILITY:** tag list = reliable (mechanical table walk). Per-tag MEANINGS: ~36 string-VERIFIED, ~80 INFERRED-from-family, ~15 UNKNOWN(runtime-registered GOT=0). **Some conflict with the 1.95-era LIVE-tested set** (1.95 tables were @0x7b7870/0x7b7c90; V2.09 layout differs). **RESOLVED:** the `0657`/`0666`/`06b3` "close_player" reading was a false positive - all three call a shared teardown preamble at the start of a disruptive mode change; that teardown is NOT their meaning. `0657`=source/work-mode transition, `0666`=output route (2/4/6), `06b3`=BT codec select. **Treat remaining INFERRED meanings as needing verification; trust live tests + resolved-handler traces over family inference.**
|
||||
|
||||
**String-VERIFIED V2.09 commands (Table A):** 0802=song count, 0620=get WIFI_MAC(/usr/data/fiio/nb.txt), 0704=wifi scan list, 0705=wifi connect(psid/passwd), 0800=write wpa_supplicant.conf, 0701=wifi init, 0700=close network card, 0720/0722=OTA(wget ota_patch), 0639/0689/0690/0634/0641=media-info query→a639 reply, 0675/0630=EQ/PEQ get(gain/filterType/frequency), **0678=set PEQ band(filterType/frequency/gain/qValue)**, 0621=DROP DB tables, 0624=mount/storage, 0815=set MEMORY_PLAY position, 0820/0821/0822=key single/double/long action, 0647=gapless, 0648=artist_class_type, **06b1=BT sample-rate param(0x40d4d8), 06b2=BT bit-depth param(0x40d66c), 06b3=BT CODEC SELECT(0=SBC 1=AAC 2/3/4=LDAC; 0x40eaf4; SBC frame `06b3000C0000` pins 44100), 06b4=LDAC QUALITY only(0x40dbe8; mob/std/high)**, 06b7=BT paired, 06c0=BT trust/power, 06c1=BT alias, 06c4=BT disconnect/remove. (0657/0666/06b3 close_player = RESOLVED false positive, see above.)
|
||||
**Table B verified:** **0100=main PLAY** (jumptable @0x650e2c by list_type), 0201=transport play/pause toggle (generic, →0x419ff4; NOT Roon-specific), 0112=playlist add song, 0115=add to custom list, 0113=love-list delete, 0114=custom-list delete, 0117=playlist reorder(src/dst), 0408=file/folder browse, 0413=album query, 0414=style/genre query, 02xx=NAS scan/browse(mq_player_server_nas.c, many GOT-null/unimplemented).
|
||||
**MCU/SPI name-commands (hw_ctrl.c, SPI to MCU):** GET/SET_DEVICE_MAX_VOL, SET_DEVICE_VOL, GET/SET_INPUT_MODE, SET_OUTPUT_MODE, SET_GAIN, GET/SET_DAC_FILTER, **SET_USB_MODE**, GET/SET_EQ_PRE, GET/SET_EQ_PARAMETER, SET_EQ_RESET, SAVE_EQ, SET_AUDIO_FORMAT, SET_FACTORY, **ENTER_MCU_UPDATE_MODE**, SET_MCU_POWER, SET_MUTE, SET_POWER_DOWN_TO_MCU (shutdown path, 63 call sites), GET/SET_ZERO_DATA_DETECT_TIME, BT_REPORT_RATE/STATE/CODEC_TO_MCU. sysconfig-key setters: RGB_COLOUR/TRIGGER_IN/SYS_THEME/LANGUAGE/USB_MODE/NETWORK_MODE/EQ_TYPE/MAX_VOL/BALANCE_VOL/POWER_SAVE/FILTER_TYPE/PLAY_MODE/FOLDER_JUMP/PLAY_GAP/INPUT_MODE/VOL_KNOB_MODE/OTA_CFG/PO_PRE_VOL/PO_VOL/PRE_VOL/TREBLE/BASS/LO_DISABLE/OS_MODE/DSD_DECODE.
|
||||
|
||||
## 6. TODO (next passes)
|
||||
- [x] ~~Live-verify 0657/0666~~ RESOLVED (handler-trace): 0657=source/work-mode transition, 0666=output route(2/4/6). "close_player" was a shared teardown preamble.
|
||||
- [ ] **Pin the AirPlay trigger tag** (mq_ui work-mode menu → callers of ui_send_cmd@0x43f82c) - the one fact needed to enable AirPlay from diskOS.
|
||||
- [x] **0622 is NOT a working rescan on V2.09:** diskOS "Rescan Library" sends 0622 → runs a long scan
|
||||
(~13min, scan_songs_thread) but writes 0 rows to song.db (mtime unchanged). Clearing SONG + reboot also scanned but
|
||||
wrote nothing. So song.db must be hand-built (laptop `tools/build_db.py` from sync_manifest.json → SONG +
|
||||
PLAYLIST_INFO + CUSTOM_PLAYLIST; diskOS lib sorts by TEXT so *_CODE only affects play-queue order). The real scan
|
||||
trigger (comm_scan_songs_thread) is still un-pinned. song.db schema is in this catalogue's table notes / dump from
|
||||
song.db.bak. Stock leaves DURATION/SAMPLE_RATE/BIT_RATE/CHANNELS=0 and ALBUM=TITLE for tagless rips - match that.
|
||||
- [x] **USB_MODE card-reader mode = VALUE 2 - LIVE-VERIFIED WORKING (transferred 32GB this way).** This is the clean
|
||||
stock MSC path for file transfer, and it SIDESTEPS the dangerous 0666 entirely. Details:
|
||||
- `usb_mode_change_handler` @ **0x48eb??** (log str "usb_mode_change_handler = %d" @vaddr 0x670358, ref'd
|
||||
by `addiu v1,v1,856` @0x48eb84). Reads/writes current usb_mode global @ **0x822d20**. Branches on the
|
||||
target mode value (s0): observed cases 2, 5, 6.
|
||||
- On **usb_mode==2** (`bne s0,2` fall-through @0x48ebc4) it accesses
|
||||
**`/sys/kernel/config/usb_gadget/storage_demo`** (lui 0x66 + 13620 = vaddr 0x663534) → builds a
|
||||
`mass_storage.0` function, **lun.0/file = `/dev/mmcblk0`** (whole SD), toggles cdrom/nofua/removable/ro,
|
||||
binds the UDC. (All configfs paths string-verified @ file 0x263534-0x263d8f.)
|
||||
- **CLEAN handoff CONFIRMED:** before/while exporting it UNMOUNTS the SD - `unmount_udisk` @0x668ed4 (called
|
||||
~7× around 0x470134-0x470388) runs `umount %s` with 5 retries + `umount -lf %s 2>/dev/null` lazy-force
|
||||
fallback on `/tmp/sdcard` (strs @0x268be8/0x266e40/0x268e40). So it properly releases mq_player's SD hold
|
||||
(the EBUSY blocker) - no corruption, unlike a raw composite-gadget export.
|
||||
- Trigger: **`SET_USB_MODE`** name-command (str @vaddr 0x66fe40; dispatch refs @0x48232c/0x4863ec/0x48d154;
|
||||
builders @0x408044/0x40fa94) and/or the `USB_MODE` sysconfig field. EXACT payload/value-encoding for
|
||||
SET_USB_MODE still to pin (one more trace), but the value is **2**.
|
||||
- ⚠ CAVEAT (untested): a UDC binds ONE gadget at a time, so binding `storage_demo` almost certainly UNBINDS
|
||||
our `serial_demo` ACM → **the serial shell will drop while in reader mode**; exiting reader mode (mode
|
||||
switch / replug) should restore it. TEST LIVE WITH USER PRESENT (power-cycle fallback). This is still far
|
||||
safer than 0666 (no player crash / MCU-reboot path).
|
||||
- ⇒ This unblocks **music-transfer-over-USB** via the stock mechanism: set USB_MODE=2 → laptop sees the SD as
|
||||
a USB drive → copy 32GB at USB speed + fsck → exit reader mode → in-device rescan. Replaces the unsafe
|
||||
wifi-push (watchdog reboots) and the blocked raw-MSC attempt.
|
||||
- [ ] Transcribe the full 221-tag command table + 102 a-frame replies.
|
||||
- [ ] Transcribe the full 207-tag command table (A+B) with handler addresses.
|
||||
- [ ] Verify audio/decoder/BT/DSD claims (§2) at instruction level (currently unverified).
|
||||
- [ ] Resolve SYSCONFIG enum value→option integer mappings.
|
||||
- [ ] Pin PLAYLIST_INFO's full column list (currently only ID firm).
|
||||
- [ ] diskOS wiring: status indicators from a644/a704/a706/a6c* + custom-EQ via PEQ write + 0689.
|
||||
|
||||
## 5b. SYSCONFIG - the master settings table (decoded 2026-06-30)
|
||||
`/usr/data/fiio/db/sysconfig.db` → table **SYSCONFIG** (single row), one INT column per setting.
|
||||
This is where every audio/system setting persists (the config-named commands write here).
|
||||
Columns incl: DSD_MODE, OUT_DEV, VOLUME, WORK_MODE, LIGHT_LEVEL(brightness), LIGTH_ON_TIME,
|
||||
MUTE, VOL_MODE, USB_MODE, NETWORK_MODE, EQ_TYPE(=0689 preset), MAX_VOL, BALANCE_VOL(channel
|
||||
balance), POWER_SAVE, FILTER_TYPE(DAC filter), PLAY_MODE(=0102), MEMORY_PLAY, FOLDER_JUMP,
|
||||
PLAY_GAP, INPUT_MODE, VOL_KNOB_MODE, BT_CODEC, SCREEN_ROT, PO_PRE_VOL/PO_VOL/PRE_VOL,
|
||||
THEME_MODE, CHARGE_PROTECT, LOCK_THEME, TREBLE, BASS, WIFI_STATUS, BT_STATUS, LO_DISABLE,
|
||||
OS_MODE, DSD_DECODE, SPDIF, AUTO_TIME, DRE_STATUS, DEVICE_OUTPUT, KEY_SINGLE/DOUBLE/LONG_CLICK_SLE,
|
||||
ARTIST_CLASS_TYPE, AUDIO_VOLUME_SET.
|
||||
Live sample (V2.09): FILTER_TYPE=1, DRE_STATUS=1, BALANCE_VOL=0, SPDIF=0, MAX_VOL=120,
|
||||
BT_CODEC=3, MEMORY_PLAY=0, FOLDER_JUMP=0, PLAY_GAP=0, SCREEN_ROT=0, CHARGE_PROTECT=0,
|
||||
ARTIST_CLASS_TYPE=0, TREBLE=0, BASS=0, OUT_DEV=6, OS_MODE=0, LO_DISABLE=0, DSD_MODE=1,
|
||||
INPUT_MODE=1, VOL_MODE=1. (No explicit GAIN column - likely VOL_MODE or encoded in OUT_DEV;
|
||||
confirm by toggling.) Filter enum (others.json 70-75): 0=FAST_LL,1=SLOW_LL,2=SLOW_PC,3=FAST_PC,
|
||||
4=NON_OS,5=Wideband_FF. To wire a setting in diskOS: write its SYSCONFIG column + send its apply
|
||||
command (verified: gapless 0647, BT codec 06b3 [0=SBC…4=LDAC-high], artist 0648, memory 0815, keys 0820-22; Gain/
|
||||
Filter/DRE/Balance apply-cmds TBD via strace-correlate of stock mq_ui - strace on mq_ui is SAFE,
|
||||
only strace-on-mq_player triggers the MCU reboot).
|
||||
|
||||
## 5c. Audio/DAC apply-commands - DECODED LIVE (2026-06-30, strace of stock mq_ui)
|
||||
Captured by strace `mq_timedsend` on stock mq_ui while toggling each setting (SAFE - strace
|
||||
on mq_ui, never mq_player). Frame format `<tag>000C<value 4hex>`. Note: SYSCONFIG column
|
||||
writes are DEFERRED (didn't update live), so these were correlated by the FRAME, not the DB.
|
||||
Background-noise tags to ignore: 0807 (recurring 0/1 status), 0704 (wifi scan).
|
||||
- **DRE** = `0812` - `0812000C0000` = ON, `0812000C0001` = OFF (startup sent 0 when DRE on).
|
||||
- **Gain** = `0645` - `0645000C0000`/`0001` (Low/High; startup sent 0).
|
||||
- **Channel balance** = `0713` - `0713000C01<level>`; center = `0101`, nudging streamed 0105..010C
|
||||
(one frame per step; 0x01 hi-byte = channel/side, lo-byte = level).
|
||||
- **DAC Filter** = `0653` - enum = menu order (clean sweep): 0=FAST_LL, 1=SLOW_LL, 2=SLOW_PC, 3=FAST_PC, 4=NON_OS, 5=Wideband_FF.
|
||||
- **SPDIF** = via output-route `0666` - SPDIF on = `0666000C0004`, analog = `0666000C0006`
|
||||
(0666 = OUT_DEV/output route: LOCAL_ANALOG=6, SPDIF=4; mutually exclusive with headphone).
|
||||
- **BT codec** = `06b3` (**CORRECTED**: mq_ui codec menu 0x464e90 sends 06b3, not 06b4):
|
||||
codec-settings menu sends payload-less `06b3000C0000`=SBC `…0001`=AAC `…0002/3/4`=LDAC mob/std/high.
|
||||
**But the ROUTE-to-speaker frame (live-captured 2026-08-03) is `06b3<len>000X<MAC>`** (X=codec, MAC payload
|
||||
kept for stock frame-shape; worker ignores it). diskOS route uses `06b3001D0000<MAC>` (SBC). `06b4` is
|
||||
LDAC-quality only. **Full working BT-transmit sequence → COMMAND_MAP.md "BT AUDIO OUTPUT" section.**
|
||||
- Also re-confirmed: `0666`=output route, `0642`=network mode (from startup sync).
|
||||
|
||||
## 5d. More settings - DECODED LIVE 2026-06-30 (strace stock mq_ui, clean batch)
|
||||
- **Channel balance** = `0713` - `0713000C<HHLL>`: center=`0000`; one side `00NN` (NN=step), other side `01NN`. (Same tag the audio-cluster capture saw.) Mixer-style, likely safe.
|
||||
- **Gapless** = `0647` - `0647000C0001` on / `0000` off. (matches COMMAND_MAP 0647=set gapless)
|
||||
- **Artist list grouping** = `0648` - `0648000C0000` Artist / `0001` Album-Artist. (ARTIST_CLASS_TYPE)
|
||||
- **Memory playback (resume)** = `0684` - `0684000C00<0|1|2>` = Off / Position / Song. (0684 was "media getter" in COMMAND_MAP - now confirmed the MEMORY_PLAY setter)
|
||||
- **Max volume** = `0711` - `0711000C00<NN>` where NN(hex)=cap, 0..0x78(120).
|
||||
NOTE: only SPDIF (0666 route) wedges on a raw send; these are config/mixer commands, expected safe - but per the wedge lesson, apply on live user change only, don't blind-send at boot.
|
||||
|
||||
## 5e. Sibilance EQ presets wired (2026-07-01, code-only, staged not-yet-deployed)
|
||||
Stock has 11 EQ presets (equalizer.json 0-10); diskOS had 9. Added preset 9="Sibilance 1",
|
||||
10="Sibilance 2" to OPT_EQ (settings.c) + T_EQ (npmenus.c), nopts 9->11, clamps q>8->q>10.
|
||||
Uses the existing 0689 path (ui_apply_eq, clamp already 0..20) so 0689000C0009/000A select them.
|
||||
Reference in New Issue
Block a user