From be4b0591ee743f8977be938eab0d02295e28ba90 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 13:28:44 +0900 Subject: [PATCH 01/88] pcm: implement pcm_switch_sink Change-Id: Iace01c2e97950cc794f3cf755dc358da6f3daa7f --- firmware/export/pcm.h | 1 + firmware/pcm.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/firmware/export/pcm.h b/firmware/export/pcm.h index 5f1509ad97..42931fbf21 100644 --- a/firmware/export/pcm.h +++ b/firmware/export/pcm.h @@ -64,6 +64,7 @@ bool pcm_is_initialized(void); enum pcm_sink_ids pcm_current_sink(void); const struct pcm_sink_caps* pcm_sink_caps(enum pcm_sink_ids sink); +bool pcm_switch_sink(enum pcm_sink_ids sink); /* shortcut for plugins */ const struct pcm_sink_caps* pcm_current_sink_caps(void); diff --git a/firmware/pcm.c b/firmware/pcm.c index eebcf70e1c..1bf0157182 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -291,6 +291,40 @@ const struct pcm_sink_caps* pcm_current_sink_caps(void) return pcm_sink_caps(pcm_current_sink()); } +bool pcm_switch_sink(enum pcm_sink_ids sink) +{ + logf("pcm_switch_sink %d to %d", cur_sink, sink); + if(sink >= ARRAYLEN(sinks)) { + return false; + } + + if(cur_sink == sink) { + return true; + } + /* save current sink before switching */ + struct pcm_sink* old_sink = sinks[cur_sink]; + /* update sink index */ + cur_sink = sink; + /* synchronize frequency */ + unsigned long cur_sampr = old_sink->caps.samprs[old_sink->pending_freq]; + pcm_set_frequency(cur_sampr); + pcm_apply_settings(); + /* when playing, continue playing on new sink */ + if(pcm_playing) { + old_sink->ops.stop(); + /* need more */ + const void *start; + size_t size; + if(pcm_get_more_int(&start, &size)) { + pcm_play_dma_start_int(start, size); + } else { + pcm_play_stop_int(); + } + } + + return true; +} + void pcm_play_data(pcm_play_callback_type get_more, pcm_status_callback_type status_cb, const void *start, size_t size) From 9ffc8a00ce4a24a3727bec7f243b174769e36b1c Mon Sep 17 00:00:00 2001 From: mojyack Date: Tue, 31 Mar 2026 11:52:43 +0900 Subject: [PATCH 02/88] pcm_mixer: implement mixer_switch_sink Change-Id: I1549470774f96a6f470817cbc5fe4611812de6fa --- firmware/export/pcm_mixer.h | 4 ++ firmware/pcm_mixer.c | 111 +++++++++++++++++++++--------------- 2 files changed, 68 insertions(+), 47 deletions(-) diff --git a/firmware/export/pcm_mixer.h b/firmware/export/pcm_mixer.h index 0607b474f6..be0e450d2e 100644 --- a/firmware/export/pcm_mixer.h +++ b/firmware/export/pcm_mixer.h @@ -23,6 +23,7 @@ #define PCM_MIXER_H #include +#include "pcm_sink.h" /** Simple config **/ @@ -111,6 +112,9 @@ void mixer_channel_play_pause(enum pcm_mixer_channel channel, bool play); /* Stop playback on a channel */ void mixer_channel_stop(enum pcm_mixer_channel channel); +/* Switch playback sink */ +bool mixer_switch_sink(enum pcm_sink_ids sink); + /* Set channel's amplitude factor */ void mixer_channel_set_amplitude(enum pcm_mixer_channel channel, unsigned int amplitude); diff --git a/firmware/pcm_mixer.c b/firmware/pcm_mixer.c index e49e3648b0..fb12e53373 100644 --- a/firmware/pcm_mixer.c +++ b/firmware/pcm_mixer.c @@ -286,6 +286,56 @@ static void mixer_start_pcm(void) start, mix_frame_size); } +/* Notify users of samplerate change */ +static void mixer_handle_sampr_change(unsigned int sampr) +{ + for (size_t i = 0; i < ARRAYLEN(active_channels) && active_channels[i]; i += 1) + { + struct mixer_channel* chan = active_channels[i]; + + /* Notify upstreams */ + if (chan->play_cbs) + { + if (chan->play_cbs->sampr_changed) + { + chan->play_cbs->sampr_changed(sampr); + } + if (chan->play_cbs->get_more) + { + /* Remake buffer */ + const void *start = NULL; + size_t size; + chan->play_cbs->get_more(&start, &size); + if (start && size) { + chan->start = start; + chan->size = size; + chan->last_size = 0; + } else { + channel_stopped(chan); + } + } + } + /* Notify buffer monitor */ + if (chan->buf_cbs) + { + if (chan->buf_cbs->sampr_changed) + { + chan->buf_cbs->sampr_changed(sampr); + } + } + } + + /* Work out how much space we really need */ + if (sampr > SAMPR_96) + mix_frame_size = 4; + else if (sampr > SAMPR_48) + mix_frame_size = 2; + else + mix_frame_size = 1; + + mix_frame_size *= MIX_FRAME_SAMPLES * 4; +} + /** Public interfaces **/ /* Start playback on a channel */ @@ -371,6 +421,19 @@ void mixer_channel_stop(enum pcm_mixer_channel channel) pcm_play_unlock(); } +/* Switch playback sink */ +bool mixer_switch_sink(enum pcm_sink_ids sink) +{ + if(pcm_current_sink() == sink) + return true; + + if(!pcm_switch_sink(sink)) + return false; + + mixer_handle_sampr_change(SAMPR_NUM(pcm_get_frequency())); + return true; +} + /* Set channel's amplitude factor */ void mixer_channel_set_amplitude(enum pcm_mixer_channel channel, unsigned int amplitude) @@ -461,53 +524,7 @@ void mixer_set_frequency(unsigned int samplerate) return; pcm_set_frequency(samplerate); - - for (size_t i = 0; i < ARRAYLEN(active_channels) && active_channels[i]; i += 1) - { - struct mixer_channel* chan = active_channels[i]; - - /* Notify upstreams */ - if (chan->play_cbs) - { - if (chan->play_cbs->sampr_changed) - { - chan->play_cbs->sampr_changed(SAMPR_NUM(samplerate)); - } - if (chan->play_cbs->get_more) - { - /* Remake buffer */ - const void *start = NULL; - size_t size; - chan->play_cbs->get_more(&start, &size); - if (start && size) { - chan->start = start; - chan->size = size; - chan->last_size = 0; - } else { - channel_stopped(chan); - } - } - } - /* Notify buffer monitor */ - if (chan->buf_cbs) - { - if (chan->buf_cbs->sampr_changed) - { - chan->buf_cbs->sampr_changed(SAMPR_NUM(samplerate)); - } - } - } - - /* Work out how much space we really need */ - if (SAMPR_NUM(samplerate) > SAMPR_96) - mix_frame_size = 4; - else if (SAMPR_NUM(samplerate) > SAMPR_48) - mix_frame_size = 2; - else - mix_frame_size = 1; - - mix_frame_size *= MIX_FRAME_SAMPLES * 4; - + mixer_handle_sampr_change(SAMPR_NUM(pcm_get_frequency())); if (pcm_is_initialized()) pcm_apply_settings(); } From 39abe4f6984d474017417477010346a51c32c451 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Fri, 17 Apr 2026 20:56:29 -0400 Subject: [PATCH 03/88] Translation updates: * Simplified Chinese (Wang Ji) * Italian (Alessio Lenzi) Change-Id: I21dcbfe07b128d080a10e52d2d4ebedd592e6868 --- apps/lang/chinese-simp.lang | 156 ++++++++++++++++++++++++++++++++++-- apps/lang/italiano.lang | 120 ++++++++++++++++++++++++++- 2 files changed, 264 insertions(+), 12 deletions(-) diff --git a/apps/lang/chinese-simp.lang b/apps/lang/chinese-simp.lang index 1120999871..e714f0c713 100644 --- a/apps/lang/chinese-simp.lang +++ b/apps/lang/chinese-simp.lang @@ -14676,7 +14676,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14691,7 +14691,7 @@ *: "请按LEFT键取消。" - android,hifietma*,zenvision: "请按BACK键取消。" + android,hifietma*: "请按BACK键取消。" cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "请按POWER键取消。" ihifi760,ihifi960: "双击RETURN键取消。" ihifi770,ihifi770c,ihifi800: "请按HOME键取消。" @@ -14707,7 +14707,7 @@ *: "请按LEFT键取消" - android,hifietma*,zenvision: "请按BACK键取消" + android,hifietma*: "请按BACK键取消" cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "请按POWER键取消" ihifi760,ihifi960: "双击RETURN键取消" ihifi770,ihifi770c,ihifi800: "请按HOME键取消" @@ -15202,7 +15202,7 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" @@ -15811,16 +15811,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "默认浏览器" + *: "" - *: "默认浏览器" + *: "" @@ -16967,3 +16967,143 @@ *: "~U S B" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "播报状态" + + + *: "播报状态" + + + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "总是记住上次的文件夹" + + + *: "总是记住上次的文件夹" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "找不到文件" + + + *: "找不到文件" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "按文件展示" + + + *: "按文件展示" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "切换左&右" + + + *: "切换左右" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "设定计时器" + + + *: "设定计时器" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "运行中" + + + *: "运行中" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "暂停" + + + *: "暂停" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "超时" + + + *: "超时" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "完成" + + + *: "完成" + + diff --git a/apps/lang/italiano.lang b/apps/lang/italiano.lang index 5e837aa636..6d83d3f61e 100644 --- a/apps/lang/italiano.lang +++ b/apps/lang/italiano.lang @@ -15804,16 +15804,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Browser Predefinito" + *: "" - *: "Browser Predefinito" + *: "" @@ -16988,3 +16988,115 @@ *: "~U S B" + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "File non trovato" + + + *: "File non trovato" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Mostra nei file" + + + *: "Mostra nei file" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Scambia sinistro & destro" + + + *: "Scambia sinistro e destro" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "IMPOSTA TIMER" + + + *: "Imposta timer" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "IN CORSO" + + + *: "In corso" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "IN PAUSA" + + + *: "In pausa" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "TEMPO SUPPLEMENTARE" + + + *: "Tempo supplementare" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "TERMINATO" + + + *: "Terminato" + + From 9dce0c32584ae60b7ed1b0d2707c06797784208c Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Fri, 17 Apr 2026 21:42:08 -0400 Subject: [PATCH 04/88] Work around a false positive compiler warning in pcm_switch_sink() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CC firmware/pcm.c firmware/pcm.c: In function ‘pcm_switch_sink’: firmware/pcm.c:311:38: warning: array subscript 1 is above array bounds of ‘struct pcm_sink *[1]’ [-Warray-bounds] 311 | struct pcm_sink* old_sink = sinks[cur_sink]; | ~~~~~^~~~~~~~~~ firmware/pcm.c:79:25: note: while referencing ‘sinks’ 79 | static struct pcm_sink* sinks[PCM_SINK_NUM] = { | PCM_SINK_NUM is 1, and cur_sink is initialized to 0. It can never be set above 0. cur_sink can never be >= PCM_SINK_NUM, ie 0, but for some reason the compiler thinks otherwise.... sometimes. This only shows up on native ARM builds with GCC9.5.0 Change-Id: I1aa731a4ee21c46a264c8b70833e3b43e777e8a7 --- firmware/export/pcm_sink.h | 1 + firmware/pcm.c | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/firmware/export/pcm_sink.h b/firmware/export/pcm_sink.h index 77c64428f6..24a1962189 100644 --- a/firmware/export/pcm_sink.h +++ b/firmware/export/pcm_sink.h @@ -52,6 +52,7 @@ struct pcm_sink { enum pcm_sink_ids { PCM_SINK_BUILTIN = 0, + PCM_SINK_NUM }; /* defined in each platform pcm source */ diff --git a/firmware/pcm.c b/firmware/pcm.c index 1bf0157182..25feed5a49 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -21,6 +21,7 @@ #include #include "system.h" #include "kernel.h" +#include "panic.h" /* Define LOGF_ENABLE to enable logf output in this file */ //#define LOGF_ENABLE @@ -76,7 +77,7 @@ * */ -static struct pcm_sink* sinks[1] = { +static struct pcm_sink* sinks[PCM_SINK_NUM] = { [PCM_SINK_BUILTIN] = &builtin_pcm_sink, }; static enum pcm_sink_ids cur_sink = PCM_SINK_BUILTIN; @@ -247,7 +248,7 @@ void pcm_init(void) { logf("pcm_init"); - for(size_t i = 0; i < ARRAYLEN(sinks); i += 1) { + for(size_t i = 0; i < PCM_SINK_NUM; i += 1) { struct pcm_sink* sink = sinks[i]; sink->pending_freq = sink->caps.default_freq; sink->configured_freq = -1U; @@ -261,7 +262,7 @@ void pcm_postinit(void) { logf("pcm_postinit"); - for(size_t i = 0; i < ARRAYLEN(sinks); i += 1) { + for(size_t i = 0; i < PCM_SINK_NUM; i += 1) { struct pcm_sink* sink = sinks[i]; sink->ops.postinit(); sink->pcm_is_ready = true; @@ -294,15 +295,29 @@ const struct pcm_sink_caps* pcm_current_sink_caps(void) bool pcm_switch_sink(enum pcm_sink_ids sink) { logf("pcm_switch_sink %d to %d", cur_sink, sink); - if(sink >= ARRAYLEN(sinks)) { + if(sink >= PCM_SINK_NUM) { return false; } if(cur_sink == sink) { return true; } + + /* This should not be possible but it silences + a false warning that only occurs with with GCC9.5 on bare metal ARM. + */ +#if __GNUC__ == 9 && defined(CPU_ARM) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + /* save current sink before switching */ struct pcm_sink* old_sink = sinks[cur_sink]; + +#if __GNUC__ == 9 +#pragma GCC diagnostic pop +#endif + /* update sink index */ cur_sink = sink; /* synchronize frequency */ From 1adadc943dc4d804ef02cdfb351da447ddc4ce88 Mon Sep 17 00:00:00 2001 From: Vencislav Atanasov Date: Mon, 30 Mar 2026 19:34:05 +0300 Subject: [PATCH 05/88] Change which macro is used to check for Windows targets This fixes a false-positive on macOS that was caused by the macro "#define TARGET_OS_WIN32 0" Change-Id: I1fcfb19b5aae4f63f00b9500094d619c4f7eea4b --- utils/jztool/Makefile | 2 +- utils/mks5lboot/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/jztool/Makefile b/utils/jztool/Makefile index d1bbae578c..02436d47bc 100644 --- a/utils/jztool/Makefile +++ b/utils/jztool/Makefile @@ -21,7 +21,7 @@ EXTRADEPS := libucl.a libmicrotar.a CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -) -ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32) +ifeq ($(findstring __WIN32__,$(CPPDEFINES)),__WIN32__) # TODO: support Windows else ifeq ($(findstring APPLE,$(CPPDEFINES)),APPLE) diff --git a/utils/mks5lboot/Makefile b/utils/mks5lboot/Makefile index 72ea521d5f..5cd4d4f826 100644 --- a/utils/mks5lboot/Makefile +++ b/utils/mks5lboot/Makefile @@ -18,7 +18,7 @@ EXTRADEPS := CPPDEFINES := $(shell echo foo | $(CROSS)$(CC) -dM -E -) -ifeq ($(findstring WIN32,$(CPPDEFINES)),WIN32) +ifeq ($(findstring __WIN32__,$(CPPDEFINES)),__WIN32__) LDOPTS += -lsetupapi # optional libusb support (needed for WinUSB and libusbK drivers) ifeq ($(findstring MINGW,$(CPPDEFINES)),MINGW) From 900061422460cf6eb308d26341e153be6fce52aa Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sat, 18 Apr 2026 12:34:15 +0200 Subject: [PATCH 06/88] pitchscreen: Fix dropouts when button held down Change-Id: I0f23ad6463f7938e832b0c5da2c121c5a5e5faa8 --- apps/plugins/pitch_screen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/plugins/pitch_screen.c b/apps/plugins/pitch_screen.c index 4af34fed3b..647b7a17d1 100644 --- a/apps/plugins/pitch_screen.c +++ b/apps/plugins/pitch_screen.c @@ -1106,6 +1106,7 @@ int gui_syncpitchscreen_run(void) /* when needed */ new_speed = 0; } + rb->yield(); } //rb->pcmbuf_set_low_latency(false); From 088b3345fbbf5e5a97d84c87acd95a77d2808878 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sat, 18 Apr 2026 14:28:34 +0200 Subject: [PATCH 07/88] pitchscreen: Fix unbalanced pcmbuf_set_low_latency when connecting USB Change-Id: I73652ef27c24485faefe16c678406d78b0d6e0fd --- apps/plugins/pitch_screen.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/plugins/pitch_screen.c b/apps/plugins/pitch_screen.c index 647b7a17d1..62cb170408 100644 --- a/apps/plugins/pitch_screen.c +++ b/apps/plugins/pitch_screen.c @@ -1109,7 +1109,6 @@ int gui_syncpitchscreen_run(void) rb->yield(); } - //rb->pcmbuf_set_low_latency(false); //pop_current_activity(); /* Clean up */ @@ -1216,6 +1215,7 @@ enum plugin_status plugin_start(const void* parameter) * -s=90 sets speed to 90% if timestrech is enabled * -k=true -k1 enables time stretch -k0 -kf-kn disables */ + enum plugin_status ret = PLUGIN_OK; bool gui = false; rb->pcmbuf_set_low_latency(true); @@ -1275,7 +1275,7 @@ enum plugin_status plugin_start(const void* parameter) } if (gui && gui_syncpitchscreen_run() == 1) - return PLUGIN_USB_CONNECTED; + ret = PLUGIN_USB_CONNECTED; rb->pcmbuf_set_low_latency(false); - return PLUGIN_OK; + return ret; } From 4e62c9f8cbf3212ede5537c7f293160e6ac2ea37 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 21 Apr 2026 12:55:36 -0400 Subject: [PATCH 08/88] FS#13879 - German Translation update (Karl Huber) Change-Id: Icee3d2778b4a0afaf567776e43a996a8e99d2cce --- apps/lang/deutsch.lang | 120 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang index 806ee79348..28c3529a3b 100644 --- a/apps/lang/deutsch.lang +++ b/apps/lang/deutsch.lang @@ -15822,16 +15822,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Standardbrowser" + *: "" - *: "Standardbrowser" + *: "" @@ -17006,3 +17006,115 @@ *: "~U S B" + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Datei nicht gefunden" + + + *: "Datei nicht gefunden" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Zeige in Dateien" + + + *: "Zeige in Dateien" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Rechts & Links tauschen" + + + *: "Rechts und Links tauschen" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "Timer stellen" + + + *: "Timer stellen" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "LÄUFT" + + + *: "Läuft" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "ANGEHALTEN" + + + *: "Angehalten" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "ÜBERZEIT" + + + *: "Überzeit" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "BEENDET" + + + *: "Beendet" + + From 9ac6edf750a060b3e2c1f8fb4236a501f7aaf68a Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 21 Apr 2026 14:32:48 +0100 Subject: [PATCH 09/88] Add panicf to plugin and codec API Change-Id: I0e11ecaf8e18233f682f572f479cfdd141c99bd5 --- apps/codecs.c | 2 ++ apps/plugin.c | 2 ++ apps/plugin.h | 1 + lib/rbcodec/codecs/codecs.h | 1 + 4 files changed, 6 insertions(+) diff --git a/apps/codecs.c b/apps/codecs.c index 7bd2de367e..c304b6eaf2 100644 --- a/apps/codecs.c +++ b/apps/codecs.c @@ -50,6 +50,7 @@ #include "splash.h" #include "general.h" #include "rbpaths.h" +#include "panic.h" #define LOGF_ENABLE #include "logf.h" @@ -150,6 +151,7 @@ struct codec_api ci = { /* new stuff at the end, sort into place next time the API gets incompatible */ + panicf, }; diff --git a/apps/plugin.c b/apps/plugin.c index 6dfb9ce765..c2ab8ad322 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -49,6 +49,7 @@ #include "core_keymap.h" #include "language.h" #include "statusbar-skinned.h" +#include "panic.h" #if CONFIG_CHARGING #include "power.h" @@ -870,6 +871,7 @@ static const struct plugin_api rockbox_api = { /* new stuff at the end, sort into place next time the API gets incompatible */ + panicf, }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index 5dcbdcaee7..f6c3434692 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -1024,6 +1024,7 @@ struct plugin_api { /* new stuff at the end, sort into place next time the API gets incompatible */ + void (*panicf)(const char *msg, ...); }; /* plugin header */ diff --git a/lib/rbcodec/codecs/codecs.h b/lib/rbcodec/codecs/codecs.h index 5e977c6f13..dad5cbe8f2 100644 --- a/lib/rbcodec/codecs/codecs.h +++ b/lib/rbcodec/codecs/codecs.h @@ -224,6 +224,7 @@ struct codec_api { /* new stuff at the end, sort into place next time the API gets incompatible */ + void (*panicf)(const char *msg, ...); }; /* codec header */ From d81505336030a1d9f2704c2d87e4f6c333f72963 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 21 Apr 2026 14:33:58 +0100 Subject: [PATCH 10/88] Add support for -fstack-protector in native builds -fstack-protector only needs a small amount of runtime support to work on native builds. It increases code size by ~1.5% on ARM/MIPS; -fstack-protector-strong adds 3-4%. This is disabled by default and must be enabled by passing '--with-stack-protector' to configure. Change-Id: If952e711d3673c9b469895f08c7bff70b3d95df6 --- apps/plugins/lib/gcc-support.c | 9 +++++++++ firmware/asm/SOURCES | 4 ++++ firmware/asm/stack-protector.c | 29 +++++++++++++++++++++++++++++ lib/rbcodec/codecs/codec_crt0.c | 9 +++++++++ tools/configure | 17 +++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 firmware/asm/stack-protector.c diff --git a/apps/plugins/lib/gcc-support.c b/apps/plugins/lib/gcc-support.c index f1fbe4ec58..53b01b9f01 100644 --- a/apps/plugins/lib/gcc-support.c +++ b/apps/plugins/lib/gcc-support.c @@ -34,6 +34,15 @@ void __aeabi_ldiv0(void) __attribute__((alias("__div0"))); #endif #endif +#if defined(USE_STACK_PROTECTOR) +const uint32_t __stack_chk_guard = 0x3BADC0DE; + +void __stack_chk_fail(void) +{ + rb->panicf("plugin smashed stack"); +} +#endif + void *memcpy(void *dest, const void *src, size_t n) { return rb->memcpy(dest, src, n); diff --git a/firmware/asm/SOURCES b/firmware/asm/SOURCES index 463c120213..74ba94c204 100644 --- a/firmware/asm/SOURCES +++ b/firmware/asm/SOURCES @@ -65,3 +65,7 @@ lcd-as-memframe.c #endif /* CPU_ARM */ #endif /* LCD_DEPTH */ #endif + +#if defined(USE_STACK_PROTECTOR) +stack-protector.c +#endif diff --git a/firmware/asm/stack-protector.c b/firmware/asm/stack-protector.c new file mode 100644 index 0000000000..b0de405980 --- /dev/null +++ b/firmware/asm/stack-protector.c @@ -0,0 +1,29 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id$ +* +* Copyright (C) 2026 by Aidan MacDonald +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied +* +****************************************************************************/ +#include "panic.h" +#include + +const uint32_t __stack_chk_guard = 0x1BADC0DE; + +void __stack_chk_fail(void) +{ + panicf("stack smashing detected"); +} diff --git a/lib/rbcodec/codecs/codec_crt0.c b/lib/rbcodec/codecs/codec_crt0.c index e95b3e31e6..54035fc232 100644 --- a/lib/rbcodec/codecs/codec_crt0.c +++ b/lib/rbcodec/codecs/codec_crt0.c @@ -78,3 +78,12 @@ void __attribute__((naked)) __div0(void) asm volatile("bx %0" : : "r"(ci->__div0)); } #endif + +#if defined(USE_STACK_PROTECTOR) +const uint32_t __stack_chk_guard = 0x2BADC0DE; + +void __stack_chk_fail(void) +{ + ci->panicf("codec smashed stack"); +} +#endif diff --git a/tools/configure b/tools/configure index 4e6439a0c1..27fb62803c 100755 --- a/tools/configure +++ b/tools/configure @@ -1531,6 +1531,9 @@ help() { --with-address-sanitizer Enables the AddressSanitizer feature. Forces SDL threads. --with-ubsan Enables the UB Sanitizer feature. Forces SDL threads. + --with-stack-protector[=MODE] + Enable -fstack-protector-MODE option. With no MODE, + enables the normal -fstack-protector option. --32-bit Force a 32-bit simulator (use with --sdl-threads for duke3d) --prefix Target installation directory --compiler-prefix Override compiler prefix (inherently dangerous) @@ -1557,6 +1560,7 @@ ARG_THREAD_SUPPORT= ARG_32BIT= ARG_ADDR_SAN= ARG_UBSAN= +ARG_STACK_PROTECTOR= ARG_PLUGINS= err= for arg in "$@"; do @@ -1583,6 +1587,8 @@ for arg in "$@"; do ARG_THREAD_SUPPORT=0;; --with-address-sanitizer) ARG_ADDR_SAN=1;; --with-ubsan) ARG_UBSAN=1;; + --with-stack-protector=*) ARG_STACK_PROTECTOR=`echo "$arg" | cut -d = -f 2`;; + --with-stack-protector) ARG_STACK_PROTECTOR=default;; --prefix=*) ARG_PREFIX=`echo "$arg" | cut -d = -f 2`;; --compiler-prefix=*) ARG_COMPILER_PREFIX=`echo "$arg" | cut -d = -f 2`;; --help) help;; @@ -4694,6 +4700,17 @@ if [ "$ARG_RBDIR" != "" ]; then echo "Using alternate rockbox dir: ${rbdir}" fi +if [ "$ARG_STACK_PROTECTOR" != "" ]; then + echo "Stack protector mode: $ARG_STACK_PROTECTOR" + extradefines="$extradefines -DUSE_STACK_PROTECTOR" + + if [ "$ARG_STACK_PROTECTOR" = "default" ]; then + GCCOPTS="$GCCOPTS -fstack-protector" + else + GCCOPTS="$GCCOPTS -fstack-protector-${ARG_STACK_PROTECTOR}" + fi +fi + cat > autoconf.h.new < Date: Tue, 21 Apr 2026 22:05:51 +0100 Subject: [PATCH 11/88] dircache: increase stack size to avoid stack overflows On FS#13821, a test build with -fstack-protector-all (which adds 4 bytes of stack to each function, roughly) overflowed the dircache stack on the Hifiwalker H2. In my own testing, the dircache thread hit 94% stack use on the Fiio M3K using the same settings. That seems a bit too close to the limit, especially since the dircache uses recursion and might consume more stack space for deeply nested directories. Adding 768 bytes should provide enough of a safety margin. This increases the stack size from 1.25k to 2k on most targets, including all X1000 targets. Change-Id: I900c19da9fb33f539d02b00830aedeb15c7449e2 --- firmware/include/dircache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/include/dircache.h b/firmware/include/dircache.h index 2cf838e539..d610c63245 100644 --- a/firmware/include/dircache.h +++ b/firmware/include/dircache.h @@ -44,7 +44,7 @@ the limiting factor is the scanning thread stack size, not the implementation -- tune the two together */ #define DIRCACHE_MAX_DEPTH 15 -#define DIRCACHE_STACK_SIZE (DEFAULT_STACK_SIZE + 0x100) +#define DIRCACHE_STACK_SIZE (DEFAULT_STACK_SIZE + 0x400) /* memory buffer constants that control allocation */ #define DIRCACHE_RESERVE (1024*64) /* 64 KB - new entry slack */ From f9a5d6fe862def561b97420b5791b2d07432e204 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Wed, 22 Apr 2026 19:23:59 +0100 Subject: [PATCH 12/88] quake: fix crash from strange printf behavior (FS#13821) This was a latent bug exposed by commit a3f2b64a467c ("Enable float formatting in printf"). The version number is passed as 1.09f, but somehow ends up printing a string that is so huge it overruns the destination buffer and corrupts the return address on the stack. Using snprintf prevents the buffer overrun and gets quake working again, though this doesn't address the underlying bug with printf. Change-Id: I37e4426bc6ebca42d83b5a3b659da497b652d1ae --- apps/plugins/sdl/progs/quake/draw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/sdl/progs/quake/draw.c b/apps/plugins/sdl/progs/quake/draw.c index c870e6baba..5489810e1f 100644 --- a/apps/plugins/sdl/progs/quake/draw.c +++ b/apps/plugins/sdl/progs/quake/draw.c @@ -548,7 +548,7 @@ void Draw_ConsoleBackground (int lines) conback = Draw_CachePic ("gfx/conback.lmp"); dest = conback->data + 320 - 43 + 320*186; - sprintf (ver, "%.2f", (float)VERSION); + snprintf (ver, sizeof(ver), "%.2f", (float)VERSION); for (x=0 ; x Date: Thu, 23 Apr 2026 14:15:46 +0200 Subject: [PATCH 13/88] pcm_mixer: fix missing NULL check for play_cbs Fixes Metronome plugin crashing (regression introduced in cb04b81) Does *not* fix missing Metronome sound (regression introduced in 017dd72) Change-Id: I1da9aa2c937b267a3d5b122c431eaa9f7e748440 --- firmware/pcm_mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/pcm_mixer.c b/firmware/pcm_mixer.c index fb12e53373..607db70839 100644 --- a/firmware/pcm_mixer.c +++ b/firmware/pcm_mixer.c @@ -163,7 +163,7 @@ fill_frame: if (chan->size == 0) { - if (chan->play_cbs->get_more) + if (chan->play_cbs && chan->play_cbs->get_more) { chan->play_cbs->get_more(&chan->start, &chan->size); ALIGN_AUDIOBUF(chan->start, chan->size); From 7960dbb9a722c3c73e4dd6660917153203c4bae2 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Thu, 23 Apr 2026 16:41:49 +0200 Subject: [PATCH 14/88] plugins: Fix muted mixer channel (FS#13809) Fixes regression where you may not have heard any audio from some of the plugins modified by commit 017dd72, due to the playback channel not being unmuted. Change-Id: Iaa184161c79d353dff6ef9bf3e0b39778c8b1bcd --- apps/plugins/doom/i_sound.c | 2 ++ apps/plugins/metronome.c | 5 +++-- apps/plugins/pacbox/pacbox.c | 2 ++ apps/plugins/pdbox/PDa/src/s_audio_rockbox.c | 6 ++++++ apps/plugins/rockboy/rbsound.c | 2 ++ apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c | 2 ++ apps/plugins/test_sampr.c | 8 +++----- apps/plugins/xrick/system/syssnd_rockbox.c | 6 ++++++ apps/plugins/xworld/sys.c | 2 ++ apps/plugins/zxbox/spsound.c | 2 ++ 10 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/plugins/doom/i_sound.c b/apps/plugins/doom/i_sound.c index 068aa915c5..7dd07bd802 100644 --- a/apps/plugins/doom/i_sound.c +++ b/apps/plugins/doom/i_sound.c @@ -474,6 +474,7 @@ void I_SubmitSound(void) void I_ShutdownSound(void) { + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); rb->mixer_set_frequency(HW_SAMPR_DEFAULT); } @@ -508,6 +509,7 @@ void I_InitSound() rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); #endif rb->mixer_set_frequency(samplerate); + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ vol_lookup=malloc(128*256*sizeof(int)); diff --git a/apps/plugins/metronome.c b/apps/plugins/metronome.c index f7baccf84f..0379bc0164 100644 --- a/apps/plugins/metronome.c +++ b/apps/plugins/metronome.c @@ -787,7 +787,6 @@ static bool beating = false; /* A beat is/was playing and count needs to increas static int display_state = 0; /* Current display state code. */ static bool display_trigger = false; /* Draw display on next occasion */ -static bool sound_active = false; static bool sound_paused = true; /* global static buffer for messages in any situation */ @@ -1168,7 +1167,7 @@ static void timer_callback(void) if(minitick >= period) { minitick = 0; - if(!sound_active && !sound_paused && !tap_count) + if(!sound_paused && !tap_count) { sound_trigger = true; rb->reset_poweroff_timer(); @@ -1213,6 +1212,7 @@ static void cleanup(void) if(fd >= 0) rb->close(fd); metronome_pause(); + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); tweak_volume(0); rb->led(0); @@ -1561,6 +1561,7 @@ enum plugin_status plugin_start(const void* file) rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); #endif rb->mixer_set_frequency(SAMPR_44); + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ if(file) { diff --git a/apps/plugins/pacbox/pacbox.c b/apps/plugins/pacbox/pacbox.c index e024c6d493..a709c78a9b 100644 --- a/apps/plugins/pacbox/pacbox.c +++ b/apps/plugins/pacbox/pacbox.c @@ -411,6 +411,7 @@ static void start_sound(void) .get_more = get_more, }; rb->mixer_set_frequency(caps->samprs[sr_index]); + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, &cbs, NULL, 0); sound_playing = true; @@ -424,6 +425,7 @@ static void stop_sound(void) if (!sound_playing) return; + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); rb->mixer_set_frequency(HW_SAMPR_DEFAULT); diff --git a/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c b/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c index fd3c764645..94779e6123 100644 --- a/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c +++ b/apps/plugins/pdbox/PDa/src/s_audio_rockbox.c @@ -65,6 +65,9 @@ void rockbox_open_audio(int rate) /* Set sample rate of the audio buffer. */ rb->mixer_set_frequency(rate); + /* Be sure channel is audible */ + rb->pcmbuf_fade(false, true); + /* Initialize output buffer. */ for(i = 0; i < OUTBUFSIZE; i++) outbuf[i].fill = 0; @@ -77,6 +80,9 @@ void rockbox_open_audio(int rate) /* Close audio. */ void rockbox_close_audio(void) { + /* Mute channel */ + rb->pcmbuf_fade(false, false); + /* Stop playback. */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); diff --git a/apps/plugins/rockboy/rbsound.c b/apps/plugins/rockboy/rbsound.c index 7d10dbdb83..7e06f5a0aa 100644 --- a/apps/plugins/rockboy/rbsound.c +++ b/apps/plugins/rockboy/rbsound.c @@ -56,12 +56,14 @@ void rockboy_pcm_init(void) #endif rb->mixer_set_frequency(pcm.hz); /* 44100 22050 11025 */ + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ } void rockboy_pcm_close(void) { memset(&pcm, 0, sizeof pcm); newly_started = true; + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); rb->mixer_set_frequency(HW_SAMPR_DEFAULT); } diff --git a/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c b/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c index 2ffcda6b46..d24c22d34b 100644 --- a/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c +++ b/apps/plugins/sdl/src/audio/rockbox/SDL_rockboxaudio.c @@ -215,6 +215,7 @@ static Uint8 *ROCKBOXAUD_GetAudioBuf(_THIS) static void ROCKBOXAUD_CloseAudio(_THIS) { + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); if ( this->hidden->mixbuf != NULL ) { SDL_FreeAudioMem(this->hidden->mixbuf); @@ -260,6 +261,7 @@ static int ROCKBOXAUD_OpenAudio(_THIS, SDL_AudioSpec *spec) LOGF("samplerate %d", spec->freq); rb->mixer_set_frequency(spec->freq); + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ /* Allocate mixing buffer */ this->hidden->mixlen = spec->size; diff --git a/apps/plugins/test_sampr.c b/apps/plugins/test_sampr.c index dfaea58183..e3a0b0c420 100644 --- a/apps/plugins/test_sampr.c +++ b/apps/plugins/test_sampr.c @@ -214,6 +214,7 @@ static void play_tone(bool volume_set) #if INPUT_SRC_CAPS != 0 /* Select playback */ rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); #endif #ifdef HAVE_ADJUSTABLE_CPU_FREQ @@ -221,11 +222,7 @@ static void play_tone(bool volume_set) #endif rb->mixer_set_frequency(hw_sampr); - -#if INPUT_SRC_CAPS != 0 - /* Recordable targets can play back from other sources */ - rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); -#endif + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ gen_quit = false; output_clear(); @@ -263,6 +260,7 @@ static void play_tone(bool volume_set) rb->thread_wait(gen_thread_id); + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); #ifdef HAVE_ADJUSTABLE_CPU_FREQ diff --git a/apps/plugins/xrick/system/syssnd_rockbox.c b/apps/plugins/xrick/system/syssnd_rockbox.c index ec8886f881..63cddbd10f 100644 --- a/apps/plugins/xrick/system/syssnd_rockbox.c +++ b/apps/plugins/xrick/system/syssnd_rockbox.c @@ -257,6 +257,9 @@ bool syssnd_init(void) rb->mixer_set_frequency(HW_FREQ_44); + /* Be sure channel is audible */ + rb->pcmbuf_fade(false, true); + rb->memset(channels, 0, sizeof(channels)); rb->memset(mixBuffers, 0, sizeof(mixBuffers)); @@ -280,6 +283,9 @@ void syssnd_shutdown(void) return; } + /* Mute channel */ + rb->pcmbuf_fade(false, false); + /* Stop playback. */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); diff --git a/apps/plugins/xworld/sys.c b/apps/plugins/xworld/sys.c index a8c2d695fe..f51d7e3357 100644 --- a/apps/plugins/xworld/sys.c +++ b/apps/plugins/xworld/sys.c @@ -1032,12 +1032,14 @@ void sys_startAudio(struct System* sys, AudioCallback callback, void *param) static const struct mixer_play_cbs cbs = { .get_more = get_more, }; + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, &cbs, NULL, 0); } void sys_stopAudio(struct System* sys) { (void) sys; + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); } diff --git a/apps/plugins/zxbox/spsound.c b/apps/plugins/zxbox/spsound.c index 4b38e68171..28f65c5fe8 100644 --- a/apps/plugins/zxbox/spsound.c +++ b/apps/plugins/zxbox/spsound.c @@ -112,12 +112,14 @@ static void open_snd(void) rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); #endif rb->mixer_set_frequency(SAMPR_44); + rb->pcmbuf_fade(false, true); /* Be sure channel is audible */ } static void close_snd(int normal) { (void)normal; sound_avail = 0; + rb->pcmbuf_fade(false, false); /* Mute channel */ rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); } From d02ad9b749aa751cba741f10039b35f5a6850d07 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 24 Apr 2026 00:13:25 +0100 Subject: [PATCH 15/88] pcm: improve workaround for false -Warray-bounds in pcm_switch_sink Change-Id: I81ff414ed07bbc61367250c25dc99c11e79d21d2 --- firmware/include/gcc_extensions.h | 14 ++++++++++++++ firmware/pcm.c | 25 ++++++++++++++----------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/firmware/include/gcc_extensions.h b/firmware/include/gcc_extensions.h index ecead8cbb5..3c4f0476a9 100644 --- a/firmware/include/gcc_extensions.h +++ b/firmware/include/gcc_extensions.h @@ -77,5 +77,19 @@ #define UNUSED_ATTR #endif +/* + * Tell the compiler to assume 'x' is true. With a new enough GCC + * there's an attribute for this based on C++23's assume attribute. + * On older GCC we need to play tricks with __builtin_unreachable(). + * As a result 'x' may or may not be evaluated at runtime and should + * be side-effect free to ensure it doesn't have any runtime impact. + */ +#if defined(__GNUC__) && (__GNUC__ >= 13) +# define ASSUME(x) __attribute__((assume((x)))) +#elif defined(__GNUC__) +# define ASSUME(x) do { if(!(x)) __builtin_unreachable(); } while (0) +#else +# define ASSUME(x) +#endif #endif /* _GCC_EXTENSIONS_H_ */ diff --git a/firmware/pcm.c b/firmware/pcm.c index 25feed5a49..bb4fd588b5 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -303,21 +303,24 @@ bool pcm_switch_sink(enum pcm_sink_ids sink) return true; } - /* This should not be possible but it silences - a false warning that only occurs with with GCC9.5 on bare metal ARM. - */ -#if __GNUC__ == 9 && defined(CPU_ARM) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif + /* + * If PCM_SINK_NUM == 1, GCC 9.5 can infer that cur_sink + * must be nonzero here (because of the above checks) and + * issue a -Warray-bounds warning. This only happens on + * some architectures (ARM), and oddly enough, only when + * cur_sink is an enum type. + * + * Since this situation isn't possible outside of memory + * corruption we can just tell the compiler to assume it + * can't happen. This avoids the warning, and saves a bit + * of code size since none of the code below is reachable + * when there's only one PCM sink. + */ + ASSUME(cur_sink < PCM_SINK_NUM); /* save current sink before switching */ struct pcm_sink* old_sink = sinks[cur_sink]; -#if __GNUC__ == 9 -#pragma GCC diagnostic pop -#endif - /* update sink index */ cur_sink = sink; /* synchronize frequency */ From 6dc731dff2d7656463d5d3074aead837007ae4dd Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 24 Apr 2026 20:06:55 +0900 Subject: [PATCH 16/88] include pcm.h in pcm_mixer.h for pcm_peaks Change-Id: I074fff95b5147a343d23e22e876b33884a173c97 --- firmware/export/pcm_mixer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/export/pcm_mixer.h b/firmware/export/pcm_mixer.h index be0e450d2e..d98a24e54a 100644 --- a/firmware/export/pcm_mixer.h +++ b/firmware/export/pcm_mixer.h @@ -23,6 +23,7 @@ #define PCM_MIXER_H #include +#include "pcm.h" #include "pcm_sink.h" /** Simple config **/ From 2690418551ad4cb11d758c288da49ccb9820ff94 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 24 Apr 2026 12:49:50 +0200 Subject: [PATCH 17/88] plugins: imageviewer: use theme in all submenus Affects submenus "Toggle Slideshow Mode" and "Slideshow Time" that were left un-themed. Also prevents theme from being re-toggled when entering "Display Options" submenu. Change-Id: I3995d5eb12bbc8fb868c179db8043576eb675dbc --- apps/plugins/imageviewer/imageviewer.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/plugins/imageviewer/imageviewer.c b/apps/plugins/imageviewer/imageviewer.c index 01683c8ace..b87a206a33 100644 --- a/apps/plugins/imageviewer/imageviewer.c +++ b/apps/plugins/imageviewer/imageviewer.c @@ -361,12 +361,6 @@ static int show_menu(void) /* return 1 to quit */ } } #endif -#if LCD_DEPTH > 1 - rb->lcd_set_backdrop(NULL); - rb->lcd_set_foreground(LCD_WHITE); - rb->lcd_set_background(LCD_BLACK); -#endif - rb->lcd_clear_display(); return 0; } @@ -776,9 +770,22 @@ static int scroll_bmp(struct image_info *info, bool initial_frame) #ifdef USEGSLIB grey_show(false); /* switch off greyscale overlay */ #endif - if (show_menu() == 1) + FOR_NB_SCREENS(i) + rb->viewportmanager_theme_enable(i, true, NULL); + int ret = show_menu(); + FOR_NB_SCREENS(i) + rb->viewportmanager_theme_undo(i, false); + + if (ret == 1) return PLUGIN_OK; +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); +#endif + rb->lcd_clear_display(); + #ifdef USEGSLIB grey_show(true); /* switch on greyscale overlay */ #else From 80e3c0b065df772f846f381d6812e23a6a0a1097 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 25 Apr 2026 12:36:46 +0100 Subject: [PATCH 18/88] mips: require 8-byte stack alignment The o32 ABI requires at least 8-byte alignment. This fixes the float formatting weirdness seen in quake (FS#13821). Change-Id: I4b587946884d7b35cef420e607c7e127664849e2 --- firmware/export/system.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/firmware/export/system.h b/firmware/export/system.h index e1d999dcd8..fce3c85b39 100644 --- a/firmware/export/system.h +++ b/firmware/export/system.h @@ -266,8 +266,10 @@ static inline void cpu_boost_unlock(void) #endif #endif -/* ARM ABIs generally require 8-byte stack alignment */ -#ifdef CPU_ARM +/* + * ARM and MIPS ABIs generally require 8-byte stack alignment. + */ +#if defined(CPU_ARM) || defined(CPU_MIPS) #define MIN_STACK_ALIGN 8 #endif From 31d8118581d98cb04752fcd343453019d6a8206d Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 25 Apr 2026 15:48:07 +0100 Subject: [PATCH 19/88] plugins: sdl: fix LoadWAVStream failing to load WAV files Reading the WAV length seems to have been accidentally commented out in commit e28d1fe91671. The WAV length is not used here but disabling the read broke WAV header parsing completely. Change-Id: Ia6d0b1a168b2b029bd1cbec9bdc482caf6fa0487 --- apps/plugins/sdl/SDL_mixer/wavestream.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/plugins/sdl/SDL_mixer/wavestream.c b/apps/plugins/sdl/SDL_mixer/wavestream.c index 489863908f..8dbb2a934a 100644 --- a/apps/plugins/sdl/SDL_mixer/wavestream.c +++ b/apps/plugins/sdl/SDL_mixer/wavestream.c @@ -286,7 +286,7 @@ static SDL_RWops *LoadWAVStream (SDL_RWops *src, SDL_AudioSpec *spec, /* WAV magic header */ Uint32 RIFFchunk; -// Uint32 wavelen; + Uint32 wavelen; Uint32 WAVEmagic; /* FMT chunk */ @@ -296,8 +296,12 @@ static SDL_RWops *LoadWAVStream (SDL_RWops *src, SDL_AudioSpec *spec, /* Check the magic header */ RIFFchunk = SDL_ReadLE32(src); -// wavelen = SDL_ReadLE32(src); + wavelen = SDL_ReadLE32(src); WAVEmagic = SDL_ReadLE32(src); + + /* Unused */ + (void)wavelen; + if ( (RIFFchunk != RIFF) || (WAVEmagic != WAVE) ) { Mix_SetError("Unrecognized file type (not WAVE)"); was_error = 1; From 83214cf18c00c69b3cfe9c302444fce5f0458e2e Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 26 Apr 2026 07:33:17 -0400 Subject: [PATCH 20/88] FS#13883 - Latvian translation update (Renalds Belaks) Change-Id: Iaa60196c9316bb90eb228b3812561dd5f81b1034 --- apps/lang/latviesu.lang | 144 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 9 deletions(-) diff --git a/apps/lang/latviesu.lang b/apps/lang/latviesu.lang index e0db4361b6..0d65d484fa 100644 --- a/apps/lang/latviesu.lang +++ b/apps/lang/latviesu.lang @@ -14781,7 +14781,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14796,7 +14796,7 @@ *: "Nospiediet LEFT, lai atceltu." - android,hifietma*,zenvision: "Nospiediet BACK, lai atceltu." + android,hifietma*: "Nospiediet BACK, lai atceltu." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Nospiediet POWER, lai atceltu." ihifi760,ihifi960: "Divreiz pieskarieties RETURN, lai atceltu." ihifi770,ihifi770c,ihifi800: "Nospiediet HOME, lai atceltu." @@ -14811,7 +14811,7 @@ *: "Nospiediet LEFT, lai atceltu." - android,hifietma*,zenvision: "Nospiediet BACK, lai atceltu." + android,hifietma*: "Nospiediet BACK, lai atceltu." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Nospiediet POWER, lai atceltu." ihifi760,ihifi960: "Divreiz pieskarieties RETURN, lai atceltu." ihifi770,ihifi770c,ihifi800: "Nospiediet HOME, lai atceltu." @@ -15306,13 +15306,13 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" - *: "dAY" + *: "dAy" *: "" @@ -15915,16 +15915,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Noklusējuma pārlūks" + *: "" - *: "Noklusējuma pārlūks" + *: "" @@ -16973,3 +16973,129 @@ *: "Ju es bī" + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Vienmēr atcerēties pēdējo mapi" + + + *: "Vienmēr atcerēties pēdējo mapi" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Fails nav atrasts" + + + *: "Fails nav atrasts" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Rādīt failu pārlūkā" + + + *: "Rādīt failu pārlūkā" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Apmainīt kanālus vietām" + + + *: "Apmainīt kanālus vietām" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "IESTATĪT TAIMERI" + + + *: "Iestatīt taimeri" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "DARBOJAS" + + + *: "Darbojas" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "APTURĒTS" + + + *: "Apturēts" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "PAPILDLAIKS" + + + *: "Papildlaiks" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "BEIDZIES" + + + *: "Beidzies" + + From 792a230c00c2872fa25ae941c884e6ae153cc6d9 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 26 Apr 2026 08:04:24 -0400 Subject: [PATCH 21/88] FS#13877 - Use FONT_UI in the Equalizer and scale sliders to match font size The graphical EQ was configured to use SYSFONT, which is limited to ASCII despite the EQ being translateable. Change this to use the current UI font instead. As the UI font can vary in size quite drastically, alter the EQ slider from a fixed 6px height (intended for use with an 8px SYSFONT) to 3/4 of the height of the selected font. Change-Id: I05e7e77be37e9b8cf633b31c12bc4ef02cbaa90a --- apps/menus/eq_menu.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/menus/eq_menu.c b/apps/menus/eq_menu.c index 5ffc78c7c5..831cd06b6f 100644 --- a/apps/menus/eq_menu.c +++ b/apps/menus/eq_menu.c @@ -442,13 +442,10 @@ enum eq_type { HIGH_SHELF }; -/* Size of just the slider/srollbar */ -#define SCROLLBAR_SIZE 6 - /* Draw the UI for a whole EQ band */ static int draw_eq_slider(struct screen * screen, int x, int y, int width, int cutoff, int q, int gain, bool selected, - enum eq_slider_mode mode, int band) + enum eq_slider_mode mode, int band, int scrollbar_size) { char buf[26]; int steps, min_item, max_item; @@ -534,14 +531,14 @@ static int draw_eq_slider(struct screen * screen, int x, int y, screen->putsxy(x1, y1, buf); /* Draw selection box */ - total_height = 3 + h + 1 + SCROLLBAR_SIZE + 3; + total_height = 3 + h + 1 + scrollbar_size + 3; screen->set_drawmode(DRMODE_SOLID); if (selected) { screen->drawrect(x, y, width, total_height); } /* Draw horizontal slider. Reuse scrollbar for this */ - gui_scrollbar_draw(screen, x + 3, y1 + h + 1, width - 6, SCROLLBAR_SIZE, + gui_scrollbar_draw(screen, x + 3, y1 + h + 1, width - 6, scrollbar_size, steps, min_item, max_item, HORIZONTAL); return total_height; @@ -550,7 +547,7 @@ static int draw_eq_slider(struct screen * screen, int x, int y, /* Draw's all the EQ sliders. Returns the total height of the sliders drawn */ static void draw_eq_sliders(struct screen * screen, int x, int y, int nb_eq_sliders, int start_item, - int current_band, enum eq_slider_mode mode) + int current_band, enum eq_slider_mode mode, int scrollbar_size) { int height = y; @@ -568,14 +565,14 @@ static void draw_eq_sliders(struct screen * screen, int x, int y, if (i >= start_item) { height += draw_eq_slider(screen, x, height, screen->lcdwidth - x - 1, cutoff, q, gain, i == current_band, mode, - i); + i, scrollbar_size); /* add a margin */ height++; } } if (nb_eq_sliders != EQ_NUM_BANDS) - gui_scrollbar_draw(screen, 0, y, SCROLLBAR_SIZE - 1, + gui_scrollbar_draw(screen, 0, y, scrollbar_size - 1, screen->lcdheight - y, EQ_NUM_BANDS, start_item, start_item + nb_eq_sliders, VERTICAL); @@ -593,20 +590,23 @@ int eq_menu_graphical(void) int current_band, x, y, step, fast_step, min, max; enum eq_slider_mode mode; int h, height, start_item, nb_eq_sliders[NB_SCREENS]; + int scrollbar_size[NB_SCREENS]; + FOR_NB_SCREENS(i) viewportmanager_theme_enable(i, false, NULL); - FOR_NB_SCREENS(i) { screens[i].set_viewport(NULL); - screens[i].setfont(FONT_SYSFIXED); screens[i].clear_display(); /* Figure out how many sliders can be drawn on the screen */ h = screens[i].getcharheight(); + scrollbar_size[i] = h * 3 / 4; + if (scrollbar_size[i] < 6) + scrollbar_size[i] = 6; /* Total height includes margins (1), text, slider, and line selector (1) */ - height = 3 + h + 1 + SCROLLBAR_SIZE + 3; + height = 3 + h + 1 + scrollbar_size[i] + 3; nb_eq_sliders[i] = screens[i].lcdheight / height; /* Make sure the "Edit Mode" text fits too */ @@ -675,14 +675,14 @@ int eq_menu_graphical(void) } else { start_item = current_band - 1; } - x = SCROLLBAR_SIZE; + x = scrollbar_size[i]; } else { x = 1; start_item = 0; } /* Draw equalizer band details */ draw_eq_sliders(&screens[i], x, y, nb_eq_sliders[i], start_item, - current_band, mode); + current_band, mode, scrollbar_size[i]); screens[i].update(); } @@ -761,7 +761,6 @@ int eq_menu_graphical(void) /* Reset screen settings */ FOR_NB_SCREENS(i) { - screens[i].setfont(FONT_UI); screens[i].clear_display(); screens[i].set_viewport(NULL); viewportmanager_theme_undo(i, false); From 5bbf1c8e5bdf92e3dc071beac7031bfcf508385b Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sun, 26 Apr 2026 13:39:00 +0200 Subject: [PATCH 22/88] tree: fix gui_synclist_scroll_stop called with uninitialized list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression introduced in 7a281ec. Fixes crash with the "remember last folder" setting, when the saved dir has since been removed: exit_to_new_screen in dirbrowse calls gui_synclist_scroll_stop when update_dir returns ≤0 for the number of dir entries. But synclist has not been initialized by update_dir when it returns early with -1 due to either tagtree_load or ft_load failing. Move gui_synclist init further up in update_dir, so that when it returns, the list is guaranteed to be initialized. Change-Id: I62aa742a3d0121d5034440ff134992034e13fd90 --- apps/tree.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/tree.c b/apps/tree.c index 387a070e3a..797e5a3936 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -413,6 +413,9 @@ static int update_dir(void) const bool id3db = false; #endif + /* Ensure that list is initialized before update_dir returns */ + gui_synclist_init(list, &tree_get_filename, &tc, false, 1, NULL); + #ifdef HAVE_TAGCACHE /* Checks for changes */ if (id3db) { @@ -461,8 +464,6 @@ static int update_dir(void) } } - gui_synclist_init(list, &tree_get_filename, &tc, false, 1, NULL); - #ifdef HAVE_TAGCACHE if (id3db) { From 6cf705886d41105682897313e025db7a01c6e124 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sun, 26 Apr 2026 17:12:15 +0200 Subject: [PATCH 23/88] skin: custom scrollbar: fix OBOE Change-Id: I0eb7463c39970c78321d53a51a61f463858dac4e --- apps/gui/bitmap/list-skinned.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gui/bitmap/list-skinned.c b/apps/gui/bitmap/list-skinned.c index bebff821f8..64a7f49ad6 100644 --- a/apps/gui/bitmap/list-skinned.c +++ b/apps/gui/bitmap/list-skinned.c @@ -163,7 +163,7 @@ void skinlist_get_scrollbar(int* nb_item, int* first_shown, int* last_shown) { *nb_item = current_item; *first_shown = 0; - *last_shown = current_nbitems; + *last_shown = current_nbitems - 1; } } From 6928581bf93210e71dd9a0d5053850851a5bec43 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 25 Mar 2026 10:56:14 -0400 Subject: [PATCH 24/88] [bugfix] open_plugin_import fails to import full path WIP settings_load_config() only reads up to 128 characters while openplugin entries could be upwards of 600 instead use the open_plugins plugin to restore entries add import from .cfg file add save to .cfg file better dupe checking Change-Id: Iec2506aad14a3eb89dcc558b0fbc1f014aad98b5 --- apps/open_plugin.c | 64 ++-- apps/open_plugin.h | 3 +- apps/plugin.c | 3 +- apps/plugins/open_plugins.c | 616 +++++++++++++++++++++++++++++++++--- apps/settings.c | 18 +- 5 files changed, 614 insertions(+), 90 deletions(-) diff --git a/apps/open_plugin.c b/apps/open_plugin.c index c892a94233..7950d96101 100644 --- a/apps/open_plugin.c +++ b/apps/open_plugin.c @@ -35,7 +35,7 @@ static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM; -static const int op_entry_sz = sizeof(struct open_plugin_entry_t); +#define OP_ENTRY_SZ (sizeof(struct open_plugin_entry_t)) static const char* strip_rockbox_root(const char *path) { @@ -49,7 +49,7 @@ static inline void op_clear_entry(struct open_plugin_entry_t *entry) { if (entry == NULL) return; - memset(entry, 0, op_entry_sz); + memset(entry, 0, OP_ENTRY_SZ); entry->lang_id = OPEN_PLUGIN_LANG_INVALID; } @@ -78,7 +78,7 @@ static int op_find_entry(int fd, struct open_plugin_entry_t *entry, { logf("OP find_entry *Searching* hash: %x lang_id: %d", hash, lang_id); - while (read(fd, entry, op_entry_sz) == op_entry_sz) + while (read(fd, entry, OP_ENTRY_SZ) == OP_ENTRY_SZ) { if (entry->lang_id == lang_id || entry->hash == hash || (lang_id == OPEN_PLUGIN_LANG_IGNOREALL))/* return first entry found */ @@ -167,9 +167,9 @@ static int op_update_dat(struct open_plugin_entry_t *entry, bool clear) lseek(fd, 0-hlc_sz, SEEK_CUR);/* back to the start of record */ break; } - lseek(fd, op_entry_sz - hlc_sz, SEEK_CUR); /* finish record */ + lseek(fd, OP_ENTRY_SZ - hlc_sz, SEEK_CUR); /* finish record */ } - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); close(fd); #else /* Everyone else make a temp file */ logf("OP update *Copying entries* %s", OPEN_PLUGIN_DAT ".tmp"); @@ -177,18 +177,18 @@ static int op_update_dat(struct open_plugin_entry_t *entry, bool clear) if (fd < 0) return OPEN_PLUGIN_NOT_FOUND; - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); int fd1 = open(OPEN_PLUGIN_DAT, O_RDONLY); if (fd1 >= 0) { /* copy non-duplicate entries back from original */ - while (read(fd1, entry, op_entry_sz) == op_entry_sz) + while (read(fd1, entry, OP_ENTRY_SZ) == OP_ENTRY_SZ) { if (entry->hash != hash && entry->lang_id != lang_id && op_entry_checksum(entry) > 0) { - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); } } close(fd1); @@ -529,41 +529,13 @@ static bool op_entry_read(int fd, int selected_item, off_t data_sz, struct open_ { op_clear_entry(op_entry); return ((selected_item >= 0) && (fd >= 0) && - (lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) && + (lseek(fd, selected_item * OP_ENTRY_SZ, SEEK_SET) >= 0) && (read(fd, op_entry, data_sz) == data_sz) && op_entry_checksum(op_entry) > 0); } -void open_plugin_import(char *strdat) +void open_plugin_import(const char *cfg_file) { - /* Note: Destroys strdat */ - char *vect[5]; - /* expected openplugin strdat: "englishid", "name", "path", "param" */ - - if (split_string(strdat, ',', vect, 5) == 4) - { - /* Space for 5 values so we will fail if excess arguments or too few */ - for(int i = 0; i < 4; i++) - { - vect[i] = skip_whitespace(vect[i]); - int eos = ((int)strlen(vect[i]))-2; - /* Failure if string is not quoted */ - if (vect[i][0] != '"' || eos < 0 || vect[i][eos + 1] != '"') - { - logf("%s[%d] error: quoted string expected\n", __func__, i); - return; - } - vect[i]++; /* skip first " */ - vect[i][eos] = '\0'; /* skip other " */ - } - char *key = vect[1]; - int lang_id = lang_english_to_id(vect[0]); - if (lang_id >= 0) - key = ID2P(lang_id); - /* if key exists it will be overwritten */ - open_plugin_add_path(key, vect[2], vect[3]); - return; - } - logf("%s error: importing entries", __func__); + plugin_load(VIEWERS_DIR"/open_plugins.rock", cfg_file); } void open_plugin_export(int cfg_fd) @@ -584,7 +556,7 @@ void open_plugin_export(int cfg_fd) logf("%s error: opening %s", __func__, OPEN_PLUGIN_DAT); return; /* OPEN_PLUGIN_NOT_FOUND */ } - while (op_entry_read(fd, index++, op_entry_sz, op_entry)) + while (op_entry_read(fd, index++, OP_ENTRY_SZ, op_entry)) { /* don't save the LANG_OPEN_PLUGIN entry -- it is for internal use */ if (op_entry->lang_id == LANG_OPEN_PLUGIN) @@ -596,8 +568,16 @@ void open_plugin_export(int cfg_fd) else lang_name = "[USER]"; /* needs to be an invalid lang string */ - fdprintf(cfg_fd,"%s: \"%s\", \"%s\", \"%s\", \"%s\"\n", - "openplugin", lang_name, op_entry->name, op_entry->path, op_entry->param); + bool dblquote = strchr(op_entry->name, '"') != NULL || + strchr(op_entry->param, '"') != NULL; + const char* fmtstr = "%s: \"%s\", \"%s\", \"%s\", \"%s\"\n"; + + if (dblquote) /* if using double quotes export with single quotes */ + { + fmtstr = "%s: '%s', '%s', '%s', '%s'\n"; + } + fdprintf(cfg_fd, fmtstr, OPEN_PLUGIN_CFGNAME, + lang_name, op_entry->name, op_entry->path, op_entry->param); logf("openplugin[%d]: \"%s\", \"%s\", \"%s\", \"%s\"\n lang id: %d hash: %x, csum: %x\n", index, lang_name, op_entry->name, op_entry->path, op_entry->param, diff --git a/apps/open_plugin.h b/apps/open_plugin.h index 7038f419f3..3e00827baf 100644 --- a/apps/open_plugin.h +++ b/apps/open_plugin.h @@ -35,6 +35,7 @@ #define OPEN_RBPLUGIN_DAT PLUGIN_DIR "/rb_plugins.dat" #define OPEN_PLUGIN_BUFSZ MAX_PATH #define OPEN_PLUGIN_NAMESZ 32 +#define OPEN_PLUGIN_CFGNAME "openplugin" enum { OPEN_PLUGIN_LANG_INVALID = (-1), @@ -84,7 +85,7 @@ int open_plugin_load_entry(const char *key); void open_plugin_browse(const char *key); int open_plugin_run(const char *key); void open_plugin_cache_flush(void); /* flush to disk */ -void open_plugin_import(char *strdat); +void open_plugin_import(const char *cfg_file); void open_plugin_export(int cfg_fd); #endif diff --git a/apps/plugin.c b/apps/plugin.c index c2ab8ad322..d787149de2 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -892,7 +892,8 @@ int plugin_load(const char* plugin, const void* parameter) !strcmp("playing_time.rock", sepch + 1) || !strcmp("main_menu_config.rock", sepch + 1) || !strcmp("text_viewer.rock", sepch + 1) || - !strcmp("disktidy.rock", sepch + 1)); + !strcmp("disktidy.rock", sepch + 1) || + !strcmp("open_plugins.rock", sepch + 1)); if (current_plugin_handle) { diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c index 9da5c1f3a2..a5f8b07dcd 100644 --- a/apps/plugins/open_plugins.c +++ b/apps/plugins/open_plugins.c @@ -31,6 +31,14 @@ * with the parameters previously supplied */ +#if defined(DEBUG) || defined(SIMULATOR) + #define logf(...) rb->debugf(__VA_ARGS__); rb->debugf("\n") +#elif defined(ROCKBOX_HAS_LOGF) + #define logf rb->logf +#else + #define logf(...) do { } while(0) +#endif + #include "plugin.h" #include "lang_enum.h" #include "../open_plugin.h" @@ -46,16 +54,77 @@ #define MENU_ID_MAIN "0" #define MENU_ID_EDIT "1" -static int fd_dat; +#define MAX_ENTRIES 128 + +static int fd_dat = -1; static struct gui_synclist lists; -struct open_plugin_entry_t op_entry; +static struct open_plugin_entry_t op_entry; static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM; static const off_t op_entry_sz = sizeof(struct open_plugin_entry_t); /* we only need the names for the first menu so don't bother reading paths yet */ const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry); +/*static char op_names[MAX_ENTRIES][OPEN_PLUGIN_NAMESZ + 1] = {0};*/ + +/* Forward declarations*/ static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key); +static int import_from_config(const char* cfgfile); +static int export_to_config(const char* cfgfile); + +static bool hash_exists(uint32_t hash, bool remove) +{ + /* keeps track of hash entries + * supply hash = 0, remove = true to erase all entries + * supply hash = 0, remove = false; returns true if empty entries are available + * supply a hash, remove = false; returns false if the hash does not exist & adds hash to the db + * supply a hash, remove = true; returns true and removes the hash from the db + * returns false if hash is unique + */ + static uint32_t seen_hashes[MAX_ENTRIES] = {0}; + int i; + if (hash == 0) + { + if (remove) + { + logf("removing all hashes"); + for(i = 0; i < MAX_ENTRIES; i++) + seen_hashes[i] = 0; + } + return seen_hashes[MAX_ENTRIES - 1] == 0; /* true if empty entries exist */ + } + for(i = 0; i < MAX_ENTRIES; i++) + { + uint32_t current_hash = seen_hashes[i]; + if (current_hash == 0 && !remove) + { + logf("(%d) added new hash: %x", i, hash); + seen_hashes[i] = hash; + break; + } + if (current_hash == hash) + { + if (remove) + { + logf("(%d) remove hash: %x", i, hash); + for (int j = i + 1; j < MAX_ENTRIES; j++, i++) + { + /* move everything above down 1*/ + uint32_t next_hash = seen_hashes[j]; + seen_hashes[i] = next_hash; + if (next_hash == 0) + break; + } + if (i + 1 == MAX_ENTRIES) /* handle last entry */ + { + seen_hashes[i] = 0; + } + } + return true; + } + } + return false; /* hash is unique */ +} static bool _yesno_pop(const char* text) { @@ -87,6 +156,7 @@ static size_t pathbasename(const char *name, const char **nameptr) *nameptr = q; return r - q; } + static int op_entry_checksum(void) { if (op_entry.checksum != open_plugin_csum + @@ -134,7 +204,36 @@ static int op_entry_read_opx(const char *path) return ret; } -static void op_entry_export(int selection) +static void op_entry_config_export(void) +{ + int i = 0; + int len; + char buf[MAX_PATH + 1]; + + do + { + i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", ROCKBOX_DIR, "open_plugins", i, ".cfg"); + } while (i < 100 && rb->file_exists(buf)); + + if( rb->kbd_input(buf, MAX_PATH, NULL ) == 0 ) + { + len = rb->strlen(buf); + if(len > 4 && buf[len] != PATH_SEPCH && + rb->strcasecmp(&((buf)[len-4]), ".cfg") != 0) + { + rb->strcat(buf, ".cfg"); + } + + if (export_to_config(buf) > 0) + return; + } + + rb->splashf( 2*HZ, "Save Failed (%s)", buf ); + +} + +static void op_entry_export_opx(int selection) { int len; int fd = -1; @@ -257,19 +356,43 @@ static void op_entry_set_param(void) rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ); } +static int op_et_exclude_invalid(struct open_plugin_entry_t *op_entry, int item, void *data) +{ + (void)item; + (void)data; + if (op_entry->hash == 0 || op_entry->name[0] == '\0') + { + logf("Exclude entry %d - Bad hash", item); + return 0; + } + if (op_entry->checksum != open_plugin_csum + + (op_entry->lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY)) + { + logf("Exclude entry %d - Bad csum", item); + return 0; + } + if (!rb->file_exists(op_entry->path)) + { + logf("Exclude entry %d - Bad path '%s'", item, op_entry->path); + return 0; + } + logf("%s Include entry %d %s - hash %x", __func__, item, + (op_entry->lang_id >= 0 ? "[Builtin]" : "[User]"), op_entry->hash); + return 1; +} + static int op_et_exclude_hash(struct open_plugin_entry_t *op_entry, int item, void *data) { (void)item; - if (op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; - if (data) { uint32_t *hash = data; + if (op_entry->hash != *hash) - return 1; + return op_et_exclude_invalid(op_entry, item, data); } + logf("Exclude entry %d - hash %x", item, op_entry->hash); return 0; } @@ -279,11 +402,12 @@ static int op_et_exclude_builtin(struct open_plugin_entry_t *op_entry, int item, (void)data; if (op_entry->lang_id >= 0) + { + logf("Exclude entry %d - Builtin", item); return 0; - else if(op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; + } - return 1; + return op_et_exclude_invalid(op_entry, item, data); } static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, void *data) @@ -292,11 +416,11 @@ static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, vo (void)data; if (op_entry->lang_id < 0) + { + logf("Exclude entry %d - User", item); return 0; - else if (op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; - - return 1; + } + return op_et_exclude_invalid(op_entry, item, data); } static int op_entry_transfer(int fd, int fd_tmp, @@ -304,21 +428,82 @@ static int op_entry_transfer(int fd, int fd_tmp, void *data) { int entries = -1; + int skipped = 0; if (fd_tmp >= 0 && fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0) { entries = 0; while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz) { - if (compfn && compfn(&op_entry, entries, data) > 0 && op_entry_checksum() > 0) + if (compfn && compfn(&op_entry, entries + skipped, data) > 0 && op_entry_checksum() > 0) { rb->write(fd_tmp, &op_entry, op_entry_sz); entries++; } + else + skipped++; } + logf("%s %d entries %d skipped", __func__, entries, skipped); + } + else + { + logf("%s Error: fd %d, fd_tmp %d, seekfd: %d", __func__, + fd, fd_tmp, (int) rb->lseek(fd, 0, SEEK_SET)); } return entries + 1; } +#if 0 +static int op_entry_exists(int fd) +{ + logf("%s? %s - %s", __func__, op_entry.name, op_entry.path); + /* returns index of the entry that already exists if not found returns -1 */ + static struct open_plugin_entry_t op_entry_rd; + int entries = 0; + if (fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0) + { + while (rb->read(fd, &op_entry_rd, op_entry_sz) == op_entry_sz) + { + logf("rd: %s - %s", op_entry_rd.name, op_entry_rd.path); + if (rb->memcmp(&op_entry_rd, &op_entry, op_entry_sz) == 0) + { + logf("%s entry exists @ %d", op_entry_rd.name, entries); + return entries; + } + entries++; + } + } + logf("%s is unique in %d entries", op_entry.name, entries); + return -1; +} +#endif + +static bool browse_configs(void) +{ + int entries = 0; + char tmp_buf[MAX_PATH+1]; + + struct browse_context browse = { + .dirfilter = SHOW_CFG, + .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER, + .title = rb->str(LANG_CUSTOM_CFG), + .icon = Icon_Plugin, + .root = ROCKBOX_DIR, + .buf = tmp_buf, + .bufsize = sizeof(tmp_buf), + }; + + if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) + { + logf("import from %s\n", tmp_buf); + if(rb->filetype_get_attr(tmp_buf) == FILE_ATTR_CFG) + { + entries = import_from_config(tmp_buf); + rb->splashf(HZ *2, "Imported %d entries", entries); + } + } + return (entries > 0); +} + static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key) { char buf[MAX_PATH]; @@ -358,8 +543,6 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha if (op_entry.name[0] == '\0' || op_entry.lang_id >= 0) rb->strlcpy(op_entry.name, pos, OPEN_PLUGIN_NAMESZ); - - if ((!parameter || parameter[0] == '\0') && fattr != FILE_ATTR_ROCK && fattr != FILE_ATTR_OPX) { rb->strlcpy(op_entry.param, plugin, OPEN_PLUGIN_BUFSZ); @@ -396,16 +579,28 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha } else if (parameter != op_entry.param) rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ); + } - /* hash on the parameter path if it is a file */ - if (op_entry.lang_id <0 && (key == op_entry.path || key == NULL) && - rb->file_exists(op_entry.param)) + op_entry_set_checksum(); + + if (!use_key) + { + if (op_entry.lang_id <0) { - open_plugin_get_hash(op_entry.param, &newhash); - op_entry.hash = newhash; + open_plugin_get_hash(op_entry.name, &hash); + op_entry.hash = hash; + } + if (hash_exists(op_entry.hash, false) && + _yesno_pop(ID2P(LANG_REALLY_OVERWRITE)) == false) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, key, op_entry.lang_id, op_entry.hash); + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + return op_entry.hash; } } - op_entry_set_checksum(); + rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */ } else if(op_entry_read_opx(plugin) == op_entry_sz) @@ -414,13 +609,23 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha if (fd_tmp < 0) return 0; - if (op_entry.lang_id <0 && rb->file_exists(op_entry.param)) - open_plugin_get_hash(op_entry.param, &hash); - else - open_plugin_get_hash(op_entry.path, &hash); + if (op_entry.lang_id <0) + { + open_plugin_get_hash(op_entry.name, &hash); + } op_entry.hash = hash; op_entry_set_checksum(); + + if (hash_exists(op_entry.hash, false)) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, key, op_entry.lang_id, op_entry.hash); + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + return op_entry.hash; + } + rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */ } else @@ -431,6 +636,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha } } + /*logf("OP add key: '%s' lang id: %d hash: %x", op_entry.name, op_entry.lang_id, op_entry.hash);*/ if (op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_hash, &hash) > 0) { rb->close(fd_tmp); @@ -451,6 +657,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha void op_entry_browse_add(int selection) { + logf("%s", __func__); char* key; op_entry_read(fd_dat, selection, op_entry_sz); if (op_entry_set_path() > 0) @@ -466,7 +673,6 @@ void op_entry_browse_add(int selection) static void op_entry_remove(int selection) { - int entries = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz; int32_t hash = 0; int lang_id = -1; @@ -486,6 +692,7 @@ static void op_entry_remove(int selection) rb->global_settings->start_in_screen = GO_TO_ROOT + 2; /* default */ } } + hash_exists(op_entry.hash, true); rb->memset(&op_entry, 0, op_entry_sz); op_entry.lang_id = lang_id; op_entry.hash = hash; @@ -685,9 +892,9 @@ static int context_menu_cb(int action, /*Run, Edit, Remove, Export, Blank, Import, Add, Back*/ switch(selection) { - case 0:case 1:case 2:case 3:case 5: + case 0:case 1:case 2:case 3:case 4:case 5:case 7: return ACTION_STD_OK; - case 4: /*blank*/ + case 6: /*blank*/ break; default: return ACTION_STD_CANCEL; @@ -709,6 +916,9 @@ static void edit_menu(int selection) if (!op_entry_read(fd_dat, selection, op_entry_sz)) return; + if (op_entry.hash != 0) /* remove old hash */ + hash_exists(op_entry.hash, true); + uint32_t crc = rb->crc_32(&op_entry, op_entry_sz, 0xffffffff); synclist_set(MENU_ID_EDIT, 2, 8, 2); @@ -762,6 +972,8 @@ static void edit_menu(int selection) op_entry_add_path(NULL, op_entry.path, param, false); fd_dat = rb->open(OPEN_PLUGIN_DAT, O_RDWR, 0666); } + else + hash_exists(op_entry.hash, false); } static int context_menu(int selection) @@ -771,9 +983,11 @@ static int context_menu(int selection) { MENUITEM_STRINGLIST(menu, op_entry.name, context_menu_cb, ID2P(LANG_RUN), ID2P(LANG_EDIT), ID2P(LANG_REMOVE), ID2P(LANG_EXPORT), - ID2P(VOICE_BLANK), ID2P(LANG_ADD), ID2P(LANG_BACK)); + ID2P(LANG_SAVE_SETTINGS), ID2P(LANG_CUSTOM_CFG), ID2P(VOICE_BLANK), + ID2P(LANG_ADD), ID2P(LANG_BACK)); selected_item = rb->do_menu(&menu, 0, NULL, false); + logf("%s %d", __func__, selected_item); switch (selected_item) { case 0: /*run*/ @@ -785,11 +999,20 @@ static int context_menu(int selection) op_entry_remove(selection); break; case 3: /*export*/ - op_entry_export(selection); + op_entry_export_opx(selection); break; - case 4: /*blank*/ + case 4: /* export to cfg */ + op_entry_config_export(); + case 5: /*import from cfg*/ + if (browse_configs()) + { + rb->plugin_open(rb->plugin_get_current_filename(), "\0"); + return OP_PLUGIN_RESTART; + } break; - case 5: /*add*/ + case 6: /*blank*/ + break; + case 7: /*add*/ op_entry_browse_add(-1); rb->plugin_open(rb->plugin_get_current_filename(), "\0"); return OP_PLUGIN_RESTART; @@ -802,6 +1025,296 @@ static int context_menu(int selection) return PLUGIN_ERROR; } +/* Read up to buffer_size chars from a quoted string + * within fd into buffer and return number of bytes read. + * A string starts with a quote character (single or double quote) + * and ends with a matching closing quote. Neither opening or closing quotes + * are stored in buffer. Too small buf or no opening and closing quote is an error. + * If an error occurs, -1 is returned (and buffer is cleared). + * If buffer too small file will still be advanced to the closing quote/LF/EOF + */ +static int read_quoted_string(int fd, char* buffer, int buffer_size) +{ + int pos = 0; + char ch; + char quote = '\0'; + /*logf("%s fd: %d bufsz: %d", __func__, fd, buffer_size);*/ + while (rb->read(fd, &ch, 1) == 1) + { + if (ch == '\n') /*LF marks end of line*/ + { + rb->lseek(fd, -1, SEEK_CUR);/*back up cursor to LF so calling fn sees it*/ + break; /* fail */ + } + if (quote == '\0' && + (ch == '\'' || ch == '"')) /*handle single or double quotes*/ + { + quote = ch; + } + else if (quote != '\0') + { + if (ch == quote) + { + if (pos < buffer_size) + { + buffer[pos] = '\0'; /*end quote*/ + return pos + 1; + } + break; /*fail*/ + } + if (pos < buffer_size) + { + buffer[pos] = ch; /*inside quote*/ + pos++; + } + } + } + + /*fail*/ + /*logf("Error %s", __func__);*/ + buffer[0] = '\0'; + return -1; +} + +static int lang_english_to_id(const char *english) +{ + int i; + unsigned char *ptr; + size_t ptrlen, len = rb->strlen(english); + for (i = 0; i < LANG_LAST_INDEX_IN_ARRAY; i++) { + ptr = rb->language_strings[i]; + ptrlen = rb->strlen((char *)ptr); + if ((ptrlen == len) && rb->memcmp(ptr, english, ptrlen) == 0) + return i; + } + return -1; +} + +static bool op_entry_config_import(int cfg_fd, int fd_tmp) +{ + /* NOTE: assumes cfg_fd is valid */ + /*"key", "name", "path", "param"*/ + /*"Start Screen", "logo.rock", "/.rockbox/rocks/demos/logo.rock", ""*/ + /*"[USER]", "text_viewer.rock", "/.rockbox/rocks/viewers/text_viewer.rock", "/text.txt"*/ + rb->memset(&op_entry, 0, op_entry_sz); + + static char errmsg[MAX_PATH]; + static char keybuf[MAX_PATH]; + int32_t lang_id; + uint32_t hash; + + if (read_quoted_string(cfg_fd, keybuf, sizeof(keybuf)) < 0) + { + logf("%s error: importing key entry @ %d", __func__, 0); + rb->snprintf(errmsg, sizeof(errmsg), "importing key entry @ %d", 1); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + + lang_id = lang_english_to_id(keybuf); + if (lang_id < 0) + { + int rd = read_quoted_string(cfg_fd, keybuf, sizeof(keybuf)); /* grab name field */ + if(rd < 0) + { + logf("%s error: importing key entry @ %d", __func__, 1); + rb->snprintf(errmsg, sizeof(errmsg), "importing key entry @ %d", 1); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + rb->lseek(cfg_fd, -(rd+2), SEEK_CUR); /* restore position to read name again */ + } + + int i, bufsz; + char *field[3] = {op_entry.name, op_entry.path, op_entry.param}; + for (i = 0, bufsz = OPEN_PLUGIN_NAMESZ; i < (int)ARRAYLEN(field); i++) + { + if (read_quoted_string(cfg_fd, field[i], bufsz) < 0) + { + logf("%s error: importing entry @ %d", __func__, i); + logf("OP import key: '%s' name: '%s' '%s' '%s'", keybuf, + op_entry.name, op_entry.path, op_entry.param); + rb->memset(&op_entry, 0, op_entry_sz); + rb->snprintf(errmsg, sizeof(errmsg), "importing entry %s @ %d", keybuf, i); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + bufsz = OPEN_PLUGIN_BUFSZ; + } + + if (!rb->file_exists(op_entry.path)) + { + logf("%s error: '%s' '%s' does not exist", __func__, keybuf, op_entry.path); + rb->splashf(HZ*2, ID2P(LANG_PLUGIN_CANT_OPEN), op_entry.path); + return false; /* fail */ + } + + if (lang_id <0) + { + open_plugin_get_hash(op_entry.name, &hash); + + } + else + { + open_plugin_get_hash(keybuf, &hash); + } + + op_entry.hash = hash; + op_entry.lang_id = lang_id; + + /*logf("OP import key: '%s' name: '%s' '%s' '%s'", keybuf, + op_entry.name, op_entry.path, op_entry.param);*/ + + op_entry_set_checksum(); + + if (fd_tmp >= 0 && fd_dat >= 0) + { + if (hash_exists(op_entry.hash, false)) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, keybuf, lang_id, hash); + return false; + } + + logf("writing to tmp: %s - %s", op_entry.name, op_entry.path); + + if (rb->write(fd_tmp, &op_entry, op_entry_sz) != op_entry_sz)/* add new entry */ + return false; + } + else + { + logf("%s error: bad fd dat: %d tmp: %d", __func__, fd_dat, fd_tmp); + return false; + } + + return true; +} + +static int import_from_config(const char* cfgfile) +{ + logf("%s() %s\r\n", __func__, cfgfile); + int fd; + char ch; + int entries = 0; + static char line[sizeof("openplugin:")]; + + fd = rb->open_utf8(cfgfile, O_RDONLY); + if (fd < 0) + return -1; + + int fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd_tmp < 0) + { + logf("%s error: can not open '%s'", __func__, OPEN_PLUGIN_DAT ".tmp"); + return -1; + } + + while ((rb->read(fd, line, sizeof line) - 1) == sizeof(line) - 1) + { + if (rb->strncasecmp(line, "openplugin:", sizeof("openplugin:") -1) == 0) + { + if (op_entry_config_import(fd, fd_tmp)) + entries++; + } + + while (rb->read(fd, &ch, 1) == 1) /* continue reading till EOL */ + { + if (ch == '\n') + break; + } + } /* while(...) */ + + rb->close(fd); +#if 1 + if (entries > 0 && op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_user, NULL) > 0 && + op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, NULL) > 0) +#else + if (entries > 0 && op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_invalid, 0) >= 0) +#endif + { + logf("%s imported %d entries", __func__, entries); + rb->close(fd_tmp); + rb->close(fd_dat); + fd_dat = -1; + rb->remove(OPEN_PLUGIN_DAT); + rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT); + } + else + { + logf("%s error: can not transfer entries", __func__); + if (entries > 0) + { + logf("%s error: can not transfer entries to '%s'", __func__, OPEN_PLUGIN_DAT ".tmp"); + entries = -1; + } + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + } + + return entries; +} + +static int export_to_config(const char* cfgfile) +{ + logf("%s() %s\r\n", __func__, cfgfile); + int cfg_fd; + char *lang_name; + int fd; + int index = 0; + + cfg_fd = rb->open_utf8(cfgfile, O_WRONLY | O_CREAT | O_TRUNC); + if (cfg_fd < 0) + return index; + + fd = rb->open_utf8(OPEN_PLUGIN_DAT, O_RDONLY); + + if (fd < 0) + { + logf("%s error: opening %s", __func__, OPEN_PLUGIN_DAT); + rb->close(cfg_fd); + rb->remove(cfgfile); + return index; /* OPEN_PLUGIN_NOT_FOUND */ + } + while (op_entry_read(fd, index, op_entry_sz)) + { + index++; + /* don't save the LANG_OPEN_PLUGIN entry -- it is for internal use */ + if (op_entry.lang_id == LANG_OPEN_PLUGIN) + { + continue; + } + if (op_entry.lang_id >=0) + lang_name = rb->str(op_entry.lang_id); + else + lang_name = "[USER]"; /* needs to be an invalid lang string */ + bool dblquote = rb->strchr(op_entry.name, '"') != NULL || + rb->strchr(op_entry.param, '"') != NULL; + + const char* fmtstr = "%s: \"%s\", \"%s\", \"%s\", \"%s\"\n"; + + if (dblquote) /* if using double quotes export with single quotes */ + { + fmtstr = "%s: '%s', '%s', '%s', '%s'\n"; + } + rb->fdprintf(cfg_fd,fmtstr, OPEN_PLUGIN_CFGNAME, + lang_name, op_entry.name, op_entry.path, op_entry.param); + + logf("openplugin[%d]: \"%s\", \"%s\", \"%s\", \"%s\"\n lang id: %d hash: %x, csum: %x\n", + index, lang_name, op_entry.name, op_entry.path, op_entry.param, + op_entry.lang_id, op_entry.hash, op_entry.checksum); + } + rb->close(fd); + rb->close(cfg_fd); + logf("%s exported %d entries", __func__, index); + if (index == 0) + { + /* Nothing to export */ + rb->remove(cfgfile); + } + + return index; +} + enum plugin_status plugin_start(const void* parameter) { int ret = PLUGIN_OK; @@ -817,11 +1330,20 @@ enum plugin_status plugin_start(const void* parameter) const int creat_flags = O_RDWR | O_CREAT; reopen_datfile: + fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666); + + hash_exists(0, true); + while (rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz) + { + hash_exists(op_entry.hash, false); + } + if (!fd_dat) exit = true; items = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz; + if (parameter) { path = (char*)parameter; @@ -833,13 +1355,18 @@ reopen_datfile: parameter = NULL; op_entry_browse_add(-1); rb->close(fd_dat); + fd_dat = -1; goto reopen_datfile; } - } + if(rb->filetype_get_attr(path) == FILE_ATTR_CFG) + { + int ret = import_from_config(path); + rb->close(fd_dat); + if (ret >= 0) + return PLUGIN_OK; + return PLUGIN_ERROR; + } - if (parameter) - { - path = (char*)parameter; res = op_entry_read_opx(path); if (res >= 0) { @@ -910,6 +1437,7 @@ reopen_datfile: if (op_entry_add_path(rb->str(LANG_ADD), cur_filename, "-add", true)) { rb->close(fd_dat); + fd_dat = -1; parameter = NULL; goto reopen_datfile; } @@ -917,8 +1445,6 @@ reopen_datfile: return PLUGIN_ERROR; } - - if (!exit) { synclist_set(MENU_ID_MAIN, selection, items, 1); @@ -947,7 +1473,7 @@ reopen_datfile: rb->gui_synclist_draw(&lists); break; } - /* Inentional fallthrough */ + /* fallthrough */ case ACTION_STD_OK: if (op_entry_read(fd_dat, selection, op_entry_sz)) { @@ -962,6 +1488,14 @@ reopen_datfile: exit = true; break; } + default: + if (rb->default_event_handler(action) == SYS_USB_CONNECTED) + { + op_entry_remove_empty(); + rb->close(fd_dat); + return PLUGIN_USB_CONNECTED; + } + break; } } op_entry_remove_empty(); diff --git a/apps/settings.c b/apps/settings.c index 7ed4257084..a87844ac24 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -405,7 +405,7 @@ bool settings_load_config(const char* file, bool apply) int fd; char line[128]; bool theme_changed = false; - + bool import_open_plugins = false; fd = open_utf8(file, O_RDONLY); if (fd < 0) return false; @@ -418,17 +418,25 @@ bool settings_load_config(const char* file, bool apply) if (!string_to_cfg(name, value, &theme_changed)) { -#ifndef __PCTOOL__ /* if we are here then name was not a valid setting */ - if (!strcmp(name, "openplugin")) + if (strcmp(name, OPEN_PLUGIN_CFGNAME) == 0) { - open_plugin_import(value); + import_open_plugins = true; + char ch; + while (read(fd, &ch, 1) == 1 && ch != '\n'){}; } -#endif } } /* while(...) */ close(fd); + +#ifndef __PCTOOL__ + if (import_open_plugins) + open_plugin_import(file); +#else + (void) import_open_plugins; +#endif + if (apply) { settings_save(); From 1068433d5b3ed4a7df75b266e547f6956c75c16e Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 29 Apr 2026 01:53:02 -0400 Subject: [PATCH 25/88] [Bugfix] FS#13878 FS#13862 lua alpha bmp overflow and image flip example FS#13878 - 292x216 images cause panic alpha channel causes overflow FS#13862 - In rlimg example "flip image" cause error missing local variable save random and rainbow images fix ball bounce direction Change-Id: I717eb029f30bf63d2eef0b7997eb04036ffeda15 --- apps/plugins/lua/rocklib_img.c | 35 ++++++++++++++++++++++-------- apps/plugins/lua_scripts/rlimg.lua | 17 ++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/plugins/lua/rocklib_img.c b/apps/plugins/lua/rocklib_img.c index c58a5b6681..df5e028283 100644 --- a/apps/plugins/lua/rocklib_img.c +++ b/apps/plugins/lua/rocklib_img.c @@ -349,7 +349,7 @@ static struct rocklua_image* rli_checktype(lua_State *L, int arg) } /* rli_checktype */ static struct rocklua_image * alloc_rlimage(lua_State *L, bool alloc_data, - int width, int height) + int width, int height, size_t *allocd) { /* rliimage is pushed on the stack it is up to you to pop it */ struct rocklua_image *img; @@ -365,10 +365,24 @@ static struct rocklua_image * alloc_rlimage(lua_State *L, bool alloc_data, n_elems = (size_t)(w_native * h_native); - if(alloc_data) /* if this a new image we need space for image data */ - sz_data = n_elems * sizeof(fb_data); + if(alloc_data) + {/* if this a new image we need space for image data */ + /* need even rows (see lcd-16bit-common.c for details) */ + sz_data = BM_SIZE(width,height,FORMAT_NATIVE,false); +#if LCD_DEPTH > 24 /* account for possible 4bit alpha per pixel */ + sz_data += ALIGN_UP(width, 2) * height / 2; +#endif +#if (LCD_DEPTH > 1) && defined(HAVE_BMP_SCALING) + if (width > BM_MAX_WIDTH) + sz_data += width*4; +#endif + sz_data = MAX(n_elems * sizeof(fb_data), sz_data); + } + if (allocd) + *allocd = sz_data; /* newuserdata pushes the userdata onto the stack */ + /*DEBUGF("rli alloc %d\n", sz_header + sz_data);*/ img = (struct rocklua_image *) lua_newuserdata(L, sz_header + sz_data); luaL_getmetatable(L, ROCKLUA_IMAGE); @@ -386,15 +400,15 @@ static struct rocklua_image * alloc_rlimage(lua_State *L, bool alloc_data, static inline void rli_wrap(lua_State *L, fb_data *src, int width, int height) { /* rliimage is pushed on the stack it is up to you to pop it */ - struct rocklua_image *a = alloc_rlimage(L, false, width, height); + struct rocklua_image *a = alloc_rlimage(L, false, width, height, NULL); a->data = src; } /* rli_wrap */ -static inline fb_data* rli_alloc(lua_State *L, int width, int height) +static inline fb_data* rli_alloc(lua_State *L, int width, int height, size_t *allocd) { /* rliimage is pushed on the stack it is up to you to pop it */ - struct rocklua_image *a = alloc_rlimage(L, true, width, height); + struct rocklua_image *a = alloc_rlimage(L, true, width, height, allocd); a->data = &a->dummy[0][0]; /* ref to beginning of alloc'd img data */ @@ -995,7 +1009,7 @@ RLI_LUA rli_new(lua_State *L) luaL_argcheck(L, width > 0 && height > 0, (width <= 0) ? 1 : 2, ERR_IDX_RANGE); - rli_alloc(L, width, height); + rli_alloc(L, width, height, NULL); return 1; } @@ -1904,10 +1918,13 @@ RB_WRAP(read_bmp_file) int result = rb->read_bmp_file(filename, &bm, 0, format | FORMAT_RETURN_SIZE, NULL); + /*DEBUGF("read_bmp wants %d %d\n", result, BM_SIZE(bm.width, bm.height, format, false));*/ + if(result > 0) { - bm.data = (unsigned char*) rli_alloc(L, bm.width, bm.height); - if(rb->read_bmp_file(filename, &bm, result, format, NULL) < 0) + size_t sz_data; + bm.data = (unsigned char*) rli_alloc(L, bm.width, bm.height, &sz_data); + if(rb->read_bmp_file(filename, &bm, sz_data, format, NULL) < 0) { /* Error occured, drop newly allocated image from stack */ lua_pop(L, 1); diff --git a/apps/plugins/lua_scripts/rlimg.lua b/apps/plugins/lua_scripts/rlimg.lua index b3fa72426c..7299f4e207 100755 --- a/apps/plugins/lua_scripts/rlimg.lua +++ b/apps/plugins/lua_scripts/rlimg.lua @@ -274,11 +274,11 @@ function bounce_image(img) if IS_COLOR_TARGET then if bit.band(loops, 128) == 128 then - _lcd:copy(imgn, x, y, 1, 1, fx, fy, false, _blit.BOR) + _lcd:copy(imgn, x, y, 1, 1, -fx, -fy, false, _blit.BOR) _lcd:copy(screen_img, x, y, x, y, imgn:width(), imgn:height(), false, _blit.BDEQC, imgn:get(1,1)) else - _lcd:copy(imgn, x, y, 1, 1, fx, fy, false, _blit.BSNEC, imgn:get(1,1)) + _lcd:copy(imgn, x, y, 1, 1, -fx, -fy, false, _blit.BSNEC, imgn:get(1,1)) end else local blitop @@ -289,7 +289,7 @@ function bounce_image(img) blitop = _blit.BXOR end - _lcd:copy(imgn, x, y, 1, 1, fx, fy, false, blitop, WHITE) + _lcd:copy(imgn, x, y, 1, 1, -fx, -fy, false, blitop, WHITE) end if hold < 1 then @@ -674,6 +674,7 @@ end -- rotate_image function flip_image(img) local blitop = _blit.BOR + local i = 1 local d = 0 local x, y, w, h @@ -689,7 +690,7 @@ function flip_image(img) --[[--Profiling code local timer = _timer.start()]] - while d >= 0 do + while d >= 0 and img do -- copy our flipped image onto the background if d == 0 then _lcd:copy(img, x, y, 1, 1, w, h, false, blitop) @@ -757,7 +758,7 @@ end -- draw_x function random_img(img) local min = _clr.set(0, 0, 0, 0) local max = _clr.set(-1, 255, 255, 255) - math.randomseed(rb.current_tick()) + --math.randomseed(rb.current_tick()) for x = 1, img:width() do for y = 1, img:height() do img:set(x, y, math.random(min, max)) @@ -896,9 +897,13 @@ function main_menu() [10] = long_text, [11] = function(RAINB) rainbow_img(_lcd()); _lcd:update(); rb.sleep(rb.HZ) + if rb.file_exists == nil or not rb.file_exists("/rainbow.bmp") then + _img_save(_LCD, "/rainbow.bmp") + end end, [12] = function(RANDM) random_img(_lcd()); _lcd:update(); rb.sleep(rb.HZ) + _img_save(_LCD, "/random.bmp") end, [13] = function(CLEAR) _lcd:clear(BLACK); rock_lua() end, [14] = function(SAVEI) _LCD:invert(); _img_save(_LCD, "/rocklua.bmp") end, @@ -927,6 +932,8 @@ _timer("main") -- keep track of how long the program ran -- Clear the screen _lcd:clear(BLACK) +math.randomseed(rb.current_tick()) + if LCD_DEPTH > 1 then --draw a gradient using available colors if IS_COLOR_TARGET == true then From 121c65b32a01ad1f135bb0baf0d491f9c7a34fbe Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 29 Apr 2026 10:30:54 -0400 Subject: [PATCH 26/88] [Bugfix] FS#13857 - Keylock with USB bug (Fiio M3K) 1. Lock buttons 2. Lock indicator turns on 3. Plug USB 4. Lock indicator still turned on 5. Unplug USB 6. Lock indicator turns off but buttons still locked and if you press lock button you see "Buttons locked" splash pretty sure this is an issue with the touchpad on this device probably applies to other touchscreen targets too Change-Id: Ia0afee7d737f3a5a2755f53d176bd53dd57d87c5 --- apps/action.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/action.c b/apps/action.c index aa21a61222..bd52e8177a 100644 --- a/apps/action.c +++ b/apps/action.c @@ -1440,6 +1440,9 @@ bool is_keys_locked(void) void set_selective_softlock_actions(bool selective, unsigned int mask) { action_last.keys_locked = false; +#if defined(HAVE_TOUCHPAD) || defined(HAVE_TOUCHSCREEN) + button_enable_touch(true); +#endif if (selective) { action_last.softlock_mask = mask | SEL_ACTION_ENABLED; From 2d419b4b93e05da0baeef405f7cf79d8184e506f Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 29 Apr 2026 11:24:34 -0400 Subject: [PATCH 27/88] [Feature] FS#13850 - Add SELECT button support in calculator on FiiO M3K baremetal port add select as a second way to enter Change-Id: I0e3ab76fee8e6accd4425def3da48fb12a6142e8 --- apps/plugins/calculator.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/plugins/calculator.c b/apps/plugins/calculator.c index 5e69e1f12a..27bb4bb438 100644 --- a/apps/plugins/calculator.c +++ b/apps/plugins/calculator.c @@ -519,6 +519,7 @@ F3: equal to "=" #define CALCULATOR_DOWN BUTTON_DOWN #define CALCULATOR_QUIT BUTTON_POWER #define CALCULATOR_INPUT BUTTON_PLAY +#define CALCULATOR_INPUT2 (BUTTON_SELECT|BUTTON_REL) #define CALCULATOR_CALC BUTTON_MENU #define CALCULATOR_CLEAR BUTTON_BACK @@ -1938,6 +1939,9 @@ Handle buttons on basic screen ----------------------------------------------------------------------- */ static void basicButtonsProcess(void){ switch (btn) { +#ifdef CALCULATOR_INPUT2 + case CALCULATOR_INPUT2: /*fallthrough*/ +#endif case CALCULATOR_INPUT: if (calStatus == cal_error && (CAL_BUTTON != btn_C) ) break; flashButton(); @@ -2041,6 +2045,9 @@ Handle buttons on scientific screen ----------------------------------------------------------------------- */ static void sciButtonsProcess(void){ switch (btn) { +#ifdef CALCULATOR_INPUT2 + case CALCULATOR_INPUT2: /*fallthrough*/ +#endif case CALCULATOR_INPUT: if (calStatus == cal_error && (CAL_BUTTON != sci_sci) ) break; flashButton(); @@ -2133,6 +2140,9 @@ static int handleButton(int button){ break; /* no unconditional break; here! */ #endif +#ifdef CALCULATOR_INPUT2 /* bypass pre button */ + case CALCULATOR_INPUT2: /*fallthrough*/ +#endif #ifdef CALCULATOR_OPERATORS case CALCULATOR_OPERATORS: #endif From 7ab1a81806e831be134da480b4c5b178167380de Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Tue, 28 Apr 2026 05:36:12 +0200 Subject: [PATCH 28/88] simple_viewer: use UI viewport and SBS title Also adjust scrollbar margins and height so it matches the look of normal lists, and hide scrollbars when set to SCROLLBAR_OFF. Change-Id: I27f6de7b16cf5ec72e12c7d6377a8772d84947ac --- apps/plugin.c | 4 ++ apps/plugin.h | 11 +++ apps/plugins/lib/simple_viewer.c | 115 +++++++++++++++++++++---------- apps/plugins/properties.c | 16 ++--- apps/screens.c | 7 +- 5 files changed, 100 insertions(+), 53 deletions(-) diff --git a/apps/plugin.c b/apps/plugin.c index d787149de2..57f0cb3d97 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -872,6 +872,9 @@ static const struct plugin_api rockbox_api = { /* new stuff at the end, sort into place next time the API gets incompatible */ panicf, + gui_synclist_scroll_stop, + add_event_ex, + remove_event_ex, }; static int plugin_buffer_handle; @@ -892,6 +895,7 @@ int plugin_load(const char* plugin, const void* parameter) !strcmp("playing_time.rock", sepch + 1) || !strcmp("main_menu_config.rock", sepch + 1) || !strcmp("text_viewer.rock", sepch + 1) || + !strcmp("view_text.rock", sepch + 1) || !strcmp("disktidy.rock", sepch + 1) || !strcmp("open_plugins.rock", sepch + 1)); diff --git a/apps/plugin.h b/apps/plugin.h index f6c3434692..6abab07cd0 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -1025,6 +1025,17 @@ struct plugin_api { /* new stuff at the end, sort into place next time the API gets incompatible */ void (*panicf)(const char *msg, ...); + void (*gui_synclist_scroll_stop)(struct gui_synclist *lists); + bool (*add_event_ex)(unsigned short id, bool oneshot, + void (*handler)(unsigned short id, + void *event_data, + void *user_data), + void *user_data); + void (*remove_event_ex)(unsigned short id, + void (*handler)(unsigned short id, + void *event_data, + void *user_data), + void *user_data); }; /* plugin header */ diff --git a/apps/plugins/lib/simple_viewer.c b/apps/plugins/lib/simple_viewer.c index e71efce753..bffea2080a 100644 --- a/apps/plugins/lib/simple_viewer.c +++ b/apps/plugins/lib/simple_viewer.c @@ -29,7 +29,9 @@ struct view_info { struct font* pf; struct viewport scrollbar_vp; /* viewport for scrollbar */ + struct viewport title_vp; struct viewport vp; + bool sbs_has_title; /* SBS comes with custom title area */ const char *title; const char *text; /* displayed text */ int display_lines; /* number of lines can be displayed */ @@ -93,17 +95,19 @@ static void calc_line_count(struct view_info *info) { ptr = get_next_line(ptr, info); i++; - if (!scrollbar && i > info->display_lines) + if (!scrollbar && i > info->display_lines && + rb->global_settings->scrollbar != SCROLLBAR_OFF) { ptr = info->text; i = 0; info->scrollbar_vp = info->vp; - info->scrollbar_vp.width = rb->global_settings->scrollbar_width; + info->scrollbar_vp.width = rb->global_settings->scrollbar_width + 1; + info->scrollbar_vp.height = info->display_lines * info->pf->height; info->vp.width -= info->scrollbar_vp.width; - if (rb->global_settings->scrollbar != SCROLLBAR_RIGHT) - info->vp.x = info->scrollbar_vp.width; + if (rb->global_settings->scrollbar == SCROLLBAR_RIGHT) + info->scrollbar_vp.x = info->vp.x + info->vp.width; else - info->scrollbar_vp.x = info->vp.width; + info->vp.x += info->scrollbar_vp.width; scrollbar = true; } } @@ -137,8 +141,14 @@ static void calc_first_line(struct view_info *info, int line) static int init_view(struct view_info *info, const char *title, const char *text) { - rb->viewport_set_defaults(&info->vp, SCREEN_MAIN); - info->pf = rb->font_get(rb->screens[SCREEN_MAIN]->getuifont()); + struct screen* display = rb->screens[SCREEN_MAIN]; + int ui_font = display->getuifont(); + + display->set_viewport(&info->vp); + display->clear_viewport(); + + info->pf = rb->font_get(ui_font); + info->vp.font = ui_font; info->display_lines = info->vp.height / info->pf->height; info->title = title; @@ -147,16 +157,21 @@ static int init_view(struct view_info *info, info->line = 0; info->start = 0; - /* no title for small screens. */ - if (info->display_lines < 4) + if (!info->sbs_has_title) { - info->title = NULL; - } - else - { - info->display_lines--; - info->vp.y += info->pf->height; - info->vp.height -= info->pf->height; + /* no title for small screens. */ + if (info->display_lines < 4) + info->title = NULL; + else + { + info->display_lines--; + + info->title_vp = info->vp; + info->title_vp.height = info->pf->height; + + info->vp.height -= info->title_vp.height; + info->vp.y += info->title_vp.height; + } } calc_line_count(info); @@ -171,20 +186,19 @@ static void draw_text(struct view_info *info) int max_show, line; struct screen* display = rb->screens[SCREEN_MAIN]; - /* clear screen */ - display->clear_display(); - /* display title. */ - if(info->title) + if (info->title && !info->sbs_has_title) { - display->set_viewport(NULL); + display->set_viewport(&info->title_vp); display->puts(0, 0, info->title); } + display->set_viewport(&info->vp); + display->clear_viewport(); + max_show = MIN(info->line_count - info->line, info->display_lines); text = info->text + info->start; - display->set_viewport(&info->vp); for (line = 0; line < max_show; line++) { int len; @@ -197,15 +211,17 @@ static void draw_text(struct view_info *info) display->puts(0, line, output); text = ptr; } - if (info->line_count > info->display_lines) + if (info->line_count > info->display_lines && + rb->global_settings->scrollbar != SCROLLBAR_OFF) { display->set_viewport(&info->scrollbar_vp); - rb->gui_scrollbar_draw(display, (info->scrollbar_vp.width? 0: 1), 0, - info->scrollbar_vp.width - 1, info->scrollbar_vp.height, - info->line_count, info->line, info->line + max_show, - VERTICAL); - } + int margin = info->scrollbar_vp.width > 2 ? 2 : 0; + int x = rb->global_settings->scrollbar == SCROLLBAR_RIGHT ? margin : 0; + rb->gui_scrollbar_draw(display, x, 0, + info->scrollbar_vp.width - margin, info->scrollbar_vp.height, + info->line_count, info->line, info->line + max_show, VERTICAL); + } display->set_viewport(NULL); display->update(); } @@ -248,6 +264,21 @@ static void scroll_to_bottom(struct view_info *info) draw_text(info); } +static void ui_update_cb(unsigned short id, void* param, void* user_data) +{ + (void)id; + (void)param; + struct view_info *info = (struct view_info *) user_data; + draw_text(info); +} + +static void cleanup(void *parameter) +{ + struct view_info *info = (struct view_info *) parameter; + rb->remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, ui_update_cb, info); + rb->viewportmanager_theme_undo(SCREEN_MAIN, false); +} + int view_text(const char *title, const char *text) { struct view_info info; @@ -256,13 +287,24 @@ int view_text(const char *title, const char *text) }; int button; + info.sbs_has_title = rb->sb_set_persistent_title(title, Icon_NOICON, SCREEN_MAIN); + rb->viewportmanager_theme_enable(SCREEN_MAIN, true, &info.vp); init_view(&info, title, text); - draw_text(&info); + + /* handle themes that draw over the UI viewport */ + rb->add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, ui_update_cb, &info); + + /* skin engine needs to render title and redraw screen */ + if (info.sbs_has_title) + rb->send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); + else + draw_text(&info); /* wait for keypress */ while(1) { - button = pluginlib_getaction(TIMEOUT_BLOCK, view_contexts, + /* don't block, so the skin engine can redraw */ + button = pluginlib_getaction(HZ/4, view_contexts, ARRAYLEN(view_contexts)); switch (button) { @@ -270,7 +312,7 @@ int view_text(const char *title, const char *text) #if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ || (CONFIG_KEYPAD == IPOD_3G_PAD) \ || (CONFIG_KEYPAD == IPOD_4G_PAD) - return PLUGIN_OK; + goto out; #endif case PLA_UP_REPEAT: #ifdef HAVE_SCROLLWHEEL @@ -291,7 +333,7 @@ int view_text(const char *title, const char *text) #if (CONFIG_KEYPAD == IPOD_1G2G_PAD) \ || (CONFIG_KEYPAD == IPOD_3G_PAD) \ || (CONFIG_KEYPAD == IPOD_4G_PAD) - return PLUGIN_OK; + goto out; #endif scroll_up(&info, info.display_lines); break; @@ -307,13 +349,14 @@ int view_text(const char *title, const char *text) case PLA_SELECT: case PLA_EXIT: case PLA_CANCEL: - return PLUGIN_OK; + goto out; default: - if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + if ((rb->default_event_handler_ex(button, cleanup, + &info) == SYS_USB_CONNECTED)) return PLUGIN_USB_CONNECTED; - break; } } - +out: + cleanup(&info); return PLUGIN_OK; } diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index 316b33292a..4eb727b7bb 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -187,7 +187,8 @@ static int browse_file_or_dir(struct dir_stats *stats) continue; switch(button) { - case ACTION_STD_OK:; + case ACTION_STD_OK: + rb->gui_synclist_scroll_stop(&properties_lists); int sel_pos = rb->gui_synclist_get_sel_pos(&properties_lists); /* "Show Track Info..." selected? */ @@ -197,17 +198,10 @@ static int browse_file_or_dir(struct dir_stats *stats) return -1; else { + const unsigned char* const *props = (props_type == PROPS_DIR) ? + props_dir : props_file; /* Display field in fullscreen */ - FOR_NB_SCREENS(i) - rb->viewportmanager_theme_enable(i, false, NULL); - if (props_type == PROPS_DIR) - view_text((char *) p2str(props_dir[sel_pos]), - (char *) props_dir[sel_pos + 1]); - else - view_text((char *) p2str(props_file[sel_pos]), - (char *) props_file[sel_pos + 1]); - FOR_NB_SCREENS(i) - rb->viewportmanager_theme_undo(i, false); + view_text((char *) p2str(props[sel_pos]), props[sel_pos + 1]); rb->gui_synclist_set_title(&properties_lists, rb->str(props_type == PROPS_DIR ? diff --git a/apps/screens.c b/apps/screens.c index 6cbd44c3d4..e6031d8f57 100644 --- a/apps/screens.c +++ b/apps/screens.c @@ -827,6 +827,7 @@ refresh_info: { if (key == ACTION_STD_OK) { + gui_synclist_scroll_stop(&id3_lists); int header_id = id3_headers[info.info_id[id3_lists.selected_item/2]]; char* title_and_text[2]; title_and_text[0] = str(header_id); @@ -835,13 +836,7 @@ refresh_info: title_and_text[1] = (char*)id3_get_or_speak_info(id3_lists.selected_item+1,&info, buffer, sizeof(buffer), false); if (view_text) - { - FOR_NB_SCREENS(i) - viewportmanager_theme_enable(i, false, NULL); view_text(title_and_text[0], title_and_text[1]); - FOR_NB_SCREENS(i) - viewportmanager_theme_undo(i, false); - } else plugin_load(VIEWERS_DIR"/view_text.rock", title_and_text); gui_synclist_set_title(&id3_lists, str(LANG_TRACK_INFO), NOICON); From c41beebcdad6e6e10bcc343c77fd54b4ab3dbadb Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Wed, 29 Apr 2026 09:07:41 +0200 Subject: [PATCH 29/88] gui: delay updating SBS when setting list title We currently force a skin refresh when setting the list title. This causes very noticeable flickering of the list, if the SBS draws over the UI viewport, when there is no displayable list content yet (For an example, check out the Adwaitapod theme). Instead, only mark the title as dirty. Later, when drawing the list, register for a UI update callback and ask the skin engine to render, so that we can draw the list at the same time. Note: Flickering related to display updates when switching activities or when toggling the theme is unrelated to this, and will still need to be addressed in separate commits. Change-Id: Icce899905aa311deccb0cc498aacce2866aaae8a --- apps/gui/list.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/gui/list.c b/apps/gui/list.c index 572a7757cc..9bbe09f325 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -49,6 +49,8 @@ void list_draw(struct screen *display, struct gui_synclist *list); static long last_dirty_tick; +static bool sb_title_is_dirty; +static bool theme_enabled; static struct viewport parent[NB_SCREENS]; static struct gui_synclist *current_lists; @@ -220,11 +222,32 @@ int gui_list_get_item_offset(struct gui_synclist * gui_list, return item_offset; } +static void sb_title_cb(unsigned short id, void *data, void *userdata) +{ + (void)id; + (void)data; + theme_enabled = true; + gui_synclist_draw((struct gui_synclist *) userdata); +} + /* * Force a full screen update. */ void gui_synclist_draw(struct gui_synclist *gui_list) { + if (sb_title_is_dirty) + { + sb_title_is_dirty = theme_enabled = false; + + /* tell skin engine to refresh, then call us back */ + add_event_ex(GUI_EVENT_NEED_UI_UPDATE, true, sb_title_cb, gui_list); + send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); + remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, sb_title_cb, gui_list); + + /* sb_title_cb was only called if theme is enabled */ + if (theme_enabled) + return; + } if (list_is_dirty(gui_list)) { list_init_viewports(gui_list); @@ -439,7 +462,7 @@ void gui_synclist_set_title(struct gui_synclist * gui_list, gui_list->title_icon = icon; FOR_NB_SCREENS(i) sb_set_title_text(title, icon, i); - send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); + sb_title_is_dirty = true; } void gui_synclist_set_nb_items(struct gui_synclist * lists, int nb_items) From dbcee0deae9e9d611e290b1a1a68b727241b3970 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Wed, 29 Apr 2026 11:33:21 +0200 Subject: [PATCH 30/88] gui: defer deadspace viewport update Change-Id: Idac4a2bf21ced25cbf4349dc32ef62b3a456f999 --- apps/gui/viewport.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/gui/viewport.c b/apps/gui/viewport.c index 1d89467682..b50cba7d74 100644 --- a/apps/gui/viewport.c +++ b/apps/gui/viewport.c @@ -91,13 +91,12 @@ static void toggle_events(bool enable) #endif } -static void set_clear_update_valid_vp(enum screen_type screen, struct viewport *vp) +static void set_clear_valid_vp(enum screen_type screen, struct viewport *vp) { if (vp->width && vp->height) { screens[screen].set_viewport(vp); screens[screen].clear_viewport(); - screens[screen].update_viewport(); } } @@ -135,21 +134,21 @@ static void toggle_theme(enum screen_type screen, bool force) deadspace.y = 0; deadspace.width = screens[screen].lcdwidth; deadspace.height = user.y; - set_clear_update_valid_vp(screen, &deadspace); + set_clear_valid_vp(screen, &deadspace); /* below */ deadspace.y = user.y + user.height; deadspace.height = screens[screen].lcdheight - deadspace.y; - set_clear_update_valid_vp(screen, &deadspace); + set_clear_valid_vp(screen, &deadspace); /* left */ deadspace.x = 0; deadspace.y = 0; deadspace.width = user.x; deadspace.height = screens[screen].lcdheight; - set_clear_update_valid_vp(screen, &deadspace); + set_clear_valid_vp(screen, &deadspace); /* below */ deadspace.x = user.x + user.width; deadspace.width = screens[screen].lcdwidth - deadspace.x; - set_clear_update_valid_vp(screen, &deadspace); + set_clear_valid_vp(screen, &deadspace); screens[screen].set_viewport(last_vp); } From 4ea2f57aaa6e6dbaa7c34a6520c133f6c2dacd22 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Fri, 1 May 2026 10:17:02 -0400 Subject: [PATCH 31/88] lang: Update URL for translation site Change-Id: I0e054a2bbae994e2eadd81abf096d4ac463d4bb5 --- apps/lang/english.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 45ed8938fc..8e0523a42b 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -78,7 +78,7 @@ # tools/voice-corrections.txt for further details. # # To validate the contents of a translation, you can use the Rockbox -# translation site at https://translate.rockbox.org. Alternatively, you +# translation site at https://www.rockbox.org/translate/ Alternatively, you # can use the command-line 'updatelang' tool as follows: # # tools/updatelang apps/lang/english.lang path/to/translation.lang path/to/updated.lang From 52edc2e06954c4285b13fa242a6a925801095d51 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Thu, 30 Apr 2026 12:42:01 -0400 Subject: [PATCH 32/88] [Feature] allow displaying the WPS/tree hotkey menu with hotkey press add 'Context Menu' item to WPS and Tree hotkeys this allows a user to display a menu of hotkey actions to execute when they press the hotkey items are voiced added 'View Album Art' Change-Id: I2199c4de536f347016e7a8d7f3c063da0b56a9a0 --- apps/onplay.c | 76 +++++++++++++++++++++++++++++++++++++++++--- apps/onplay.h | 2 ++ apps/settings_list.c | 32 ++++++++++++------- 3 files changed, 93 insertions(+), 17 deletions(-) diff --git a/apps/onplay.c b/apps/onplay.c index 82b5ab8931..824b2f38cc 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -1287,6 +1287,7 @@ static int hotkey_tree_run_plugin(void *param) return ONPLAY_RELOAD_DIR; } +static int hotkey_run_menu(void); /* display hotkey items as a menu */ static int hotkey_wps_run_plugin(void) { open_plugin_run(ID2P(LANG_HOTKEY_WPS)); @@ -1297,6 +1298,7 @@ static int hotkey_wps_run_plugin(void) /* Any desired hotkey functions go here, in the enum in onplay.h, and in the settings menu in settings_list.c. The order here is not important. */ + static const struct hotkey_assignment hotkey_items[] = { [0]{ .action = HOTKEY_OFF, .lang_id = LANG_OFF, @@ -1360,6 +1362,18 @@ static const struct hotkey_assignment hotkey_items[] = { .func = HOTKEY_FUNC(hotkey_tree_run_plugin, (void *)"properties"), .return_code = ONPLAY_FUNC_RETURN, .flags = HOTKEY_FLAG_TREE }, + { .action = HOTKEY_CONTEXT_MENU, + .lang_id = LANG_ONPLAY_MENU_TITLE, + .func = HOTKEY_FUNC(hotkey_run_menu, NULL), + .return_code = ONPLAY_FUNC_RETURN, + .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_TREE }, +#ifdef HAVE_ALBUMART + { .action = HOTKEY_ALBUMART, + .lang_id = LANG_VIEW_ALBUMART, + .func = HOTKEY_FUNC(view_album_art, NULL), + .return_code = ONPLAY_OK, + .flags = HOTKEY_FLAG_WPS | HOTKEY_FLAG_NOSBS }, +#endif #ifdef HAVE_TAGCACHE { .action = HOTKEY_PICTUREFLOW, .lang_id = LANG_ONPLAY_PICTUREFLOW, @@ -1380,11 +1394,8 @@ const struct hotkey_assignment *get_hotkey(int action) } /* Execute the hotkey function, if listed */ -static int execute_hotkey(bool is_wps) +static int execute_hotkey(int action) { - const int action = (is_wps ? global_settings.hotkey_wps : - global_settings.hotkey_tree); - /* search assignment struct for a match for the hotkey setting */ const struct hotkey_assignment *this_item = get_hotkey(action); @@ -1405,6 +1416,60 @@ static int execute_hotkey(bool is_wps) return func_return; /* Use value returned by function */ return return_code; /* or return the associated value */ } + +static const char* hotkey_get_name(int selected_item, void * data, + char * buffer, size_t buffer_len) +{ + (void)buffer; (void)buffer_len; + const struct hotkey_assignment **hk_menu = (const struct hotkey_assignment **)data; + return ID2P(hk_menu[selected_item + 1]->lang_id); /* +1 to skip HOTKEY_OFF */ +} + +static int hotkey_get_talk(int selected_item, void * data) +{ + if (global_settings.talk_menu) + { + const struct hotkey_assignment **hk_menu = (const struct hotkey_assignment **)data; + talk_id(hk_menu[selected_item + 1]->lang_id, false); /* +1 to skip HOTKEY_OFF */ + } + return 0; +} + +static int hotkey_run_menu(void) +{ + intptr_t flag = HOTKEY_FLAG_WPS; + if (selected_file.context != CONTEXT_WPS) + flag = HOTKEY_FLAG_TREE; + + const struct hotkey_assignment *hk_menu[ARRAYLEN(hotkey_items)]; + + struct simplelist_info info; + int count = 0; + for (size_t i = 0; i < ARRAYLEN(hotkey_items); i++) + { + hk_menu[i] = NULL; /*clear all the hk_menu entries prior to setting them */ + if ((hotkey_items[i].flags & flag) == flag) + { + if (hotkey_items[i].action != HOTKEY_CONTEXT_MENU) + { + hk_menu[count++] = &hotkey_items[i]; + } + } + } + + /* count -1 don't display HOTKEY_OFF item */ + simplelist_info_init(&info, str(LANG_ONPLAY_MENU_TITLE), count - 1, (void*)&hk_menu); + info.get_name = hotkey_get_name; + info.get_icon = NULL; + info.get_talk = hotkey_get_talk; + + simplelist_show_list(&info); + if (info.selection >= 0) /* run user selected hotkey item */ + { + return execute_hotkey(hk_menu[info.selection + 1]->action); + } + return ONPLAY_RELOAD_DIR; +} #endif /* HOTKEY */ int onplay(char* file, int attr, int from_context, bool hotkey, int customaction) @@ -1441,7 +1506,8 @@ int onplay(char* file, int attr, int from_context, bool hotkey, int customaction #ifdef HAVE_HOTKEY if (hotkey) - return execute_hotkey(from_context == CONTEXT_WPS); + return execute_hotkey((from_context == CONTEXT_WPS ? + global_settings.hotkey_wps : global_settings.hotkey_tree)); #else (void)hotkey; #endif diff --git a/apps/onplay.h b/apps/onplay.h index 4e5e6eeb69..e811b9b983 100644 --- a/apps/onplay.h +++ b/apps/onplay.h @@ -63,6 +63,8 @@ enum hotkey_action { HOTKEY_INSERT, HOTKEY_INSERT_SHUFFLED, HOTKEY_BOOKMARK_LIST, + HOTKEY_ALBUMART, + HOTKEY_CONTEXT_MENU, /* shows / executes above actions in a menu */ }; enum hotkey_flags { HOTKEY_FLAG_NONE = 0x0, diff --git a/apps/settings_list.c b/apps/settings_list.c index 59fd4537a0..e8508ab099 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -2337,30 +2337,38 @@ const struct settings_list settings[] = { #endif #ifdef HAVE_HOTKEY +/* WPS HOTKEY */ TABLE_SETTING(F_CB_ON_SELECT_ONLY, hotkey_wps, LANG_HOTKEY_WPS, HOTKEY_VIEW_PLAYLIST, "hotkey wps", "off,view playlist,show track info,pitchscreen,open with,delete,bookmark,plugin,bookmark list" - ,UNIT_INT, hotkey_formatter, hotkey_getlang, hotkey_callback,9, HOTKEY_OFF, - HOTKEY_VIEW_PLAYLIST, HOTKEY_SHOW_TRACK_INFO, HOTKEY_PITCHSCREEN, - HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_BOOKMARK, HOTKEY_PLUGIN, HOTKEY_BOOKMARK_LIST), +#ifdef HAVE_ALBUMART + ",show_album_art,context menu" + ,UNIT_INT, hotkey_formatter, hotkey_getlang, hotkey_callback,11, +#else + ",context menu" + ,UNIT_INT, hotkey_formatter, hotkey_getlang, hotkey_callback,10, +#endif + HOTKEY_OFF, HOTKEY_VIEW_PLAYLIST, HOTKEY_SHOW_TRACK_INFO, HOTKEY_PITCHSCREEN, + HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_BOOKMARK, HOTKEY_PLUGIN, HOTKEY_BOOKMARK_LIST, +#ifdef HAVE_ALBUMART + HOTKEY_ALBUMART, +#endif + HOTKEY_CONTEXT_MENU), +/* TREE HOTKEY */ TABLE_SETTING(0, hotkey_tree, LANG_HOTKEY_FILE_BROWSER, HOTKEY_OFF, "hotkey tree", #ifdef HAVE_TAGCACHE - "off,properties,pictureflow,open with,delete,insert,insert shuffled", + "off,properties,pictureflow,open with,delete,insert,insert shuffled,context menu", + UNIT_INT, hotkey_formatter, hotkey_getlang, NULL, 8, #else - "off,properties,open with,delete,insert,insert shuffled", -#endif - UNIT_INT, hotkey_formatter, hotkey_getlang, NULL, -#ifdef HAVE_TAGCACHE - 7, -#else - 6, + "off,properties,open with,delete,insert,insert shuffled,context menu", + UNIT_INT, hotkey_formatter, hotkey_getlang, NULL, 7, #endif HOTKEY_OFF,HOTKEY_PROPERTIES, #ifdef HAVE_TAGCACHE HOTKEY_PICTUREFLOW, #endif - HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_INSERT, HOTKEY_INSERT_SHUFFLED), + HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_INSERT, HOTKEY_INSERT_SHUFFLED, HOTKEY_CONTEXT_MENU), #endif /* HAVE_HOTKEY */ INT_SETTING(F_TIME_SETTING, resume_rewind, LANG_RESUME_REWIND, 0, From 8e8206f6d59f93d19c9116e09f461b513c646e5c Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Fri, 1 May 2026 11:40:33 -0400 Subject: [PATCH 33/88] =?UTF-8?q?FS#13886=20-=20Updated=20Serbian=20transl?= =?UTF-8?q?ation=20(Ivan=20Pe=C5=A1i=C4=87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I10321a41db686ebbfdadf82c84d3797551fe7319 --- apps/lang/srpski.lang | 142 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/apps/lang/srpski.lang b/apps/lang/srpski.lang index 1a238f8207..1044e3f32b 100644 --- a/apps/lang/srpski.lang +++ b/apps/lang/srpski.lang @@ -14780,7 +14780,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14795,7 +14795,7 @@ *: "Притисните LEFT за прекид." - android,hifietma*,zenvision: "Притисните BACK за прекид." + android,hifietma*: "Притисните BACK за прекид." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Притисните POWER за прекид." ihifi760,ihifi960: "Дупли тап RETURN за прекид." ihifi770,ihifi770c,ihifi800: "Притисните HOME за прекид." @@ -14810,7 +14810,7 @@ *: "Притисните LEFT за прекид." - android,hifietma*,zenvision: "Притисните BACK за прекид." + android,hifietma*: "Притисните BACK за прекид." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Притисните POWER за прекид." ihifi760,ihifi960: "Дупли тап RETURN за прекид." ihifi770,ihifi770c,ihifi800: "Притисните HOME за прекид." @@ -15305,7 +15305,7 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" @@ -15914,16 +15914,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Подраз. прегледач" + *: "" - *: "Подраз. прегледач" + *: "" @@ -16972,3 +16972,129 @@ *: "У Ес Бе" + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Увек памти последњи фолдер" + + + *: "Увек памти последњи фолдер" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Фајл није пронађен" + + + *: "Фајл није пронађен" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Прикажи у фајловима" + + + *: "Прикажи у фајловима" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Замени леви и десни" + + + *: "Замени леви и десни" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "ПОСТАВИ ТАЈМЕР" + + + *: "Постави тајмер" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "ОДБРОЈАВАЊЕ" + + + *: "Одбројавање" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "ПАУЗИРАНО" + + + *: "Паузирано" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "ИСТЕКЛО ВРЕМЕ" + + + *: "Истекло време" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "ГОТОВО" + + + *: "Готово" + + From f886bfc572f6a102ed1969be6c0316402a2cbb14 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Fri, 1 May 2026 22:42:58 -0400 Subject: [PATCH 34/88] misc: Address issues uncovered with GCC 16 + binutils 2.46 (1/N) * Funky macro-based definitions for memchr and strstr which require an #undef before we use our own in codecs & plugins * Return value of of strstr is const Still have several more warnings and link failure with some plugins but this is a good start. Change-Id: Ife1f2d3e6f0e0629e3125a9058abc39c6102f452 --- apps/cuesheet.c | 7 ++++--- apps/filetypes.c | 4 ++-- apps/gui/skin_engine/skin_parser.c | 2 +- apps/playlist.c | 2 +- apps/playlist_viewer.c | 2 +- apps/plugin.h | 2 ++ apps/plugins/lua/rockconf.h | 4 ++++ lib/rbcodec/codecs/lib/codeclib.c | 7 ++++--- 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/cuesheet.c b/apps/cuesheet.c index c5a1aacad9..9779f40c5d 100644 --- a/apps/cuesheet.c +++ b/apps/cuesheet.c @@ -46,7 +46,8 @@ static bool search_for_cuesheet(const char *path, struct cuesheet_file *cue_file { size_t len; char cuepath[MAX_PATH]; - char *dot, *slash, *slash_cuepath; + char *dot, *slash_cuepath; + const char *slash; cue_file->pos = 0; cue_file->size = 0; @@ -281,7 +282,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) } s = skip_whitespace(line); -/* RECOGNIZED TAGS *********************** +/* RECOGNIZED TAGS *********************** * eCS_TRACK = 0, eCS_INDEX_01, eCS_TITLE, * eCS_PERFORMER, eCS_SONGWRITER, eCS_FILE, */ @@ -306,7 +307,7 @@ bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue) cue->tracks[cue->track_count-1].offset = parse_cue_index(s); #endif } - else if (option != eCS_NOTFOUND) + else if (option != eCS_NOTFOUND) { char *dest = NULL; char *string = get_string(s); diff --git a/apps/filetypes.c b/apps/filetypes.c index 88e6a17666..0e16b2c24b 100644 --- a/apps/filetypes.c +++ b/apps/filetypes.c @@ -525,7 +525,7 @@ static void read_config_init(int fd) static int file_find_extension(const char* file) { - char *extension = strrchr(file, '.'); + const char *extension = strrchr(file, '.'); if (extension) extension++; return find_extension(extension); @@ -704,7 +704,7 @@ int filetype_load_plugin(const char* plugin, const char* file) { int i; char plugin_name[MAX_PATH]; - char *s; + const char *s; for (i=1;icodec_get_buffer((size_t *)&bufsize); - + return 0; } @@ -64,9 +64,9 @@ void* codec_malloc(size_t size) if (mem_ptr + (long)size > bufsize) return NULL; - + x=&mallocbuf[mem_ptr]; - + /* Keep memory aligned to MEM_ALIGN_SIZE. */ mem_ptr += MEM_ALIGN_UP(size); @@ -136,6 +136,7 @@ int memcmp(const void *s1, const void *s2, size_t n) return(ci->memcmp(s1,s2,n)); } +#undef memchr void* memchr(const void *s, int c, size_t n) { return(ci->memchr(s,c,n)); From 89dd08a3b46371233bca5e55c255f004cf01d3c3 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 2 May 2026 07:40:15 -0400 Subject: [PATCH 35/88] configure: Explicitly disallow erosqnative_v3/v4 for "normal" builds They are only intended for bootloaders. Change-Id: I4da3e7acd55b803e016bc9c42526860dfdc6fa9a --- tools/configure | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/configure b/tools/configure index 27fb62803c..800013ee25 100755 --- a/tools/configure +++ b/tools/configure @@ -3976,6 +3976,7 @@ fi # player version, for bootloader usage # version 3 extradefines="$extradefines -DEROSQN_VER=3" + bootloader_only=1 ;; 249|erosqnative_v4) @@ -4004,6 +4005,7 @@ fi # player version, for bootloader usage # version 4 extradefines="$extradefines -DEROSQN_VER=4" + bootloader_only=1 ;; 232|ihifi770c) @@ -4412,6 +4414,11 @@ if [ -z "$debug" ]; then GCCOPTS="$GCCOPTS $GCCOPTIMIZE" fi +if [ "$bootloader_only" = "1" -a "$bootloader" != "1" ]; then + echo "Target can only be used for bootloader builds." + exit 9 +fi + # if building a simulator for an hosted port, APPLICATION # define clashes with SIMULATOR define From 83e55164f4f1bbb721c56f2927d8987807bbf29a Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sat, 2 May 2026 14:19:29 +0200 Subject: [PATCH 36/88] gui: remove SBS lock/unlock redraw lag Stop the lock indicators on the SBS from lagging behind their actual state when lock notifications are disabled. Request immediate skin update in button loop, so the device doesn't feel laggy. Change-Id: I42955f65d9ad4ca9196549d806538d1badb5f79d --- apps/action.c | 20 +++++++++++--------- apps/gui/skin_engine/skin_engine.c | 15 ++++++++++++++- apps/gui/skin_engine/skin_engine.h | 1 + apps/gui/statusbar-skinned.c | 5 +++++ apps/gui/statusbar-skinned.h | 1 + firmware/backlight.c | 4 ++++ 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apps/action.c b/apps/action.c index bd52e8177a..34af7f30a1 100644 --- a/apps/action.c +++ b/apps/action.c @@ -28,6 +28,7 @@ #if !defined(BOOTLOADER) #include "language.h" +#include "skin_engine/skin_engine.h" #endif #include "appevents.h" @@ -937,19 +938,20 @@ static inline void do_softlock(action_last_t *last, action_cur_t *cur) if (notify_user) { +#ifndef BOOTLOADER + skin_request_update_locked(); +#endif action_handle_backlight(true, false); -#ifdef HAVE_BACKLIGHT - /* If we don't wait for a moment for the backlight queue to process, - * the user will never see the message - */ - if (!is_backlight_on(false)) - { - sleep(HZ/2); - } -#endif if (!has_flag(last->softlock_mask, SEL_ACTION_ALLNONOTIFY)) { +#ifdef HAVE_BACKLIGHT + /* If we don't wait for a moment for the backlight queue to process, + * the user will never see the message + */ + if (!is_backlight_on(false)) + sleep(HZ/2); +#endif if (last->keys_locked) { splash(HZ/2, ID2P(LANG_KEYLOCK_ON)); diff --git a/apps/gui/skin_engine/skin_engine.c b/apps/gui/skin_engine/skin_engine.c index bc2f599b4e..99a33caea8 100644 --- a/apps/gui/skin_engine/skin_engine.c +++ b/apps/gui/skin_engine/skin_engine.c @@ -180,7 +180,7 @@ void settings_apply_skins(void) audio_stop(); bool first_run = skin_backdrop_init(); - + if (!first_run) { /* Make sure all skins unloaded */ @@ -342,6 +342,19 @@ void skin_request_full_update(enum skinnable_screens skin) skins[skin][i].needs_full_update = true; } + +/* Request skin update for lock state change */ +void skin_request_update_locked(void) +{ + if (get_current_activity() == ACTIVITY_WPS) + return; + + sb_skin_force_next_update(); +#ifdef HAS_BUTTON_HOLD + button_queue_post(BUTTON_NONE, 0); +#endif +} + bool dbg_skin_engine(void) { struct simplelist_info info; diff --git a/apps/gui/skin_engine/skin_engine.h b/apps/gui/skin_engine/skin_engine.h index ce9527ab12..383086a19c 100644 --- a/apps/gui/skin_engine/skin_engine.h +++ b/apps/gui/skin_engine/skin_engine.h @@ -82,6 +82,7 @@ void skin_unload_all(void); bool skin_do_full_update(enum skinnable_screens skin, enum screen_type screen); void skin_request_full_update(enum skinnable_screens skin); +void skin_request_update_locked(void); bool dbg_skin_engine(void); diff --git a/apps/gui/statusbar-skinned.c b/apps/gui/statusbar-skinned.c index 0242016e1e..1aaef617c9 100644 --- a/apps/gui/statusbar-skinned.c +++ b/apps/gui/statusbar-skinned.c @@ -224,6 +224,11 @@ void sb_skin_set_update_delay(int delay) update_delay = delay; } +void sb_skin_force_next_update(void) +{ + force_waiting = true; +} + /* This creates and loads a ".sbs" based on the user settings for: * - regular statusbar * - colours diff --git a/apps/gui/statusbar-skinned.h b/apps/gui/statusbar-skinned.h index 905a15b369..323ce7f0d2 100644 --- a/apps/gui/statusbar-skinned.h +++ b/apps/gui/statusbar-skinned.h @@ -38,6 +38,7 @@ struct viewport *sb_skin_get_info_vp(enum screen_type screen); void sb_skin_update(enum screen_type screen, bool force); void sb_skin_set_update_delay(int delay); +void sb_skin_force_next_update(void); bool sb_set_title_text(const char* title, enum themable_icons icon, enum screen_type screen); bool sb_set_persistent_title(const char* title, enum themable_icons icon, enum screen_type screen); diff --git a/firmware/backlight.c b/firmware/backlight.c index ee7b147aee..c6bc7d8aba 100644 --- a/firmware/backlight.c +++ b/firmware/backlight.c @@ -24,6 +24,7 @@ #if !defined(BOOTLOADER) #include "settings.h" #include "action.h" +#include "../apps/gui/skin_engine/skin_engine.h" #endif #include #include "cpu.h" @@ -887,6 +888,9 @@ void backlight_set_timeout_plugged(int value) /* Hold button change event handler. */ void backlight_hold_changed(bool hold_button) { +#ifndef BOOTLOADER + skin_request_update_locked(); +#endif if (!hold_button || (backlight_on_button_hold > 0)) { /* if unlocked or override in effect */ From 88d4903d10385fade0a6eeb3c679c91a7a20d623 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 1 May 2026 19:28:12 +0200 Subject: [PATCH 37/88] gui: fix "lock screens" making UI viewport disappear Themes like Adwaitapod, Themify, or FreshOS have custom "lock screens" that are drawn on top of the UI viewport. Request a full skin update when unlocked, so you don't have to press a button to make the hidden UI viewport appear again. Change-Id: Idf5023b4e12f7aea1cd7a2e9d9ab2f754387dc48 --- apps/action.c | 2 +- apps/gui/skin_engine/skin_engine.c | 6 +++++- apps/gui/skin_engine/skin_engine.h | 2 +- firmware/backlight.c | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/action.c b/apps/action.c index 34af7f30a1..c7dd74f60a 100644 --- a/apps/action.c +++ b/apps/action.c @@ -939,7 +939,7 @@ static inline void do_softlock(action_last_t *last, action_cur_t *cur) if (notify_user) { #ifndef BOOTLOADER - skin_request_update_locked(); + skin_request_update_locked(last->keys_locked); #endif action_handle_backlight(true, false); diff --git a/apps/gui/skin_engine/skin_engine.c b/apps/gui/skin_engine/skin_engine.c index 99a33caea8..5528385f80 100644 --- a/apps/gui/skin_engine/skin_engine.c +++ b/apps/gui/skin_engine/skin_engine.c @@ -344,12 +344,16 @@ void skin_request_full_update(enum skinnable_screens skin) /* Request skin update for lock state change */ -void skin_request_update_locked(void) +void skin_request_update_locked(bool locked) { if (get_current_activity() == ACTIVITY_WPS) return; sb_skin_force_next_update(); + + /* fix themes that draw on top of the UI viewport when locked */ + if (!locked) + skin_request_full_update(CUSTOM_STATUSBAR); #ifdef HAS_BUTTON_HOLD button_queue_post(BUTTON_NONE, 0); #endif diff --git a/apps/gui/skin_engine/skin_engine.h b/apps/gui/skin_engine/skin_engine.h index 383086a19c..fbaf1bf68a 100644 --- a/apps/gui/skin_engine/skin_engine.h +++ b/apps/gui/skin_engine/skin_engine.h @@ -82,7 +82,7 @@ void skin_unload_all(void); bool skin_do_full_update(enum skinnable_screens skin, enum screen_type screen); void skin_request_full_update(enum skinnable_screens skin); -void skin_request_update_locked(void); +void skin_request_update_locked(bool locked); bool dbg_skin_engine(void); diff --git a/firmware/backlight.c b/firmware/backlight.c index c6bc7d8aba..3623162adb 100644 --- a/firmware/backlight.c +++ b/firmware/backlight.c @@ -889,7 +889,7 @@ void backlight_set_timeout_plugged(int value) void backlight_hold_changed(bool hold_button) { #ifndef BOOTLOADER - skin_request_update_locked(); + skin_request_update_locked(hold_button); #endif if (!hold_button || (backlight_on_button_hold > 0)) { From 0dc670d25cb6a4a7283c007f7bf96a73fbc18da0 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 2 May 2026 13:22:51 -0400 Subject: [PATCH 38/88] build: Make bitmap object files position independent to silence linker warnings Finally addresses the "warning: creating DT_TEXTREL in a shared object" warnings seen when linking plugins that reference bitmaps. This currently only happens with simulator builds using recent-ish toolchains (GCC >= 12 IIRC). However, binutils 2.46 promotes this warning to an error, so it's finally tracked down and addressed. Change-Id: I4b4926c14f7c0047496892c55009c26da2a4756d --- apps/bitmaps/bitmaps.make | 8 ++++++++ apps/plugins/bitmaps/pluginbitmaps.make | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/apps/bitmaps/bitmaps.make b/apps/bitmaps/bitmaps.make index d39531a8b8..cf166f3f8c 100644 --- a/apps/bitmaps/bitmaps.make +++ b/apps/bitmaps/bitmaps.make @@ -50,3 +50,11 @@ $(BUILDDIR)/apps/bitmaps/remote_mono/%.c: $(ROOTDIR)/apps/bitmaps/remote_mono/%. $(BUILDDIR)/apps/bitmaps/remote_native/%.c: $(ROOTDIR)/apps/bitmaps/remote_native/%.bmp $(TOOLSDIR)/bmp2rb $(SILENT)mkdir -p $(dir $@) $(BMPINCDIR) $(call PRINTS,BMP2RB $( $@ + +ifdef APP_TYPE +# Bitmaps must be explicitly Position independent to avoid linker warnings +$(BUILDDIR)/apps/bitmaps/native/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/bitmaps/mono/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/bitmaps/remote_mono/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/bitmaps/remote_native/%.o: CFLAGS += -fPIC +endif diff --git a/apps/plugins/bitmaps/pluginbitmaps.make b/apps/plugins/bitmaps/pluginbitmaps.make index 78294a5e8c..caf7d23535 100644 --- a/apps/plugins/bitmaps/pluginbitmaps.make +++ b/apps/plugins/bitmaps/pluginbitmaps.make @@ -58,4 +58,12 @@ $(BUILDDIR)/apps/plugins/bitmaps/remote_native/%.c: $(ROOTDIR)/apps/plugins/bitm $(SILENT)mkdir -p $(dir $@) $(PBMPINCDIR) $(call PRINTS,BMP2RB $( $@ +ifdef APP_TYPE +# Bitmaps must be explicitly Position independent to avoid linker warnings +$(BUILDDIR)/apps/plugins/bitmaps/native/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/plugins/bitmaps/mono/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/plugins/bitmaps/remote_mono/%.o: CFLAGS += -fPIC +$(BUILDDIR)/apps/plugins/bitmaps/remote_native/%.o: CFLAGS += -fPIC +endif + endif From 472acce40182eb0cf684ee53ff909ea8be195e14 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 1 May 2026 13:08:11 +0900 Subject: [PATCH 39/88] reggen.h: fix broken stm32 build with gcc-14 Change-Id: Ib504e6a75ac88be325adc6dd19c88bf263178f2d --- firmware/export/reggen.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/export/reggen.h b/firmware/export/reggen.h index ce66bd718b..4a15ddb912 100644 --- a/firmware/export/reggen.h +++ b/firmware/export/reggen.h @@ -22,7 +22,9 @@ #ifndef __REGGEN_H__ #define __REGGEN_H__ +#ifndef __ASSEMBLER__ #include +#endif #define __REGGEN_VAR_OR1(p, s1) \ ((p ## s1)) From cf6fb81346e39c35624a97e8611478607cb52833 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 2 May 2026 22:32:59 +0100 Subject: [PATCH 40/88] firmware: drop hand-rolled from libc Perhaps this was needed in the distant past, but these days GCC provides a working for freestanding targets. Change-Id: I4f02f12058a13b6a086ccc52f02a12ce21a986c1 --- firmware/libc/include/inttypes.h | 2 +- firmware/libc/include/stdint.h | 112 ------------------------------- 2 files changed, 1 insertion(+), 113 deletions(-) delete mode 100644 firmware/libc/include/stdint.h diff --git a/firmware/libc/include/inttypes.h b/firmware/libc/include/inttypes.h index ddcaa988b1..ed6fd6d47c 100644 --- a/firmware/libc/include/inttypes.h +++ b/firmware/libc/include/inttypes.h @@ -18,11 +18,11 @@ * KIND, either express or implied. * ****************************************************************************/ - #ifndef __INTTYPES_H__ #define __INTTYPES_H__ #include +#include /* could possibly have (f)scanf format specifiers here */ diff --git a/firmware/libc/include/stdint.h b/firmware/libc/include/stdint.h deleted file mode 100644 index a36dcf59b7..0000000000 --- a/firmware/libc/include/stdint.h +++ /dev/null @@ -1,112 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2005 by Dave Chapman - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -#ifndef __STDINT_H__ -#define __STDINT_H__ - -#include - -/* 8 bit */ -#define INT8_MIN SCHAR_MIN -#define INT8_MAX SCHAR_MAX -#define UINT8_MAX UCHAR_MAX -#define int8_t signed char -#define uint8_t unsigned char - -/* 16 bit */ -#if USHRT_MAX == 0xffff - -#define INT16_MIN SHRT_MIN -#define INT16_MAX SHRT_MAX -#define UINT16_MAX USHRT_MAX -#define int16_t short -#define uint16_t unsigned short - -#endif - -/* 32 bit */ -#if ULONG_MAX == 0xfffffffful - -#define SIZE_MAX ULONG_MAX -#define INT32_MIN LONG_MIN -#define INT32_MAX LONG_MAX -#define UINT32_MAX ULONG_MAX -#define int32_t long -#define uint32_t unsigned long - -#define INTPTR_MIN LONG_MIN -#define INTPTR_MAX LONG_MAX -#define UINTPTR_MAX ULONG_MAX -#define intptr_t long -#define uintptr_t unsigned long - -#elif UINT_MAX == 0xffffffffu - -#define SIZE_MAX UINT_MAX -#define INT32_MIN INT_MIN -#define INT32_MAX INT_MAX -#define UINT32_MAX UINT_MAX -#define int32_t int -#define uint32_t unsigned int - -#endif - -/* 64 bit */ -#ifndef LLONG_MIN -#define LLONG_MIN ((long long)9223372036854775808ull) -#endif - -#ifndef LLONG_MAX -#define LLONG_MAX 9223372036854775807ll -#endif - -#ifndef ULLONG_MAX -#define ULLONG_MAX 18446744073709551615ull -#endif - -#if ULONG_MAX == 0xffffffffffffffffull - -#define SIZE_MAX ULONG_MAX -#define INT64_MIN LONG_MIN -#define INT64_MAX LONG_MAX -#define UINT64_MAX ULONG_MAX -#define int64_t long -#define uint64_t unsigned long - -#define INTPTR_MIN LONG_MIN -#define INTPTR_MAX LONG_MAX -#define UINTPTR_MAX ULONG_MAX -#define intptr_t long -#define uintptr_t unsigned long - -#else - -#define INT64_MIN LLONG_MIN -#define INT64_MAX LLONG_MAX -#define UINT64_MAX ULLONG_MAX -#define int64_t long long -#define uint64_t unsigned long long - -#endif - -#define uintmax_t unsigned long long -#define intmax_t long long -#endif /* __STDINT_H__ */ From bafc796ce793dcebae593c54a2f42da4ef0016e2 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 2 May 2026 23:21:26 +0100 Subject: [PATCH 41/88] Fix errors from stdint.h removal (cf6fb81346e) Change-Id: Ie9457121448f47db14300e035dc5b5eccd086884 --- firmware/export/axp-2101.h | 1 + firmware/export/axp-pmu.h | 1 + firmware/target/arm/s5l8700/app.lds | 1 + firmware/target/arm/s5l8702/crypto-s5l8702.h | 2 ++ 4 files changed, 5 insertions(+) diff --git a/firmware/export/axp-2101.h b/firmware/export/axp-2101.h index e84f552730..cd810ff006 100644 --- a/firmware/export/axp-2101.h +++ b/firmware/export/axp-2101.h @@ -25,6 +25,7 @@ #include "config.h" #include #include +#include /* ADC channels */ #define AXP2101_ADC_VBAT_VOLTAGE 0 diff --git a/firmware/export/axp-pmu.h b/firmware/export/axp-pmu.h index 24c992dea3..77ef1cc356 100644 --- a/firmware/export/axp-pmu.h +++ b/firmware/export/axp-pmu.h @@ -25,6 +25,7 @@ #include "config.h" #include #include +#include /* ADC channels */ #define ADC_ACIN_VOLTAGE 0 diff --git a/firmware/target/arm/s5l8700/app.lds b/firmware/target/arm/s5l8700/app.lds index 01b57c3bbd..a0bc6550f3 100644 --- a/firmware/target/arm/s5l8700/app.lds +++ b/firmware/target/arm/s5l8700/app.lds @@ -1,3 +1,4 @@ +#define __ASSEMBLER__ #include "config.h" #include "cpu.h" diff --git a/firmware/target/arm/s5l8702/crypto-s5l8702.h b/firmware/target/arm/s5l8702/crypto-s5l8702.h index d32227c808..7915a53872 100644 --- a/firmware/target/arm/s5l8702/crypto-s5l8702.h +++ b/firmware/target/arm/s5l8702/crypto-s5l8702.h @@ -21,7 +21,9 @@ #ifndef __CRYPTO_S5L8702_H__ #define __CRYPTO_S5L8702_H__ +#ifndef __ASSEMBLER__ #include +#endif #include "config.h" From 3eba3ecd179fc1196b34babd4e8530b83972dbb2 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 2 May 2026 20:41:28 -0400 Subject: [PATCH 42/88] fill_metadata_from_path modifies a "const" argument ...so convert the argument to non-const Change-Id: I9e04d817e8674facae7c3be234fd9756f1a4f8ad --- lib/rbcodec/metadata/metadata.c | 6 +++--- lib/rbcodec/metadata/metadata.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c index fafd227c14..9dbb76e2a9 100644 --- a/lib/rbcodec/metadata/metadata.c +++ b/lib/rbcodec/metadata/metadata.c @@ -381,7 +381,7 @@ bool format_buffers_with_offset(int afmt) /* Simple file type probing by looking at the filename extension. */ unsigned int probe_file_format(const char *filename) { - char *suffix; + const char *suffix; unsigned int i; suffix = strrchr(filename, '.'); @@ -418,7 +418,7 @@ unsigned int probe_file_format(const char *filename) * file that would prevent playback. supply a filedescriptor <0 and the file will be opened * and closed automatically within the get_metadata call * get_metadata_ex allows flags to change the way get_metadata behaves - * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer + * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer * METADATA_CLOSE_FD_ON_EXIT closes the open filedescriptor on exit */ bool get_metadata_ex(struct mp3entry* id3, int fd, const char* trackname, int flags) @@ -546,7 +546,7 @@ void wipe_mp3entry(struct mp3entry *id3) } /* Glean what is possible from the filename alone - does not parse metadata */ -void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) +void fill_metadata_from_path(struct mp3entry *id3, char *trackname) { char *p; diff --git a/lib/rbcodec/metadata/metadata.h b/lib/rbcodec/metadata/metadata.h index a48afee004..45ac054726 100644 --- a/lib/rbcodec/metadata/metadata.h +++ b/lib/rbcodec/metadata/metadata.h @@ -335,7 +335,7 @@ void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); void wipe_mp3entry(struct mp3entry *id3); -void fill_metadata_from_path(struct mp3entry *id3, const char *trackname); +void fill_metadata_from_path(struct mp3entry *id3, char *trackname); int get_audio_base_codec_type(int type); const char * get_codec_string(int type); bool rbcodec_format_is_atomic(int afmt); From 026ce110f23d46f8d6dfb33a3e35a93375745e91 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 2 May 2026 22:58:45 -0400 Subject: [PATCH 43/88] Revert "fill_metadata_from_path modifies a "const" argument" This reverts commit 3eba3ecd179fc1196b34babd4e8530b83972dbb2. --- lib/rbcodec/metadata/metadata.c | 6 +++--- lib/rbcodec/metadata/metadata.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c index 9dbb76e2a9..fafd227c14 100644 --- a/lib/rbcodec/metadata/metadata.c +++ b/lib/rbcodec/metadata/metadata.c @@ -381,7 +381,7 @@ bool format_buffers_with_offset(int afmt) /* Simple file type probing by looking at the filename extension. */ unsigned int probe_file_format(const char *filename) { - const char *suffix; + char *suffix; unsigned int i; suffix = strrchr(filename, '.'); @@ -418,7 +418,7 @@ unsigned int probe_file_format(const char *filename) * file that would prevent playback. supply a filedescriptor <0 and the file will be opened * and closed automatically within the get_metadata call * get_metadata_ex allows flags to change the way get_metadata behaves - * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer + * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer * METADATA_CLOSE_FD_ON_EXIT closes the open filedescriptor on exit */ bool get_metadata_ex(struct mp3entry* id3, int fd, const char* trackname, int flags) @@ -546,7 +546,7 @@ void wipe_mp3entry(struct mp3entry *id3) } /* Glean what is possible from the filename alone - does not parse metadata */ -void fill_metadata_from_path(struct mp3entry *id3, char *trackname) +void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) { char *p; diff --git a/lib/rbcodec/metadata/metadata.h b/lib/rbcodec/metadata/metadata.h index 45ac054726..a48afee004 100644 --- a/lib/rbcodec/metadata/metadata.h +++ b/lib/rbcodec/metadata/metadata.h @@ -335,7 +335,7 @@ void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); void wipe_mp3entry(struct mp3entry *id3); -void fill_metadata_from_path(struct mp3entry *id3, char *trackname); +void fill_metadata_from_path(struct mp3entry *id3, const char *trackname); int get_audio_base_codec_type(int type); const char * get_codec_string(int type); bool rbcodec_format_is_atomic(int afmt); From 8768266d27616ec58278c980558dffcba2a1ef18 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 2 May 2026 23:01:00 -0400 Subject: [PATCH 44/88] Simpler const fix for fill_metadata_from_path() Make sure the output of strchr(const*) is assinged to a const* This way the filename argument can stay const Change-Id: Ie46be936491eb62ba2a7e729b8cd7881e205bba7 --- lib/rbcodec/metadata/metadata.c | 8 ++++---- lib/skin_parser/skin_debug.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c index fafd227c14..00c6bfd581 100644 --- a/lib/rbcodec/metadata/metadata.c +++ b/lib/rbcodec/metadata/metadata.c @@ -381,7 +381,7 @@ bool format_buffers_with_offset(int afmt) /* Simple file type probing by looking at the filename extension. */ unsigned int probe_file_format(const char *filename) { - char *suffix; + const char *suffix; unsigned int i; suffix = strrchr(filename, '.'); @@ -418,7 +418,7 @@ unsigned int probe_file_format(const char *filename) * file that would prevent playback. supply a filedescriptor <0 and the file will be opened * and closed automatically within the get_metadata call * get_metadata_ex allows flags to change the way get_metadata behaves - * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer + * METADATA_EXCLUDE_ID3_PATH won't copy filename path to the id3 path buffer * METADATA_CLOSE_FD_ON_EXIT closes the open filedescriptor on exit */ bool get_metadata_ex(struct mp3entry* id3, int fd, const char* trackname, int flags) @@ -554,8 +554,8 @@ void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) wipe_mp3entry(id3); /* Find the filename portion of the path */ - p = strrchr(trackname, '/'); - strlcpy(id3->id3v2buf, p ? ++p : id3->path, ID3V2_BUF_SIZE); + const char *pt = strrchr(trackname, '/'); + strlcpy(id3->id3v2buf, pt ? ++pt : id3->path, ID3V2_BUF_SIZE); /* Get the format from the extension and trim it off */ p = strrchr(id3->id3v2buf, '.'); diff --git a/lib/skin_parser/skin_debug.c b/lib/skin_parser/skin_debug.c index b9e57d9573..44fba90445 100644 --- a/lib/skin_parser/skin_debug.c +++ b/lib/skin_parser/skin_debug.c @@ -317,7 +317,7 @@ void skin_error_format_message(void) char text[128]; if (!error_line_start) return; - char* line_end = strchr(error_line_start, '\n'); + const char* line_end = strchr(error_line_start, '\n'); int len = MIN(line_end - error_line_start, 80); if (!line_end) len = strlen(error_line_start); From 81fcb10f8fa92c5d01eda887e0ecde5682c240f9 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 8 Nov 2025 18:51:43 -0500 Subject: [PATCH 45/88] metadata: Normalize all metadata to Unicode NFC form * Standalone database tool * Simulator builds * Target firmware (Hosted and Native, for all >2MB targets) Change-Id: Ia7361affc2fc6a08e73c31ecc9ef3a4008c2415d --- apps/features.txt | 4 ++++ firmware/common/unicode.c | 32 ++++++++++++++++++++++++++++++-- firmware/include/rbunicode.h | 5 +++-- lib/rbcodec/metadata/metadata.c | 20 ++++++++++++++++++++ tools/database/database.make | 4 +++- tools/root.make | 8 ++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/apps/features.txt b/apps/features.txt index c9dea50abd..2d3ad26a73 100644 --- a/apps/features.txt +++ b/apps/features.txt @@ -316,3 +316,7 @@ lto #if defined(USB_ENABLE_AUDIO) usbdac #endif + +#if defined(UTF8PROC_EXPORTS) +utf8proc +#endif diff --git a/firmware/common/unicode.c b/firmware/common/unicode.c index c1e187a709..f7d44e1ee2 100644 --- a/firmware/common/unicode.c +++ b/firmware/common/unicode.c @@ -44,6 +44,10 @@ #define O_NOISODECODE 0 #endif +#ifdef UTF8PROC_EXPORTS +#include "utf8proc.h" +#endif + #define getle16(p) (p[0] | (p[1] << 8)) #define getbe16(p) ((p[0] << 8) | p[1]) @@ -59,8 +63,9 @@ #define open_noiso_internal open #endif /* !APPLICATION */ -#if 0 /* not needed just now (will probably end up a spinlock) */ #include "mutex.h" + +#if 0 /* not needed just now (will probably end up a spinlock) */ static struct mutex cp_mutex SHAREDBSS_ATTR; #define cp_lock_init() mutex_init(&cp_mutex) #define cp_lock_enter() mutex_lock(&cp_mutex) @@ -651,9 +656,32 @@ const char * get_codepage_name(int cp) return cp_info[cp].name; } -#if 0 /* not needed just now */ +#ifdef UTF8PROC_EXPORTS +static utf8proc_int32_t normbuf[2048]; +static struct mutex norm_mutex SHAREDBSS_ATTR; + +void utf8_normalize(char *string) +{ + utf8proc_ssize_t result, orig; + + if (!string || !*string) + return; + + mutex_lock(&norm_mutex); + orig = strlen(string); + result = utf8proc_decompose(string, 0, normbuf, sizeof(normbuf)/4 -1, UTF8PROC_NULLTERM); + if (result > 0) { + result = utf8proc_reencode(normbuf, result, UTF8PROC_NULLTERM|UTF8PROC_COMPOSE|UTF8PROC_STABLE); + if (result > 0 && result <= orig && strcmp((char*)normbuf, string)) + strcpy(string, (char*)normbuf); + } + mutex_unlock(&norm_mutex); +} + void unicode_init(void) { cp_lock_init(); + mutex_init(&norm_mutex); } + #endif diff --git a/firmware/include/rbunicode.h b/firmware/include/rbunicode.h index 48ca1b2583..d243c08596 100644 --- a/firmware/include/rbunicode.h +++ b/firmware/include/rbunicode.h @@ -76,8 +76,9 @@ const char *get_current_codepage_name_linux(void); #endif #endif /* APPLICATION */ -#if 0 /* not needed just now */ -void unicode_init(void); +#ifdef UTF8PROC_EXPORTS +void utf8_normalize(char *string); +void unicode_init(void) INIT_ATTR; #else #define unicode_init() do {} while (0) #endif diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c index 00c6bfd581..16af58a291 100644 --- a/lib/rbcodec/metadata/metadata.c +++ b/lib/rbcodec/metadata/metadata.c @@ -27,6 +27,10 @@ #include "logf.h" #include "metadata.h" +#ifdef UTF8PROC_EXPORTS +#include "rbunicode.h" +#endif + #include "metadata_parsers.h" /* For trailing tag stripping and base audio data types */ @@ -471,6 +475,22 @@ bool get_metadata_ex(struct mp3entry* id3, int fd, const char* trackname, int fl wipe_mp3entry(id3); /* ensure the mp3entry is clear */ } +#ifdef UTF8PROC_EXPORTS + if (success) { + utf8_normalize(id3->title); + utf8_normalize(id3->artist); + utf8_normalize(id3->album); + utf8_normalize(id3->genre_string); + utf8_normalize(id3->disc_string); + utf8_normalize(id3->track_string); + utf8_normalize(id3->year_string); + utf8_normalize(id3->composer); + utf8_normalize(id3->comment); + utf8_normalize(id3->albumartist); + utf8_normalize(id3->grouping); + } +#endif + if ((flags & METADATA_CLOSE_FD_ON_EXIT)) close(fd); else diff --git a/tools/database/database.make b/tools/database/database.make index 79f84b4bee..c4b2d85f2a 100644 --- a/tools/database/database.make +++ b/tools/database/database.make @@ -9,6 +9,8 @@ GCCOPTS += -g -DDEBUG -D__PCTOOL__ -DDBTOOL +include $(ROOTDIR)/lib/utf8proc/utf8proc.make + METADATAS := $(wildcard $(ROOTDIR)/lib/rbcodec/metadata/*.c) DATABASE_SRC = $(call preprocess, $(TOOLSDIR)/database/SOURCES) $(METADATAS) @@ -28,7 +30,7 @@ INCLUDES += -I$(ROOTDIR)/apps/gui \ -I$(APPSDIR) \ -I$(BUILDDIR) -OTHERLIBS := $(FIXEDPOINTLIB) +OTHERLIBS := $(FIXEDPOINTLIB) $(LIBUTF8PROC) .SECONDEXPANSION: # $$(OBJ) is not populated until after this diff --git a/tools/root.make b/tools/root.make index 523eb5d180..b1659e7da4 100644 --- a/tools/root.make +++ b/tools/root.make @@ -128,6 +128,14 @@ else # core include $(APPSDIR)/apps.make include $(ROOTDIR)/lib/rbcodec/rbcodec.make + # bootloaders don't get utf8proc + ifeq (,$(findstring checkwps,$(APP_TYPE))) + IS_GREATER := $(shell [ $(MEMORYSIZE) -gt 2 ] && echo true || echo false) + ifeq ($(IS_GREATER),true) + include $(ROOTDIR)/lib/utf8proc/utf8proc.make + endif + endif + ifeq ($(ENABLEDPLUGINS),yes) include $(APPSDIR)/plugins/bitmaps/pluginbitmaps.make include $(APPSDIR)/plugins/plugins.make From 4f2c918f28c22eaebdc6ccf8f0d6b1f9513d4471 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 3 May 2026 00:48:31 +0100 Subject: [PATCH 46/88] mpegplayer: disable ARM assembly code for Cortex-M Change-Id: I6fe8a359ef6d6160918239bf5c45a3034e52356b --- apps/plugins/mpegplayer/SOURCES | 2 +- apps/plugins/mpegplayer/libmpeg2/idct.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/plugins/mpegplayer/SOURCES b/apps/plugins/mpegplayer/SOURCES index 3fc079dfbd..c1d0afe4c2 100644 --- a/apps/plugins/mpegplayer/SOURCES +++ b/apps/plugins/mpegplayer/SOURCES @@ -8,7 +8,7 @@ libmpeg2/slice.c libmpeg2/idct_coldfire.S libmpeg2/motion_comp_coldfire_c.c libmpeg2/motion_comp_coldfire_s.S -#elif defined CPU_ARM +#elif defined CPU_ARM_CLASSIC #if ARM_ARCH >= 6 libmpeg2/idct_armv6.S #else diff --git a/apps/plugins/mpegplayer/libmpeg2/idct.c b/apps/plugins/mpegplayer/libmpeg2/idct.c index 7f0b9a3c12..e202da31d2 100644 --- a/apps/plugins/mpegplayer/libmpeg2/idct.c +++ b/apps/plugins/mpegplayer/libmpeg2/idct.c @@ -33,7 +33,7 @@ #include "attributes.h" #include "mpeg2_internal.h" -#if defined(CPU_COLDFIRE) || defined (CPU_ARM) +#if defined(CPU_COLDFIRE) || defined (CPU_ARM_CLASSIC) #define IDCT_ASM #endif From 30ad7f4ee84f4d95ddf3d437871cb1bd792f0d17 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 3 May 2026 08:07:51 -0400 Subject: [PATCH 47/88] manual: Mention [lack of] unicode normalization in the manual Change-Id: Icdb19a47367fbe7b2fd32679885a26669a799612 --- manual/appendix/file_formats.tex | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/manual/appendix/file_formats.tex b/manual/appendix/file_formats.tex index cb37d9b532..32e4ddbd8b 100644 --- a/manual/appendix/file_formats.tex +++ b/manual/appendix/file_formats.tex @@ -89,7 +89,7 @@ & \fname{.aac}, \fname{.m4a}, \fname{.m4b}, \fname{.mp4}, \fname{.rm}, \fname{.ra}, \fname{.rmvb} \opt{codec_aac_he}{ & Supports AAC-LC, -HEv1, and -HEv2 profiles\\} - \nopt{codec_aac_he}{ % low memory (CODEC_SIZE <= 512 KB) or slow cpu targets + \nopt{codec_aac_he}{ % low memory (CODEC_SIZE <= 512 KB) or slow cpu targets & Supports AAC-LC profile\\} MPEG audio & \fname{.mpa}, \fname{.mpga}, \fname{.mp1}, \fname{.mp2}, \fname{.mp3} @@ -146,7 +146,7 @@ & Linear PCM 8/16/24/32 bit, IEEE float 32/64 bit, ITU-T G.711 a-law/$\mu$-law\\ Free Lossless Audio & \fname{.flac} - & Multichannel and/or high-resolution files will be downmixed to 16-bit stereo. + & Multichannel and/or high-resolution files will be downmixed to 16-bit stereo. \opt{ipod,iriverh10,sansaclip,sansam200v4,sansac200v2,iriverh100,iriverh300,iaudiox5,iaudiom5,iaudiom3,mpiohd200,mpiphd300,cowond2}{ \nopt{ipodnano2g,ipod6g}{Due to resource constraints, files with large block sizes are not supported, and realtime playback may not be possible where downmixing is necessary.}} \\ @@ -377,4 +377,14 @@ \item The maximum size of each metadata item (e.g. Artists) is limited to 90 bytes. } + \opt{utf8proc}{ + \item All textual metadata is normalized into most compact Unicode + NFC form. This simplifies font/glpyh rendering, and makes + it possible to compare/sort strings that use different + normalization forms. + } + \nopt{utf8proc}{ + \item Due to RAM limitations, the \playertype does not process or + normalize textual metadata in any way. + } \end{enumerate} From d839cab05a02306e121e5ec3302a4b7a490fcb6f Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 3 May 2026 08:08:16 -0400 Subject: [PATCH 48/88] manual: Correct a minor error in the countdown timer documentation Change-Id: I5105a2ebf51e030c9d58a234c521b473141e71f7 --- manual/plugins/countdown_timer.tex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manual/plugins/countdown_timer.tex b/manual/plugins/countdown_timer.tex index c022203a92..8dc0a27bc7 100644 --- a/manual/plugins/countdown_timer.tex +++ b/manual/plugins/countdown_timer.tex @@ -1,5 +1,4 @@ -% $Id$ % -\subsection{Countdown_timer} +\subsection{Countdown Timer} A countdown timer. Set the desired duration, start the countdown, and the \dap{} will alert you with a beep sequence when time is up. The timer @@ -8,7 +7,7 @@ defaults to 10 minutes on launch. \subsubsection{Setting the timer} When the plugin starts, the display shows the time to count down from with -the active field highlighted in brackets (e.g.\ \texttt{[10]:00}). +the active field highlighted in brackets (e.g. \texttt{[10]:00}). \begin{btnmap} \opt{scrollwheel}{\PluginScrollFwd{} / \PluginScrollBack{} or } From 30adfbf5c9c55a1fa1aaabf9081711fa7d92b555 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 3 May 2026 01:26:40 +0100 Subject: [PATCH 49/88] puzzles: fix possible crash due to non-NULL terminated list Change-Id: Id0cadfd6d2d88e8cd27a34d042403ec54e8aca41 --- apps/plugins/puzzles/rockbox.c | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index 172207b63b..452f647954 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -3066,6 +3066,7 @@ static void tune_input(const char *name) "Signpost", "Slide", "Untangle", + NULL }; input_settings.sticky_mouse = string_in_list(name, sticky_mouse_games); From 2053bba56158894bd86e41a645a86764e16d6a3c Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 3 May 2026 12:57:21 +0100 Subject: [PATCH 50/88] makefiles: fix duke3d overlay not using objcopy_plugin This should use $(call objcopy_plugin) in order to generate the correct binary format on platforms where the plugin binary format differs from the main binary format (ie. STM32). Change-Id: I027cc74e3c5d55b9a3538f4f16c3fd5ece25a4b5 --- apps/plugins/sdl/sdl.make | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/sdl/sdl.make b/apps/plugins/sdl/sdl.make index 37b98fa2ac..f04f727aa7 100644 --- a/apps/plugins/sdl/sdl.make +++ b/apps/plugins/sdl/sdl.make @@ -78,7 +78,7 @@ $(SDL_OBJDIR)/duke3d.ovl: $(SDL_OBJ) $(DUKE3D_OBJ) $(TLSFLIB) $(DUKE3D_OUTLDS) $(filter %.o, $^) \ $(filter %.a, $+) \ -lgcc -T$(DUKE3D_OUTLDS) $(SDL_OVLFLAGS) - $(call PRINTS,LD $(@F))$(call objcopy,$(basename $@).elf,$@) + $(call PRINTS,LD $(@F))$(call objcopy_plugin,$(basename $@).elf,$@) # Wolf3D From 790a8aa56073ddbe9e4c05d9c544d4c2076eca95 Mon Sep 17 00:00:00 2001 From: mojyack Date: Sat, 25 Apr 2026 01:01:01 +0900 Subject: [PATCH 51/88] plugins: chessbox: respect GLOBAL_LDOPTS Change-Id: Ia9b20c24f91a7fd9fa7b0a73e352199aeaf6dd05 --- apps/plugins/chessbox/chessbox.make | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/chessbox/chessbox.make b/apps/plugins/chessbox/chessbox.make index c4b1b495e1..7fa0598eb9 100644 --- a/apps/plugins/chessbox/chessbox.make +++ b/apps/plugins/chessbox/chessbox.make @@ -19,7 +19,7 @@ ifeq ($(findstring YES, $(call preprocess, $(APPSDIR)/plugins/BUILD_OVERLAY)), Y ### lowmem targets ROCKS += $(CHESSBOX_OBJDIR)/chessbox.ovl CHESSBOX_OUTLDS = $(CHESSBOX_OBJDIR)/chessbox.link - CHESSBOX_OVLFLAGS = -T$(CHESSBOX_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map + CHESSBOX_OVLFLAGS = -T$(CHESSBOX_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map $(GLOBAL_LDOPTS) else ROCKS += $(CHESSBOX_OBJDIR)/chessbox.rock endif From ae8013405af611e42785fc7781cb0ddd260b43e9 Mon Sep 17 00:00:00 2001 From: mojyack Date: Sat, 25 Apr 2026 01:01:10 +0900 Subject: [PATCH 52/88] plugins: rockboy: respect GLOBAL_LDOPTS Change-Id: I5224e39b41bda71506ee99d9df79b7b19f7e29cb --- apps/plugins/rockboy/rockboy.make | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/rockboy/rockboy.make b/apps/plugins/rockboy/rockboy.make index ba8ffe2390..190f1ac59f 100644 --- a/apps/plugins/rockboy/rockboy.make +++ b/apps/plugins/rockboy/rockboy.make @@ -19,7 +19,7 @@ ifeq ($(findstring YES, $(call preprocess, $(APPSDIR)/plugins/BUILD_OVERLAY)), Y ## lowmem targets ROCKS += $(ROCKBOY_OBJDIR)/rockboy.ovl ROCKBOY_OUTLDS = $(ROCKBOY_OBJDIR)/rockboy.link - ROCKBOY_OVLFLAGS = -T$(ROCKBOY_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map + ROCKBOY_OVLFLAGS = -T$(ROCKBOY_OUTLDS) -Wl,--gc-sections -Wl,-Map,$(basename $@).map $(GLOBAL_LDOPTS) else ROCKS += $(ROCKBOY_OBJDIR)/rockboy.rock endif From 6d699f08f463cd9ab9c1e66421f8997fade05588 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sun, 3 May 2026 18:09:44 +0200 Subject: [PATCH 53/88] plugins: imageviewer: fix incomplete previous commits missed in commit 2690418: grayscale targets need to have access to the Display Options menu now, since it contains the hide_info option missed in commit f4dc4d8: "resizing" message for bmp files wasn't hidden even with hide_info enabled Change-Id: I1a73e3816305ab6f032fc226d79f09df0d9aa96b --- apps/plugins/imageviewer/bmp/bmp.c | 3 ++- apps/plugins/imageviewer/imageviewer.c | 20 +++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/plugins/imageviewer/bmp/bmp.c b/apps/plugins/imageviewer/bmp/bmp.c index 3911917fec..8ad822892c 100644 --- a/apps/plugins/imageviewer/bmp/bmp.c +++ b/apps/plugins/imageviewer/bmp/bmp.c @@ -276,7 +276,8 @@ static int get_image(struct image_info *info, int frame, int ds) buf_images += size; buf_images_size -= size; - if (!iv->running_slideshow) + if (!iv->settings->hide_info && + !iv->running_slideshow) { rb->lcd_putsf(0, 3, "resizing %d*%d", info->width, info->height); rb->lcd_update(); diff --git a/apps/plugins/imageviewer/imageviewer.c b/apps/plugins/imageviewer/imageviewer.c index b87a206a33..45836134fe 100644 --- a/apps/plugins/imageviewer/imageviewer.c +++ b/apps/plugins/imageviewer/imageviewer.c @@ -246,28 +246,32 @@ static bool set_option_dithering(void) return false; } +MENUITEM_FUNCTION(grayscale_item, 0, ID2P(LANG_GRAYSCALE), + set_option_grayscale, NULL, Icon_NOICON); +MENUITEM_FUNCTION(dithering_item, 0, ID2P(LANG_DITHERING), + set_option_dithering, NULL, Icon_NOICON); + +#endif /* HAVE_LCD_COLOR */ + static bool set_option_hide_info(void) { rb->set_bool(rb->str(LANG_HIDE_INFO), &settings.hide_info); return false; } -MENUITEM_FUNCTION(grayscale_item, 0, ID2P(LANG_GRAYSCALE), - set_option_grayscale, NULL, Icon_NOICON); -MENUITEM_FUNCTION(dithering_item, 0, ID2P(LANG_DITHERING), - set_option_dithering, NULL, Icon_NOICON); MENUITEM_FUNCTION(hide_info_item, 0, ID2P(LANG_HIDE_INFO), set_option_hide_info, NULL, Icon_NOICON); MAKE_MENU(display_menu, ID2P(LANG_MENU_DISPLAY_OPTIONS), NULL, Icon_NOICON, +#ifdef HAVE_LCD_COLOR &grayscale_item, &dithering_item, +#endif /* HAVE_LCD_COLOR */ &hide_info_item); static void display_options(void) { rb->do_menu(&display_menu, NULL, NULL, false); } -#endif /* HAVE_LCD_COLOR */ static int show_menu(void) /* return 1 to quit */ { @@ -281,9 +285,7 @@ static int show_menu(void) /* return 1 to quit */ #ifdef USE_PLUG_BUF MIID_SHOW_PLAYBACK_MENU, #endif -#ifdef HAVE_LCD_COLOR MIID_DISPLAY_OPTIONS, -#endif MIID_QUIT, }; @@ -294,9 +296,7 @@ static int show_menu(void) /* return 1 to quit */ #ifdef USE_PLUG_BUF ID2P(LANG_PLAYBACK_CONTROL), #endif -#ifdef HAVE_LCD_COLOR ID2P(LANG_MENU_DISPLAY_OPTIONS), -#endif ID2P(LANG_MENU_QUIT)); static const struct opt_items slideshow[2] = { @@ -332,11 +332,9 @@ static int show_menu(void) /* return 1 to quit */ } break; #endif -#ifdef HAVE_LCD_COLOR case MIID_DISPLAY_OPTIONS: display_options(); break; -#endif case MIID_QUIT: return 1; break; From 2b9e4a8d70b7a4c1282f94270a296a43676a984f Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 17:21:18 +0900 Subject: [PATCH 54/88] config: define USB_ENABLE_IAP iap is only enabled for idevices. it will not work without apple vendor id anyway. Change-Id: I1696dbc8a2304fc5eecc5432b4c52e25801c468e --- firmware/export/config.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firmware/export/config.h b/firmware/export/config.h index 4cd178e405..bcea581141 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -1396,6 +1396,10 @@ Lyre prototype 1 */ #define USB_ENABLE_AUDIO #endif +#if defined(USB_HAS_INTERRUPT) && defined(USB_HAS_ISOCHRONOUS) && USB_VENDOR_ID == 0x05ac +#define USB_ENABLE_IAP +#endif + #endif /* BOOTLOADER */ #endif /* HAVE_USBSTACK */ From 3bb656625b94b7515c7577d58deaae07de159101 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 20:45:16 +0900 Subject: [PATCH 55/88] usb: add usb iAP driver add class driver source files. also register iap audio sink. usbstack/iap/libiap directory is imported from libiap. Change-Id: I776c5caec33fe9efadc448e2e3b37d500bf19c9f --- firmware/SOURCES | 14 + firmware/export/config.h | 4 + firmware/export/iap-usb.h | 39 + firmware/export/pcm_sink.h | 5 + firmware/export/usb.h | 3 + firmware/pcm.c | 7 + firmware/usb.c | 6 + firmware/usbstack/iap/audio.c | 268 ++++ firmware/usbstack/iap/audio.h | 28 + firmware/usbstack/iap/buffer.c | 47 + firmware/usbstack/iap/buffer.h | 30 + firmware/usbstack/iap/debug.c | 89 ++ firmware/usbstack/iap/debug.h | 42 + firmware/usbstack/iap/libiap-sync.sh | 17 + firmware/usbstack/iap/libiap/HEAD | 1 + firmware/usbstack/iap/libiap/LICENSE | 21 + firmware/usbstack/iap/libiap/README.rockbox | 5 + firmware/usbstack/iap/libiap/bool.h | 5 + firmware/usbstack/iap/libiap/constants.h | 11 + firmware/usbstack/iap/libiap/context.h | 107 ++ firmware/usbstack/iap/libiap/datetime.h | 11 + firmware/usbstack/iap/libiap/debug.c | 1052 +++++++++++++ firmware/usbstack/iap/libiap/endian.h | 28 + .../usbstack/iap/libiap/fid-token-values.c | 141 ++ firmware/usbstack/iap/libiap/hid.c | 181 +++ firmware/usbstack/iap/libiap/iap.c | 1361 +++++++++++++++++ firmware/usbstack/iap/libiap/iap.h | 55 + firmware/usbstack/iap/libiap/macros.h | 27 + firmware/usbstack/iap/libiap/notification.c | 200 +++ firmware/usbstack/iap/libiap/notification.h | 20 + firmware/usbstack/iap/libiap/pack-util.h | 41 + firmware/usbstack/iap/libiap/platform.h | 116 ++ firmware/usbstack/iap/libiap/span.c | 46 + firmware/usbstack/iap/libiap/span.h | 32 + firmware/usbstack/iap/libiap/spec/hid.h | 14 + firmware/usbstack/iap/libiap/spec/iap.h | 54 + .../libiap/spec/lingoes/accessory-equalizer.h | 18 + .../accessory-equalizer/current-eq-index.h | 10 + .../accessory-equalizer/eq-index-name.h | 11 + .../accessory-equalizer/eq-setting-count.h | 6 + .../iap/libiap/spec/lingoes/accessory-power.h | 8 + .../iap/libiap/spec/lingoes/digital-audio.h | 16 + .../accessory-sample-rate-caps.h | 8 + .../lingoes/digital-audio/set-video-delay.h | 6 + .../track-new-audio-attributes.h | 8 + .../iap/libiap/spec/lingoes/display-remote.h | 56 + .../lingoes/display-remote/artwork-formats.h | 21 + .../display-remote/current-eq-profile-index.h | 11 + .../lingoes/display-remote/genius-playlist.h | 10 + .../display-remote/indexed-eq-profile-name.h | 12 + .../indexed-playing-track-info.h | 85 + .../spec/lingoes/display-remote/ipod-state.h | 204 +++ .../lingoes/display-remote/num-eq-profiles.h | 6 + .../display-remote/num-playing-tracks.h | 6 + .../spec/lingoes/display-remote/play-status.h | 9 + .../display-remote/power-battery-state.h | 7 + .../display-remote/remote-event-status.h | 6 + .../set-current-playing-track.h | 6 + .../display-remote/track-artwork-data.h | 26 + .../display-remote/track-artwork-times.h | 15 + .../libiap/spec/lingoes/extended-interface.h | 109 ++ .../lingoes/extended-interface/artwork-data.h | 58 + .../extended-interface/artwork-times.h | 48 + .../extended-interface/audiobook-speed.h | 11 + .../color-display-image-limits.h | 16 + .../current-playing-track-chapter.h | 30 + .../current-playing-track-index.h | 6 + .../lingoes/extended-interface/database.h | 42 + .../extended-interface/db-itunes-info.h | 42 + .../db-selection-hierarchy.h | 13 + .../extended-interface/genius-playlist.h | 23 + .../indexed-playing-track-info.h | 81 + .../indexed-playing-track-string.h | 20 + .../lingoes/extended-interface/ipod-ack.h | 7 + .../mono-display-image-limits.h | 10 + .../lingoes/extended-interface/play-control.h | 25 + .../play-current-selection.h | 8 + .../play-status-change-notification.h | 137 ++ .../lingoes/extended-interface/play-status.h | 15 + .../extended-interface/playlist-info.h | 18 + .../spec/lingoes/extended-interface/repeat.h | 13 + .../extended-interface/set-display-image.h | 18 + .../spec/lingoes/extended-interface/shuffle.h | 13 + .../lingoes/extended-interface/track-info.h | 59 + .../lingoes/extended-interface/uid-list.h | 15 + .../iap/libiap/spec/lingoes/general.h | 112 ++ .../iap/libiap/spec/lingoes/general/acc-ack.h | 7 + .../spec/lingoes/general/acc-auth-info.h | 27 + .../spec/lingoes/general/acc-auth-sig.h | 22 + .../libiap/spec/lingoes/general/acc-info.h | 83 + .../lingoes/general/acc-status-notification.h | 18 + .../spec/lingoes/general/cancel-command.h | 8 + .../spec/lingoes/general/data-session.h | 11 + .../spec/lingoes/general/data-transfer.h | 12 + .../libiap/spec/lingoes/general/end-idps.h | 33 + .../spec/lingoes/general/event-notification.h | 31 + .../lingoes/general/extended-interface-mode.h | 8 + .../spec/lingoes/general/fid-token-values.h | 58 + .../general/fid-token-values/acc-caps.h | 31 + .../acc-digital-audio-sample-rates.h | 16 + .../acc-digital-audio-video-delay.h | 16 + .../general/fid-token-values/acc-info.h | 33 + .../fid-token-values/bundle-seed-id-pref.h | 16 + .../fid-token-values/ea-protocol-metadata.h | 23 + .../general/fid-token-values/ea-protocol.h | 20 + .../general/fid-token-values/identify.h | 28 + .../fid-token-values/ipod-preference.h | 21 + .../fid-token-values/microphone-caps.h | 24 + .../general/fid-token-values/screen-info.h | 27 + .../lingoes/general/identify-device-lingoes.h | 30 + .../libiap/spec/lingoes/general/identify.h | 15 + .../libiap/spec/lingoes/general/ipod-ack.h | 57 + .../spec/lingoes/general/ipod-auth-info.h | 14 + .../spec/lingoes/general/ipod-auth-sig.h | 17 + .../libiap/spec/lingoes/general/ipod-name.h | 8 + .../spec/lingoes/general/ipod-notification.h | 120 ++ .../lingoes/general/ipod-options-for-lingo.h | 114 ++ .../spec/lingoes/general/ipod-options.h | 11 + .../spec/lingoes/general/ipod-preferences.h | 95 ++ .../spec/lingoes/general/ipod-serial-num.h | 6 + .../lingoes/general/ipod-software-version.h | 8 + .../lingoes/general/lingo-protocol-version.h | 12 + .../spec/lingoes/general/localization-info.h | 16 + .../general/notify-ipod-state-change.h | 13 + .../general/now-playing-app-bundle-name.h | 8 + .../general/request-application-launch.h | 9 + .../lingoes/general/set-available-current.h | 6 + .../set-internal-battery-charging-state.h | 11 + .../general/transport-max-payload-size.h | 6 + .../iap/libiap/spec/lingoes/general/ui-mode.h | 13 + .../lingoes/general/wifi-connection-info.h | 24 + .../iap/libiap/spec/lingoes/ipod-out.h | 14 + .../ipod-out/accessory-state-change-event.h | 18 + .../spec/lingoes/ipod-out/ipod-out-options.h | 29 + .../iap/libiap/spec/lingoes/location.h | 19 + .../iap/libiap/spec/lingoes/microphone.h | 22 + .../libiap/spec/lingoes/microphone/acc-caps.h | 14 + .../libiap/spec/lingoes/microphone/acc-ctrl.h | 22 + .../lingoes/microphone/ipod-mode-change.h | 14 + .../iap/libiap/spec/lingoes/rf-tuner.h | 69 + .../libiap/spec/lingoes/rf-tuner/acc-ack.h | 13 + .../libiap/spec/lingoes/rf-tuner/hd-data.h | 42 + .../libiap/spec/lingoes/rf-tuner/hd-program.h | 17 + .../libiap/spec/lingoes/rf-tuner/rds-data.h | 18 + .../spec/lingoes/rf-tuner/rds-notify-mask.h | 12 + .../spec/lingoes/rf-tuner/rds-ready-status.h | 18 + .../lingoes/rf-tuner/status-notification.h | 16 + .../libiap/spec/lingoes/rf-tuner/tuner-band.h | 19 + .../libiap/spec/lingoes/rf-tuner/tuner-caps.h | 42 + .../libiap/spec/lingoes/rf-tuner/tuner-ctrl.h | 18 + .../libiap/spec/lingoes/rf-tuner/tuner-freq.h | 13 + .../libiap/spec/lingoes/rf-tuner/tuner-mode.h | 22 + .../spec/lingoes/rf-tuner/tuner-seek-rssi.h | 12 + .../libiap/spec/lingoes/rf-tuner/tuner-seek.h | 33 + .../spec/lingoes/rf-tuner/tuner-status.h | 17 + .../iap/libiap/spec/lingoes/simple-remote.h | 39 + .../lingoes/simple-remote/button-status.h | 98 ++ .../simple-remote/camera-button-status.h | 11 + .../current-voice-over-item-property.h | 72 + .../libiap/spec/lingoes/simple-remote/hid.h | 75 + .../simple-remote/ipod-out-button-status.h | 26 + .../simple-remote/radio-button-status.h | 11 + .../simple-remote/rotation-input-status.h | 33 + .../simple-remote/set-voice-over-context.h | 17 + .../lingoes/simple-remote/voice-over-event.h | 85 + .../simple-remote/voice-over-parameter.h | 28 + .../libiap/spec/lingoes/sound-check-state.h | 11 + .../usbstack/iap/libiap/spec/lingoes/sports.h | 25 + .../spec/lingoes/sports/accessory-caps.h | 11 + .../spec/lingoes/sports/accessory-version.h | 7 + .../libiap/spec/lingoes/sports/ipod-caps.h | 12 + .../libiap/spec/lingoes/sports/user-data.h | 73 + .../libiap/spec/lingoes/sports/user-index.h | 6 + .../iap/libiap/spec/lingoes/storage.h | 27 + .../iap/libiap/spec/lingoes/storage/acc-ack.h | 8 + .../spec/lingoes/storage/accessory-caps.h | 11 + .../libiap/spec/lingoes/storage/ipod-ack.h | 10 + .../libiap/spec/lingoes/storage/ipod-caps.h | 13 + .../spec/lingoes/storage/ipod-file-handle.h | 30 + .../spec/lingoes/storage/ipod-free-space.h | 8 + .../lingoes/storage/write-ipod-file-data.h | 10 + .../iap/libiap/spec/lingoes/usb-host-mode.h | 14 + .../spec/lingoes/usb-host-mode/usb-mode.h | 22 + firmware/usbstack/iap/libiap/time.h | 11 + firmware/usbstack/iap/libiap/unaligned.h | 7 + firmware/usbstack/iap/macros.h | 44 + firmware/usbstack/iap/notification.c | 111 ++ firmware/usbstack/iap/platform-macros.h | 35 + firmware/usbstack/iap/platform.c | 482 ++++++ firmware/usbstack/iap/platform.h | 49 + firmware/usbstack/usb_core.c | 34 +- firmware/usbstack/usb_iap.c | 679 ++++++++ firmware/usbstack/usb_iap.h | 39 + 193 files changed, 9650 insertions(+), 1 deletion(-) create mode 100644 firmware/export/iap-usb.h create mode 100644 firmware/usbstack/iap/audio.c create mode 100644 firmware/usbstack/iap/audio.h create mode 100644 firmware/usbstack/iap/buffer.c create mode 100644 firmware/usbstack/iap/buffer.h create mode 100644 firmware/usbstack/iap/debug.c create mode 100644 firmware/usbstack/iap/debug.h create mode 100755 firmware/usbstack/iap/libiap-sync.sh create mode 100644 firmware/usbstack/iap/libiap/HEAD create mode 100644 firmware/usbstack/iap/libiap/LICENSE create mode 100644 firmware/usbstack/iap/libiap/README.rockbox create mode 100644 firmware/usbstack/iap/libiap/bool.h create mode 100644 firmware/usbstack/iap/libiap/constants.h create mode 100644 firmware/usbstack/iap/libiap/context.h create mode 100644 firmware/usbstack/iap/libiap/datetime.h create mode 100644 firmware/usbstack/iap/libiap/debug.c create mode 100644 firmware/usbstack/iap/libiap/endian.h create mode 100644 firmware/usbstack/iap/libiap/fid-token-values.c create mode 100644 firmware/usbstack/iap/libiap/hid.c create mode 100644 firmware/usbstack/iap/libiap/iap.c create mode 100644 firmware/usbstack/iap/libiap/iap.h create mode 100644 firmware/usbstack/iap/libiap/macros.h create mode 100644 firmware/usbstack/iap/libiap/notification.c create mode 100644 firmware/usbstack/iap/libiap/notification.h create mode 100644 firmware/usbstack/iap/libiap/pack-util.h create mode 100644 firmware/usbstack/iap/libiap/platform.h create mode 100644 firmware/usbstack/iap/libiap/span.c create mode 100644 firmware/usbstack/iap/libiap/span.h create mode 100644 firmware/usbstack/iap/libiap/spec/hid.h create mode 100644 firmware/usbstack/iap/libiap/spec/iap.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/current-eq-index.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-index-name.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-setting-count.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/accessory-power.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/digital-audio.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/accessory-sample-rate-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/set-video-delay.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/track-new-audio-attributes.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/artwork-formats.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/current-eq-profile-index.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/genius-playlist.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-eq-profile-name.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-playing-track-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/ipod-state.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-eq-profiles.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-playing-tracks.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/play-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/power-battery-state.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/remote-event-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/set-current-playing-track.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-times.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-times.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/audiobook-speed.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/color-display-image-limits.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-chapter.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-index.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/database.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-itunes-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-selection-hierarchy.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/genius-playlist.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-string.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/ipod-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/mono-display-image-limits.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-control.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-current-selection.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status-change-notification.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/playlist-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/repeat.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/set-display-image.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/shuffle.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/track-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/uid-list.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/acc-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-sig.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/acc-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/acc-status-notification.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/cancel-command.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/data-session.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/data-transfer.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/end-idps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/event-notification.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/extended-interface-mode.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-sample-rates.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-video-delay.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/bundle-seed-id-pref.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol-metadata.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/identify.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ipod-preference.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/microphone-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/screen-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/identify-device-lingoes.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/identify.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-sig.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-name.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-notification.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options-for-lingo.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-preferences.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-serial-num.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-software-version.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/lingo-protocol-version.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/localization-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/notify-ipod-state-change.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/now-playing-app-bundle-name.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/request-application-launch.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/set-available-current.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/set-internal-battery-charging-state.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/transport-max-payload-size.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/ui-mode.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/general/wifi-connection-info.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/ipod-out.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/accessory-state-change-event.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/ipod-out-options.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/location.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/microphone.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-ctrl.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/microphone/ipod-mode-change.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/acc-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-program.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-notify-mask.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-ready-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/status-notification.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-band.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-ctrl.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-freq.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-mode.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek-rssi.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/button-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/camera-button-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/current-voice-over-item-property.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/hid.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/ipod-out-button-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/radio-button-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/rotation-input-status.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/set-voice-over-context.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-event.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-parameter.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sound-check-state.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-version.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports/ipod-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports/user-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/sports/user-index.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/acc-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/accessory-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-ack.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-caps.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-file-handle.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-free-space.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/storage/write-ipod-file-data.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode.h create mode 100644 firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode/usb-mode.h create mode 100644 firmware/usbstack/iap/libiap/time.h create mode 100644 firmware/usbstack/iap/libiap/unaligned.h create mode 100644 firmware/usbstack/iap/macros.h create mode 100644 firmware/usbstack/iap/notification.c create mode 100644 firmware/usbstack/iap/platform-macros.h create mode 100644 firmware/usbstack/iap/platform.c create mode 100644 firmware/usbstack/iap/platform.h create mode 100644 firmware/usbstack/usb_iap.c create mode 100644 firmware/usbstack/usb_iap.h diff --git a/firmware/SOURCES b/firmware/SOURCES index 888d296fd0..b80dc1a272 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -957,6 +957,20 @@ usbstack/usb_charging_only.c #ifdef USB_ENABLE_HID usbstack/usb_hid.c #endif +#ifdef USB_ENABLE_IAP +usbstack/iap/audio.c +usbstack/iap/buffer.c +usbstack/iap/debug.c +usbstack/iap/libiap/debug.c +usbstack/iap/libiap/fid-token-values.c +usbstack/iap/libiap/hid.c +usbstack/iap/libiap/iap.c +usbstack/iap/libiap/notification.c +usbstack/iap/libiap/span.c +usbstack/iap/notification.c +usbstack/iap/platform.c +usbstack/usb_iap.c +#endif #if CONFIG_USBOTG == USBOTG_M66591 drivers/m66591.c #elif CONFIG_USBOTG == USBOTG_ARC diff --git a/firmware/export/config.h b/firmware/export/config.h index bcea581141..4b0a16f0f1 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -1400,6 +1400,10 @@ Lyre prototype 1 */ #define USB_ENABLE_IAP #endif +#if defined(USB_ENABLE_IAP) +#define HAVE_MULTIMEDIA_KEYS +#endif + #endif /* BOOTLOADER */ #endif /* HAVE_USBSTACK */ diff --git a/firmware/export/iap-usb.h b/firmware/export/iap-usb.h new file mode 100644 index 0000000000..24f837981b --- /dev/null +++ b/firmware/export/iap-usb.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +#include "config.h" + +#ifdef USB_ENABLE_IAP +/* usbstack/iap/notification.c */ +void iap_on_track_time_position(uint32_t pos_ms); +void iap_on_track_playback_index(uint32_t index, bool track_ready); +void iap_on_tracks_count(uint32_t count); +void iap_on_play_status(int status /* AUDIO_STATUS_* */); +void iap_on_volume(int volume); +void iap_on_shuffle_state(bool state); +void iap_on_repeat_state(int state); +#else +static inline void iap_on_track_time_position(uint32_t pos_ms) { + (void)pos_ms; +} +static inline void iap_on_track_playback_index(uint32_t index, bool track_ready) { + (void)index; + (void)track_ready; +} +static inline void iap_on_tracks_count(uint32_t count) { + (void)count; +} +static inline void iap_on_play_status(int status) { + (void)status; +} +static inline void iap_on_volume(int volume) { + (void)volume; +} +static inline void iap_on_shuffle_state(bool state) { + (void)state; +} +static inline void iap_on_repeat_state(int state) { + (void)state; +} +#endif diff --git a/firmware/export/pcm_sink.h b/firmware/export/pcm_sink.h index 24a1962189..a4945d3534 100644 --- a/firmware/export/pcm_sink.h +++ b/firmware/export/pcm_sink.h @@ -21,6 +21,8 @@ #include #include +#include "config.h" + struct pcm_sink_caps { const unsigned long* samprs; uint16_t num_samprs; @@ -52,6 +54,9 @@ struct pcm_sink { enum pcm_sink_ids { PCM_SINK_BUILTIN = 0, +#ifdef USB_ENABLE_IAP + PCM_SINK_IAP, +#endif PCM_SINK_NUM }; diff --git a/firmware/export/usb.h b/firmware/export/usb.h index 6d9784f93e..ed76ade521 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -172,6 +172,9 @@ enum { #endif #ifdef USB_ENABLE_AUDIO USB_DRIVER_AUDIO, +#endif +#ifdef USB_ENABLE_IAP + USB_DRIVER_IAP, #endif USB_NUM_DRIVERS }; diff --git a/firmware/pcm.c b/firmware/pcm.c index bb4fd588b5..be5b47673a 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -77,8 +77,15 @@ * */ +#ifdef USB_ENABLE_IAP +extern struct pcm_sink iap_pcm_sink; +#endif + static struct pcm_sink* sinks[PCM_SINK_NUM] = { [PCM_SINK_BUILTIN] = &builtin_pcm_sink, +#ifdef USB_ENABLE_IAP + [PCM_SINK_IAP] = &iap_pcm_sink, +#endif }; static enum pcm_sink_ids cur_sink = PCM_SINK_BUILTIN; diff --git a/firmware/usb.c b/firmware/usb.c index 2645d3b290..c3e35d22b3 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -214,6 +214,9 @@ static inline void usb_configure_drivers(int for_state) #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 2)); // while "always" or "only in charge-only mode" #endif /* USB_ENABLE_AUDIO */ +#ifdef USB_ENABLE_IAP + usb_core_enable_driver(USB_DRIVER_IAP, false); +#endif #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, true); @@ -233,6 +236,9 @@ static inline void usb_configure_drivers(int for_state) #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 3)); // while "always" or "only in mass-storage mode" #endif /* USB_ENABLE_AUDIO */ +#ifdef USB_ENABLE_IAP + usb_core_enable_driver(USB_DRIVER_IAP, true); +#endif #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, false); #endif diff --git a/firmware/usbstack/iap/audio.c b/firmware/usbstack/iap/audio.c new file mode 100644 index 0000000000..d01b133d69 --- /dev/null +++ b/firmware/usbstack/iap/audio.c @@ -0,0 +1,268 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "core_alloc.h" +#include "pcm-internal.h" +#include "pcm_sampr.h" +#include "pcm_sink.h" +#include "system.h" +#include "usb_drv.h" + +#include "../usb_iap.h" +#include "buffer.h" +#include "libiap/iap.h" +#include "macros.h" +#include "platform.h" + +static const unsigned long samprs[] = { + SAMPR_48, + SAMPR_44, +}; + +struct StagingBuffer { + struct IAPAllocResult buf; + uint16_t cursor; +}; +static struct StagingBuffer staging_buffers[USB_BATCH_SLOTS + 1]; /* +1 for silent buffer */ +static int staging_buffer_index; + +#define zero_buffer (staging_buffers[USB_BATCH_SLOTS]) + +static const uint8_t* pulled_buf; +static size_t pulled_buf_size; +static size_t pulled_buf_cursor; +static int8_t set_freq; /* requested freq from rockbox */ +static int8_t cur_freq; /* requested freq from accessory */ +static uint8_t packet_count; + +static bool enabled; +static bool exhausted; +static bool track_attrs_sent; + +extern struct pcm_sink iap_pcm_sink; + +static void sink_set_freq(uint16_t freq) { + LOG("freq=%d", freq); + + track_attrs_sent = true; + + set_freq = freq; + + struct IAPContext* ctx = _iap_acquire_ctx(true); + check_act(iap_select_sampr(ctx, samprs[freq]), ); + _iap_release_ctx(); +} + +static size_t calc_packet_size(uint8_t cur_sampr, uint8_t packet_count) { + /* packet size calculation + * (4 = sizeof(int16_t) * channels) + * (1000 = usb frames per second) + * 48000Hz: + * 48000 * 4 / 1000 = 192.0 + * => 192 + ... + * 44100Hz: + * 44100 * 4 / 1000 = 176.4 + * => (9 * 176 + 180) + (... + */ + if(cur_sampr == 0) { + /*48k*/ + return 192; + } else { + /*44.1k*/ + return packet_count % 10 == 0 ? 180 : 176; + } +} + +static void batch_get_more(const void** ptr, size_t* len) { + const size_t packet_size = calc_packet_size(cur_freq, packet_count); +#if 0 + const int cur_frame = usb_drv_get_frame_number(); + LOG("ex=%d set=%d cur=%d", exhausted, set_freq, cur_freq); +#endif + +start: + if(exhausted || cur_freq != set_freq) { + *ptr = zero_buffer.buf.ptr; + *len = packet_size; + packet_count += 1; + return; + } + + if(pulled_buf_cursor == pulled_buf_size) { + /* run out of previously pulled data. + * let's start filling them, by requesting new data from upstream */ + if(!pcm_play_dma_complete_callback(PCM_DMAST_OK, (const void**)&pulled_buf, &pulled_buf_size)) { + /* no more data, but we have to keep sending something as long as the audio stream interface is enabled */ + exhausted = true; + goto start; + } + + /* pushing_{buf,buf_size} are filled. reset cursor and continue filling */ + pcm_play_dma_status_callback(PCM_DMAST_STARTED); + pulled_buf_cursor = 0; + } + + /* fill this single packet */ + struct StagingBuffer* stage = &staging_buffers[staging_buffer_index]; + const size_t copy = MIN(packet_size - stage->cursor, pulled_buf_size - pulled_buf_cursor); + memcpy(stage->buf.ptr + stage->cursor, pulled_buf + pulled_buf_cursor, copy); + pulled_buf_cursor += copy; + stage->cursor += copy; +#define AUDIO_STAT 0 +#if AUDIO_STAT == 1 + static int last_hz; + static int sample; +#endif + if(stage->cursor == packet_size) { + *ptr = stage->buf.ptr; + *len = stage->cursor; + stage->cursor = 0; + staging_buffer_index = (staging_buffer_index + 1) % USB_BATCH_SLOTS; + packet_count += 1; +#if AUDIO_STAT == 1 + sample += packet_size / 4; + if(current_tick >= last_hz + HZ) { + logf("pushed %d %d", packet_size, sample); + sample = 0; + last_hz = current_tick; + } +#endif + } else { /* pushing_buf_cursor == pushing_buf_size */ + goto start; + } +} + +static void sink_play(const void* addr, size_t size) { + LOG("play"); + + pulled_buf = addr; + pulled_buf_size = size; + pulled_buf_cursor = 0; + exhausted = false; + + /* resolve pending play request before sending TrackNewAudioAttributes (by sink_set_freq). + * some accessories will get confused when receiving it while waiting for an ack */ + struct IAPContext* ctx = _iap_acquire_ctx(true); + struct Platform* plt = ctx->platform; + if(plt->control_pending) { + check_act(iap_control_response(ctx, plt->pending_control, true), ); + plt->control_pending = false; + } + _iap_release_ctx(); + + /* rockbox only calls set_freq when changes occur, but we must call + * set_freq(and iap_select_sampr) at least once per connection. */ + if(!track_attrs_sent) { + sink_set_freq(iap_pcm_sink.configured_freq); + } +} + +static void sink_stop(void) { + LOG("stop"); + /* we don't call usb_drv_batch_stop() here, + * because we need to send something, even not playing. */ +} + +static void sink_nop(void) { +} + +struct pcm_sink iap_pcm_sink = { + .caps = { + .samprs = samprs, + .num_samprs = ARRAYLEN(samprs), + .default_freq = 0, + }, + .ops = { + .init = sink_nop, + .postinit = sink_nop, + .set_freq = sink_set_freq, + .lock = sink_nop, + .unlock = sink_nop, + .play = sink_play, + .stop = sink_stop, + }, +}; + +bool iap_audio_init(void) { + check_act(usb_drv_batch_init(AS_EP_IN, batch_get_more) == 0, return false); + + for(size_t i = 0; i < ARRAYLEN(staging_buffers); i += 1) { + check_act(iap_alloc_usb_send_buffer(AS_PACKET_SIZE, &staging_buffers[i].buf), goto error); + staging_buffers[i].cursor = 0; + } + staging_buffer_index = 0; + + memset(zero_buffer.buf.ptr, 0, AS_PACKET_SIZE); + + set_freq = -1; + cur_freq = -2; /* something <0 && !=set_freq */ + enabled = false; + exhausted = true; + track_attrs_sent = false; + packet_count = 0; + + return true; + +error: + for(size_t i = 0; i < ARRAYLEN(staging_buffers); i += 1) { + if(staging_buffers[i].buf.ptr != NULL) { + core_free(staging_buffers[i].buf.handle); + staging_buffers[i].buf.ptr = NULL; + } + } + return false; +} + +bool iap_audio_deinit(void) { + check_act(usb_drv_batch_deinit() == 0, ); + for(size_t i = 0; i < ARRAYLEN(staging_buffers); i += 1) { + core_free(staging_buffers[i].buf.handle); + staging_buffers[i].buf.ptr = NULL; + } + return true; +} + +bool iap_audio_enable(void) { + LOG("enabled=%d", enabled); + check_act(enabled || usb_drv_batch_start() == 0, return false); + enabled = true; + return true; +} + +bool iap_audio_disable(void) { + LOG("enabled=%d", enabled); + check_act(!enabled || usb_drv_batch_stop() == 0, return false); + enabled = false; + return true; +} + +bool iap_audio_set_sampr(uint32_t sampr) { + uint16_t freq = 0; + for(; freq < ARRAYLEN(samprs); freq += 1) { + if(samprs[freq] == sampr) { + break; + } + } + check_act(freq < ARRAYLEN(samprs), return false); + cur_freq = freq; + + LOG("sampr=%lu, set_freq=%d cur_freq=%d", sampr, set_freq, cur_freq); + + return true; +} diff --git a/firmware/usbstack/iap/audio.h b/firmware/usbstack/iap/audio.h new file mode 100644 index 0000000000..334ce41ebb --- /dev/null +++ b/firmware/usbstack/iap/audio.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once +#include +#include + +bool iap_audio_init(void); +bool iap_audio_deinit(void); +bool iap_audio_enable(void); +bool iap_audio_disable(void); +bool iap_audio_set_sampr(uint32_t sampr); diff --git a/firmware/usbstack/iap/buffer.c b/firmware/usbstack/iap/buffer.c new file mode 100644 index 0000000000..32b6cbdcfd --- /dev/null +++ b/firmware/usbstack/iap/buffer.c @@ -0,0 +1,47 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "core_alloc.h" + +#include "buffer.h" +#include "macros.h" + +bool iap_alloc_buffer(size_t size, struct IAPAllocResult* result) { + int handle = core_alloc_ex(size, &buflib_ops_locked); + check_act(handle > 0, return false); + uint8_t* ptr = core_get_data(handle); + + result->handle = handle; + result->ptr = ptr; + return true; +} + +bool iap_alloc_usb_send_buffer(size_t size, struct IAPAllocResult* result) { + /* + 31 to handle worst-case misalignment */ + check_act(iap_alloc_buffer(size + 31, result), return false); + + /* align to 32 */ + result->ptr = (void*)((uintptr_t)(result->ptr + 31) & 0xffffffe0); + /* uncached */ +#if defined(UNCACHED_ADDR) && CONFIG_CPU != AS3525 + result->ptr = UNCACHED_ADDR(result->ptr); +#endif + + return true; +} diff --git a/firmware/usbstack/iap/buffer.h b/firmware/usbstack/iap/buffer.h new file mode 100644 index 0000000000..2620fe3960 --- /dev/null +++ b/firmware/usbstack/iap/buffer.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once +#include +#include + +struct IAPAllocResult { + void* ptr; + int handle; +}; + +bool iap_alloc_buffer(size_t size, struct IAPAllocResult* result); +bool iap_alloc_usb_send_buffer(size_t size, struct IAPAllocResult* result); diff --git a/firmware/usbstack/iap/debug.c b/firmware/usbstack/iap/debug.c new file mode 100644 index 0000000000..80aa02741e --- /dev/null +++ b/firmware/usbstack/iap/debug.c @@ -0,0 +1,89 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include +#include + +#include "logf.h" +#include "system.h" +#include "tick.h" + +#include "font.h" +#include "lcd.h" + +#define MAX_COLS 64 + +static int rows; +static int columns; +static unsigned count; + +static void update_color(void) { + unsigned avail = rows - 1; + if((count % (avail * 2)) > avail) { + lcd_set_drawinfo(DRMODE_SOLID, LCD_BLACK, LCD_WHITE); + } else { + lcd_set_drawinfo(DRMODE_SOLID, LCD_WHITE, LCD_BLACK); + } +} + +void iap_lcd_scatter(const char* fmt, ...) { + if(rows == 0) { + int w, h; + font_getstringsize((unsigned char*)"A", &w, &h, FONT_SYSFIXED); + columns = MIN(LCD_WIDTH / w, MAX_COLS); + rows = LCD_HEIGHT / h; + } + + va_list ap; + char buf[256]; + va_start(ap, fmt); + int len = vsnprintf(buf, sizeof(buf), (char*)fmt, ap); + va_end(ap); + + logf("%s", buf); + + lcd_set_backdrop(NULL); + lcd_setfont(FONT_SYSFIXED); + + update_color(); + lcd_putsf(0, 0, (unsigned char*)"count %u", count); + for(int i = 0; i < len;) { + char line[MAX_COLS]; + int copy = MIN(len - i, columns - 1); + memcpy(line, buf + i, copy); + memset(line + copy, ' ', columns - 1 - copy); + line[columns - 1] = '\0'; + update_color(); + lcd_puts(0, 1 + count % (rows - 1), (unsigned char*)line); + count += 1; + i += copy; + } + lcd_update(); +} + +static unsigned long timestamp_epoch; + +unsigned long iap_debug_timestamp(void) { + return current_tick - timestamp_epoch; +} + +void iap_debug_reset_timestamp(void) { + timestamp_epoch = current_tick; +} diff --git a/firmware/usbstack/iap/debug.h b/firmware/usbstack/iap/debug.h new file mode 100644 index 0000000000..034a050f7f --- /dev/null +++ b/firmware/usbstack/iap/debug.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once + +/* debug switches */ +#define DEBUG_ENABLE_INFO 0 +#define DEBUG_ENABLE_ERROR 1 +#define DEBUG_LCD_PRINT 0 +#define DEBUG_DUMP_TX 0 +#define DEBUG_DUMP_RX 0 +#define DEBUG_HEXDUMP_NOLIMIT 0 + +#if DEBUG_ENABLE_INFO == 1 || DEBUG_ENABLE_ERROR == 1 +#define LOGF_ENABLE +#include "logf.h" +#endif + +void iap_lcd_scatter(const char* fmt, ...); +unsigned long iap_debug_timestamp(void); +void iap_debug_reset_timestamp(void); + +#if DEBUG_LCD_PRINT == 1 +#undef logf +#define logf(...) iap_lcd_scatter(__VA_ARGS__) +#endif diff --git a/firmware/usbstack/iap/libiap-sync.sh b/firmware/usbstack/iap/libiap-sync.sh new file mode 100755 index 0000000000..030c29f370 --- /dev/null +++ b/firmware/usbstack/iap/libiap-sync.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +url=https://github.com/mojyack/libiap.git +src=/tmp/libiap +dst=$PWD/libiap + +if [[ ! -e $src ]]; then + git clone "$url" "$src" +else + git -C "$src" pull +fi + +rsync --recursive --delete "$src/src/iap/" "$dst/" --exclude /README.rockbox +cp "$src/LICENSE" "$dst/LICENSE" +git -C "$src" rev-parse HEAD > "$dst/HEAD" diff --git a/firmware/usbstack/iap/libiap/HEAD b/firmware/usbstack/iap/libiap/HEAD new file mode 100644 index 0000000000..30d689eb0d --- /dev/null +++ b/firmware/usbstack/iap/libiap/HEAD @@ -0,0 +1 @@ +ad06cdfc66f5db55d16b741df265269309105af3 diff --git a/firmware/usbstack/iap/libiap/LICENSE b/firmware/usbstack/iap/libiap/LICENSE new file mode 100644 index 0000000000..fb320d67e7 --- /dev/null +++ b/firmware/usbstack/iap/libiap/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 mojyack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/firmware/usbstack/iap/libiap/README.rockbox b/firmware/usbstack/iap/libiap/README.rockbox new file mode 100644 index 0000000000..e6dbf7f555 --- /dev/null +++ b/firmware/usbstack/iap/libiap/README.rockbox @@ -0,0 +1,5 @@ +Files under this directory were imported from libiap(https://github.com/mojyack/libiap) except for: +- README.rockbox: this file, Rockbox specific notes +- HEAD: libiap commit hash + +To sync with the upstream, use ../libiap-sync.sh script. diff --git a/firmware/usbstack/iap/libiap/bool.h b/firmware/usbstack/iap/libiap/bool.h new file mode 100644 index 0000000000..f94595ede1 --- /dev/null +++ b/firmware/usbstack/iap/libiap/bool.h @@ -0,0 +1,5 @@ +#pragma once + +typedef unsigned char IAPBool; +#define iap_true 1 +#define iap_false 0 diff --git a/firmware/usbstack/iap/libiap/constants.h b/firmware/usbstack/iap/libiap/constants.h new file mode 100644 index 0000000000..2dca15d05f --- /dev/null +++ b/firmware/usbstack/iap/libiap/constants.h @@ -0,0 +1,11 @@ +#pragma once + +#define HID_BUFFER_SIZE 1024 +#define SEND_BUFFER_SIZE 767 /* 0x2ff - 1: input_report_size_table_hs[0x0C].size - sizeof(report_id) */ + +enum IAPPhase { + IAPPhase_Connected = 0, /* initial state, waiting for StartIDPS */ + IAPPhase_IDPS, /* idps started */ + IAPPhase_Auth, /* idps completed, authenticating accessory */ + IAPPhase_Authed, /* authentication completed, processing requests */ +}; diff --git a/firmware/usbstack/iap/libiap/context.h b/firmware/usbstack/iap/libiap/context.h new file mode 100644 index 0000000000..f7e6ca3600 --- /dev/null +++ b/firmware/usbstack/iap/libiap/context.h @@ -0,0 +1,107 @@ +#pragma once +#include +#include + +#include "bool.h" +#include "notification.h" +#include "platform.h" + +struct IAPContext; +struct IAPSpan; + +typedef IAPBool (*IAPOnSendComplete)(struct IAPContext* ctx); + +struct IAPOpts { + /* indicate usb transport is highspeed */ + IAPBool usb_highspeed : 1; + /* needed to support some accessories which don't set correct hid report id */ + IAPBool ignore_hid_report_id : 1; + /* limit packet size while sending artworks for stability */ + IAPBool artwork_single_report : 1; + /* dump each packets */ + IAPBool enable_packet_dump : 1; +}; + +struct IAPContextButtons { + IAPBool play_pause : 1; + IAPBool volume_up : 1; + IAPBool volume_down : 1; + IAPBool next_track : 1; + IAPBool prev_track : 1; + IAPBool next_album : 1; + IAPBool prev_album : 1; + IAPBool stop : 1; + IAPBool play : 1; + IAPBool pause : 1; + IAPBool mute_toggle : 1; + IAPBool next_chapter : 1; + IAPBool prev_chapter : 1; + IAPBool next_playlist : 1; + IAPBool prev_playlist : 1; + IAPBool shuffle_advance : 1; + IAPBool repeat_advance : 1; +}; + +struct IAPActiveEvent; + +typedef IAPBool (*IAPActiveEventReadyCallback)(struct IAPContext* ctx, struct IAPActiveEvent* event); + +struct IAPActiveEvent { + IAPActiveEventReadyCallback callback; + union { + uint32_t sampr; + struct { + struct IAPPlatformPendingControl control; + IAPBool result; + } control_response; + }; +}; + +struct IAPContext { + /* set by user */ + void* platform; /* opaque to platform functions */ + struct IAPOpts opts; /* options */ + + /* iap_feed_hid_report */ + uint8_t* hid_recv_buf; + size_t hid_recv_buf_cursor; + /* _iap_send_packet, _iap_send_hid_reports */ + uint8_t* send_buf; + size_t send_buf_sending_cursor; + size_t send_buf_sending_range_begin; + size_t send_buf_sending_range_end; + /* _iap_send_next_report */ + /* for library-oriented, manageable events */ + IAPOnSendComplete on_send_complete; + /* for user-oriented, unexpectable events */ + struct IAPActiveEvent active_events[2]; + /* _iap_feed_packet */ + int32_t handling_trans_id; + /* iap.c */ + struct IAPContextButtons context_button_state; + struct IAPPlatformArtwork artwork; + size_t artwork_cursor; + uint16_t trans_id; + uint16_t artwork_chunk_index; + int32_t artwork_trans_id; + uint16_t artwork_data_command; + uint8_t artwork_data_lingo; + uint8_t trans_id_support; /* TransIDSupport */ + /* notification.c */ + /* DisplayRemote::SetRemoteEventNotification */ + uint32_t enabled_notifications_3; + uint32_t notifications_3; + /* ExtendedInterface::SetPlayStatusChangeNotification */ + uint32_t enabled_notifications_4; + uint32_t notifications_4; + /* notification data */ + struct _IAPNotifyState notification_data; + uint8_t notification_tick; + /* _iap_send_hid_reports */ + uint8_t* hid_send_staging_buf; + IAPBool send_busy : 1; + IAPBool flushing_notifications : 1; + IAPBool waiting_for_audio_attrs_ack : 1; + + uint8_t phase; /* IAPPhase */ +}; diff --git a/firmware/usbstack/iap/libiap/datetime.h b/firmware/usbstack/iap/libiap/datetime.h new file mode 100644 index 0000000000..00f0b13336 --- /dev/null +++ b/firmware/usbstack/iap/libiap/datetime.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPDateTime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t seconds; +}; diff --git a/firmware/usbstack/iap/libiap/debug.c b/firmware/usbstack/iap/libiap/debug.c new file mode 100644 index 0000000000..c6386eb82a --- /dev/null +++ b/firmware/usbstack/iap/libiap/debug.c @@ -0,0 +1,1052 @@ +#include +#include + +#include "endian.h" +#include "macros.h" +#include "span.h" +#include "spec/iap.h" + +#define entry(lingo, command) [IAP##lingo##CommandID_##command] = #command + +static const char* strs_general[] = { + entry(General, RequestIdentify), + entry(General, Identify), + entry(General, IPodAck), + entry(General, RequestExtendedInterfaceMode), + entry(General, ReturnExtendedInterfaceMode), + entry(General, EnterExtendedInterfaceMode), + entry(General, ExitExtendedInterfaceMode), + entry(General, RequestIPodName), + entry(General, ReturnIPodName), + entry(General, RequestIPodSoftwareVersion), + entry(General, ReturnIPodSoftwareVersion), + entry(General, RequestIPodSerialNum), + entry(General, ReturnIPodSerialNum), + entry(General, RequestIPodModelNum), + entry(General, ReturnIPodModelNum), + entry(General, RequestLingoProtocolVersion), + entry(General, ReturnLingoProtocolVersion), + entry(General, RequestTransportMaxPayloadSize), + entry(General, ReturnTransportMaxPayloadSize), + entry(General, IdentifyDeviceLingoes), + entry(General, GetAccessoryAuthenticationInfo), + entry(General, RetAccessoryAuthenticationInfo), + entry(General, AckAccessoryAuthenticationInfo), + entry(General, GetAccessoryAuthenticationSignature), + entry(General, RetAccessoryAuthenticationSignature), + entry(General, AckAccessoryAuthenticationStatus), + entry(General, GetIPodAuthenticationInfo), + entry(General, RetIPodAuthenticationInfo), + entry(General, AckIPodAuthenticationInfo), + entry(General, GetIPodAuthenticationSignature), + entry(General, RetIPodAuthenticationSignature), + entry(General, AckIPodAuthenticationStatus), + entry(General, NotifyIPodStateChange), + entry(General, GetIPodOptions), + entry(General, RetIPodOptions), + entry(General, GetAccessoryInfo), + entry(General, RetAccessoryInfo), + entry(General, GetIPodPreferences), + entry(General, RetIPodPreferences), + entry(General, SetIPodPreferences), + entry(General, GetUIMode), + entry(General, RetUIMode), + entry(General, SetUIMode), + entry(General, StartIDPS), + entry(General, SetFIDTokenValues), + entry(General, AckFIDTokenValues), + entry(General, EndIDPS), + entry(General, IDPSStatus), + entry(General, OpenDataSessionForProtocol), + entry(General, CloseDataSession), + entry(General, AccessoryAck), + entry(General, AccessoryDataTransfer), + entry(General, IPodDataTransfer), + entry(General, SetAccessoryStatusNotification), + entry(General, RetAccessoryStatusNotification), + entry(General, AccessoryStatusNotification), + entry(General, SetEventNotification), + entry(General, IPodNotification), + entry(General, GetIPodOptionsForLingo), + entry(General, RetIPodOptionsForLingo), + entry(General, GetEventNotification), + entry(General, RetEventNotification), + entry(General, GetSupportedEventNotification), + entry(General, CancelCommand), + entry(General, RetSupportedEventNotification), + entry(General, SetAvailableCurrent), + entry(General, SetInternalBatteryChargingState), + entry(General, RequestApplicationLaunch), + entry(General, GetNowPlayingApplicationBundleName), + entry(General, RetNowPlayingApplicationBundleName), + entry(General, GetLocalizationInfo), + entry(General, RetLocalizationInfo), + entry(General, RequestWiFiConnectionInfo), + entry(General, WiFiConnectionInfo), +}; +static const char* strs_simple[] = { + entry(SimpleRemote, ContextButtonStatus), + entry(SimpleRemote, IPodAck), + entry(SimpleRemote, ImageButtonStatus), + entry(SimpleRemote, VideoButtonStatus), + entry(SimpleRemote, AudioButtonStatus), + entry(SimpleRemote, IPodOutButtonStatus), + entry(SimpleRemote, RotationInputStatus), + entry(SimpleRemote, RadioButtonStatus), + entry(SimpleRemote, CameraButtonStatus), + entry(SimpleRemote, RegisterDescriptor), + entry(SimpleRemote, IPodHIDReport), + entry(SimpleRemote, AccessoryHIDReport), + entry(SimpleRemote, UnregisterDescriptor), + entry(SimpleRemote, VoiceOverEvent), + entry(SimpleRemote, GetVoiceOverParameter), + entry(SimpleRemote, RetVoiceOverParameter), + entry(SimpleRemote, SetVoiceOverParameter), + entry(SimpleRemote, GetCurrentVoiceOverItemProperty), + entry(SimpleRemote, RetCurrentVoiceOverItemProperty), + entry(SimpleRemote, SetVoiceOverContext), + entry(SimpleRemote, VoiceOverParameterChanged), + entry(SimpleRemote, AccessoryAck), +}; +static const char* strs_display[] = { + entry(DisplayRemote, IPodAck), + entry(DisplayRemote, GetCurrentEQProfileIndex), + entry(DisplayRemote, RetCurrentEQProfileIndex), + entry(DisplayRemote, SetCurrentEQProfileIndex), + entry(DisplayRemote, GetNumEQProfiles), + entry(DisplayRemote, RetNumEQProfiles), + entry(DisplayRemote, GetIndexedEQProfileName), + entry(DisplayRemote, RetIndexedEQProfileName), + entry(DisplayRemote, SetRemoteEventNotification), + entry(DisplayRemote, RemoteEventNotification), + entry(DisplayRemote, GetRemoteEventStatus), + entry(DisplayRemote, RetRemoteEventStatus), + entry(DisplayRemote, GetIPodStateInfo), + entry(DisplayRemote, RetIPodStateInfo), + entry(DisplayRemote, SetIPodStateInfo), + entry(DisplayRemote, GetPlayStatus), + entry(DisplayRemote, RetPlayStatus), + entry(DisplayRemote, SetCurrentPlayingTrack), + entry(DisplayRemote, GetIndexedPlayingTrackInfo), + entry(DisplayRemote, RetIndexedPlayingTrackInfo), + entry(DisplayRemote, GetNumPlayingTracks), + entry(DisplayRemote, RetNumPlayingTracks), + entry(DisplayRemote, GetArtworkFormats), + entry(DisplayRemote, RetArtworkFormats), + entry(DisplayRemote, GetTrackArtworkData), + entry(DisplayRemote, RetTrackArtworkData), + entry(DisplayRemote, GetPowerBatteryState), + entry(DisplayRemote, RetPowerBatteryState), + entry(DisplayRemote, GetSoundCheckState), + entry(DisplayRemote, RetSoundCheckState), + entry(DisplayRemote, SetSoundCheckState), + entry(DisplayRemote, GetTrackArtworkTimes), + entry(DisplayRemote, RetTrackArtworkTimes), + entry(DisplayRemote, CreateGeniusPlaylist), + entry(DisplayRemote, IsGeniusAvailableForTrack), +}; +static const char* strs_ext[] = { + entry(ExtendedInterface, IPodAck), + entry(ExtendedInterface, GetCurrentPlayingTrackChapterInfo), + entry(ExtendedInterface, ReturnCurrentPlayingTrackChapterInfo), + entry(ExtendedInterface, SetCurrentPlayingTrackChapter), + entry(ExtendedInterface, GetCurrentPlayingTrackChapterPlayStatus), + entry(ExtendedInterface, ReturnCurrentPlayingTrackChapterPlayStatus), + entry(ExtendedInterface, GetCurrentPlayingTrackChapterName), + entry(ExtendedInterface, ReturnCurrentPlayingTrackChapterName), + entry(ExtendedInterface, GetAudiobookSpeed), + entry(ExtendedInterface, RetAudiobookSpeed), + entry(ExtendedInterface, SetAudiobookSpeed), + entry(ExtendedInterface, GetIndexedPlayingTrackInfo), + entry(ExtendedInterface, ReturnIndexedPlayingTrackInfo), + entry(ExtendedInterface, GetArtworkFormats), + entry(ExtendedInterface, RetArtworkFormats), + entry(ExtendedInterface, GetTrackArtworkData), + entry(ExtendedInterface, RetTrackArtworkData), + entry(ExtendedInterface, RequestProtocolVersion), + entry(ExtendedInterface, ReturnProtocolVersion), + entry(ExtendedInterface, RequestIPodName), + entry(ExtendedInterface, ReturnIPodName), + entry(ExtendedInterface, ResetDBSelection), + entry(ExtendedInterface, SelectDBRecord), + entry(ExtendedInterface, GetNumberCategorizedDBRecords), + entry(ExtendedInterface, ReturnNumberCategorizedDBRecords), + entry(ExtendedInterface, RetrieveCategorizedDatabaseRecords), + entry(ExtendedInterface, ReturnCategorizedDatabaseRecords), + entry(ExtendedInterface, GetPlayStatus), + entry(ExtendedInterface, ReturnPlayStatus), + entry(ExtendedInterface, GetCurrentPlayingTrackIndex), + entry(ExtendedInterface, ReturnCurrentPlayingTrackIndex), + entry(ExtendedInterface, GetIndexedPlayingTrackTitle), + entry(ExtendedInterface, ReturnIndexedPlayingTrackTitle), + entry(ExtendedInterface, GetIndexedPlayingTrackArtistName), + entry(ExtendedInterface, ReturnIndexedPlayingTrackArtistName), + entry(ExtendedInterface, GetIndexedPlayingTrackAlbumName), + entry(ExtendedInterface, ReturnIndexedPlayingTrackAlbumName), + entry(ExtendedInterface, SetPlayStatusChangeNotification), + entry(ExtendedInterface, PlayStatusChangeNotification), + entry(ExtendedInterface, PlayCurrentSelection), + entry(ExtendedInterface, PlayControl), + entry(ExtendedInterface, GetTrackArtworkTimes), + entry(ExtendedInterface, RetTrackArtworkTimes), + entry(ExtendedInterface, GetShuffle), + entry(ExtendedInterface, ReturnShuffle), + entry(ExtendedInterface, SetShuffle), + entry(ExtendedInterface, GetRepeat), + entry(ExtendedInterface, ReturnRepeat), + entry(ExtendedInterface, SetRepeat), + entry(ExtendedInterface, SetDisplayImage), + entry(ExtendedInterface, GetMonoDisplayImageLimits), + entry(ExtendedInterface, ReturnMonoDisplayImageLimits), + entry(ExtendedInterface, GetNumPlayingTracks), + entry(ExtendedInterface, ReturnNumPlayingTracks), + entry(ExtendedInterface, SetCurrentPlayingTrack), + entry(ExtendedInterface, SelectSortDBRecord), + entry(ExtendedInterface, GetColorDisplayImageLimits), + entry(ExtendedInterface, ReturnColorDisplayImageLimits), + entry(ExtendedInterface, ResetDBSelectionHierarchy), + entry(ExtendedInterface, GetDBITunesInfo), + entry(ExtendedInterface, RetDBITunesInfo), + entry(ExtendedInterface, GetUIDTrackInfo), + entry(ExtendedInterface, RetUIDTrackInfo), + entry(ExtendedInterface, GetDBTrackInfo), + entry(ExtendedInterface, RetDBTrackInfo), + entry(ExtendedInterface, GetPBTrackInfo), + entry(ExtendedInterface, RetPBTrackInfo), + entry(ExtendedInterface, CreateGeniusPlaylist), + entry(ExtendedInterface, RefreshGeniusPlaylist), + entry(ExtendedInterface, IsGeniusAvailableForTrack), + entry(ExtendedInterface, GetPlaylistInfo), + entry(ExtendedInterface, RetPlaylistInfo), + entry(ExtendedInterface, PrepareUIDList), + entry(ExtendedInterface, PlayPreparedUIDList), + entry(ExtendedInterface, GetArtworkTimes), + entry(ExtendedInterface, RetArtworkTimes), + entry(ExtendedInterface, GetArtworkData), + entry(ExtendedInterface, RetArtworkData), +}; +static const char* strs_da[] = { + entry(DigitalAudio, AccessoryAck), + entry(DigitalAudio, IPodAck), + entry(DigitalAudio, GetAccessorySampleRateCaps), + entry(DigitalAudio, RetAccessorySampleRateCaps), + entry(DigitalAudio, TrackNewAudioAttributes), + entry(DigitalAudio, SetVideoDelay), +}; + +#undef entry + +#define entry(lingo, strs, size) [IAPLingoID_##lingo] = {#lingo, \ + strs, \ + size} +static struct { + const char* lingo; + const char** strs; + size_t size; +} strs[] = { + entry(General, strs_general, array_size(strs_general)), + entry(Microphone, NULL, 0), + entry(SimpleRemote, strs_simple, array_size(strs_simple)), + entry(DisplayRemote, strs_display, array_size(strs_display)), + entry(ExtendedInterface, strs_ext, array_size(strs_ext)), + entry(AccessoryPower, NULL, 0), + entry(USBHostMode, NULL, 0), + entry(RFTuner, NULL, 0), + entry(AccessoryEqualizer, NULL, 0), + entry(Sports, NULL, 0), + entry(DigitalAudio, strs_da, array_size(strs_da)), + entry(Storage, NULL, 0), + entry(IPodOut, NULL, 0), + entry(Location, NULL, 0), +}; + +#undef entry + +const char* _iap_lingo_str_or_null(uint8_t lingo) { + check_ret(lingo < array_size(strs), NULL); + return strs[lingo].lingo; +} + +const char* _iap_lingo_str(uint8_t lingo) { + const char* ret = _iap_lingo_str_or_null(lingo); + return ret ? ret : "?"; +} + +const char* _iap_command_str_or_null(uint8_t lingo, uint16_t command) { + check_ret(lingo < array_size(strs), NULL); + check_ret(command < strs[lingo].size, NULL); + return strs[lingo].strs[command]; +} + +const char* _iap_command_str(uint8_t lingo, uint16_t command) { + const char* ret = _iap_command_str_or_null(lingo, command); + return ret ? ret : "?"; +} + +IAPBool _iap_span_is_str(const struct IAPSpan* span) { + return span->size > 0 && span->ptr[span->size - 1] == '\0'; +} + +const char* _iap_span_as_str(const struct IAPSpan* span) { + return _iap_span_is_str(span) ? (char*)span->ptr : "(invalid)"; +} + +#if !defined(IAP_LOGF_MUTED) +void _iap_dump_packet(uint8_t lingo, uint16_t command, int32_t trans_id, struct IAPSpan span) { + const char* lingo_str = _iap_lingo_str_or_null(lingo); + const char* command_str = _iap_command_str_or_null(lingo, command); + if(lingo_str == NULL) { + IAP_LOGF("?(0x%02" PRIX8 ") trans=%" PRIi32, lingo, trans_id); + return; + } else if(command_str == NULL) { + IAP_LOGF("%s:?(0x%02" PRIX8 ") trans=%" PRIi32, lingo_str, command, trans_id); + return; + } else { + IAP_LOGF("%s:%s trans=%" PRIi32, lingo_str, command_str, trans_id); + } + +#define span_read(Type) \ + const struct Type* payload = iap_span_read(&span, sizeof(*payload)); \ + check_act(payload != NULL, return); + + switch(lingo) { + case IAPLingoID_General: + switch(command) { + case IAPGeneralCommandID_IPodAck: { + span_read(IAPIPodAckPayload); + IAP_LOGF(" id=%s", _iap_command_str(lingo, payload->id)); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + case IAPGeneralCommandID_ReturnExtendedInterfaceMode: { + span_read(IAPReturnExtendedInterfaceModePayload); + IAP_LOGF(" mode=%" PRIu8, payload->is_ext_mode); + } break; + case IAPGeneralCommandID_ReturnIPodSoftwareVersion: { + span_read(IAPReturnIPodSoftwareVersionPayload); + IAP_LOGF(" version=%" PRIu8 ".%" PRIu8 ".%" PRIu8, payload->major, payload->minor, payload->revision); + } break; + case IAPGeneralCommandID_ReturnIPodSerialNum: { + IAP_LOGF(" serial=%s", _iap_span_as_str(&span)); + } break; + case IAPGeneralCommandID_ReturnIPodModelNum: { + IAP_LOGF(" model=%s", _iap_span_as_str(&span)); + } break; + case IAPGeneralCommandID_RequestLingoProtocolVersion: { + span_read(IAPRequestLingoProtocolVersionPayload); + IAP_LOGF(" lingo=%s", _iap_lingo_str(payload->lingo)); + } break; + case IAPGeneralCommandID_ReturnLingoProtocolVersion: { + span_read(IAPReturnLingoProtocolVersionPayload); + IAP_LOGF(" lingo=%s", _iap_lingo_str(payload->lingo)); + IAP_LOGF(" version=%" PRIu8 ".%" PRIu8, payload->major, payload->minor); + } break; + case IAPGeneralCommandID_ReturnTransportMaxPayloadSize: { + span_read(IAPReturnTransportMaxPayloadSizePayload); + IAP_LOGF(" size=%" PRIu16, swap_16(payload->max_payload_size)); + } break; + case IAPGeneralCommandID_IdentifyDeviceLingoes: { + span_read(IAPIdentifyDeviceLingoesPayload); + uint32_t bits = swap_32(payload->lingoes_bits); + for(int i = 0; i < 16; i += 1) { + if(bits & (1u << i)) { + IAP_LOGF(" supports %s", _iap_lingo_str(i)); + } + } + IAP_LOGF(" option=0x%02" PRIX32, swap_32(payload->options)); + IAP_LOGF(" device_id=0x%04" PRIX32, swap_32(payload->device_id)); + } break; + case IAPGeneralCommandID_RetIPodOptions: { + span_read(IAPRetIPodOptionsPayload); + IAP_LOGF(" state=0x%08" PRIX64, swap_64(payload->state)); + } break; + case IAPGeneralCommandID_GetIPodPreferences: { + span_read(IAPGetIPodPreferencesPayload); + IAP_LOGF(" class=0x%02" PRIX8, payload->class_id); + } break; + case IAPGeneralCommandID_RetIPodPreferences: { + span_read(IAPSetIPodPreferencesPayload); + IAP_LOGF(" class=0x%02X", payload->class_id); + IAP_LOGF(" setting=0x%02X", payload->setting_id); + } break; + case IAPGeneralCommandID_SetIPodPreferences: { + span_read(IAPSetIPodPreferencesPayload); + IAP_LOGF(" class=0x%02" PRIX8, payload->class_id); + IAP_LOGF(" setting=0x%02" PRIX8, payload->setting_id); + IAP_LOGF(" roe=%" PRIu8, payload->restore_on_exit); + } break; + case IAPGeneralCommandID_SetUIMode: { + span_read(IAPSetUIModePayload); + IAP_LOGF(" mode=0x%02" PRIX8, payload->ui_mode); + } break; + case IAPGeneralCommandID_SetFIDTokenValues: { + span_read(IAPSetFIDTokenValuesPayload); + for(int i = 0; i < payload->num_token_values; i += 1) { + check_act(span.size > sizeof(struct IAPFIDTokenValuesToken), return); + struct IAPFIDTokenValuesToken* token_header = (void*)span.ptr; + struct IAPSpan token_span = { + span.ptr, + token_header->length + 1 /* length does not include sizeof itself */, + }; + check_act(span.size >= token_span.size, return); + iap_span_read(&span, token_span.size); + + switch(token_header->type << 8 | token_header->subtype) { + case IAPFIDTokenTypes_Identify: { + /* IAPFIDTokenValuesIdentifyToken contains vla, need to parse manually */ + const struct IAPFIDTokenValuesIdentifyTokenHead* token_head = iap_span_read(&token_span, sizeof(*token_head)); + check_act(token_head != NULL, return); + print("accessory supported lingoes(%" PRIu8 "):", token_head->num_lingoes); + for(int i = 0; i < token_head->num_lingoes; i += 1) { + uint8_t lingo_id; + check_act(iap_span_read_8(&token_span, &lingo_id), return); + IAP_LOGF(" %s(0x%" PRIX8 ")", _iap_lingo_str(lingo_id), lingo_id); + } + const struct IAPFIDTokenValuesIdentifyTokenTail* token_tail = iap_span_read(&token_span, sizeof(*token_tail)); + check_act(token_tail != NULL, return); + const uint32_t opt = swap_32(token_tail->device_option); + const uint32_t id = swap_32(token_tail->device_id); + print("options=%04" PRIX32 " device_id=%04" PRIX32, opt, id); + } break; + case IAPFIDTokenTypes_AccCaps: { + const struct IAPFIDTokenValuesAccCapsToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + const uint64_t caps = swap_64(token->caps_bits); + print("accessory caps: %" PRIX64, caps); + } break; + case IAPFIDTokenTypes_AccInfo: { + const struct IAPFIDTokenValuesAccInfoToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + switch(token->info_type) { + case IAPFIDTokenValuesAccInfoTypes_AccName: + print("accessory name: %s", _iap_span_as_str(&token_span)); + break; + case IAPFIDTokenValuesAccInfoTypes_FirmwareVersion: + check_act(token_span.size == 3, return); + print("accessory firmware version: %" PRIX8 ".%" PRIX8 ".%" PRIX8, token_span.ptr[0], token_span.ptr[1], token_span.ptr[2]); + break; + case IAPFIDTokenValuesAccInfoTypes_HardwareVersion: + check_act(token_span.size == 3, return); + print("accessory hardware version: %" PRIX8 ".%" PRIX8 ".%" PRIX8, token_span.ptr[0], token_span.ptr[1], token_span.ptr[2]); + break; + case IAPFIDTokenValuesAccInfoTypes_Manufacture: + print("accessory manufacture: %s", _iap_span_as_str(&token_span)); + break; + case IAPFIDTokenValuesAccInfoTypes_ModelNumber: + print("accessory model number: %s", _iap_span_as_str(&token_span)); + break; + case IAPFIDTokenValuesAccInfoTypes_SerialNumber: + print("accessory serial number: %s", _iap_span_as_str(&token_span)); + break; + case IAPFIDTokenValuesAccInfoTypes_MaxPayloadSize: { + uint16_t val; + check_act(iap_span_read_16(&token_span, &val), return); + print("accessory max payload size: %" PRIu16, val); + } break; + case IAPFIDTokenValuesAccInfoTypes_AccStatus: { + uint32_t val; + check_act(iap_span_read_32(&token_span, &val), return); + print("accessory status: %" PRIX32, val); + } break; + case IAPFIDTokenValuesAccInfoTypes_RFCerts: { + uint32_t val; + check_act(iap_span_read_32(&token_span, &val), return); + print("accessory rf cert: %" PRIX32, val); + } break; + } + } break; + case IAPFIDTokenTypes_IPodPreference: { + const struct IAPFIDTokenValuesIPodPreferenceToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("accessory setting %" PRIX8 "=%" PRIX8, token->class_id, token->setting_id); + } break; + case IAPFIDTokenTypes_EAProtocol: { + const struct IAPFIDTokenValuesEAProtocolToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("ea protocol %" PRIX8 "=%s", token->protocol_index, _iap_span_as_str(&token_span)); + } break; + case IAPFIDTokenTypes_BundleSeedIDPref: { + const struct IAPFIDTokenValuesBundleSeedIDPrefToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("bundle seed id %.10s", token->bundle_seed_id_string); + } break; + case IAPFIDTokenTypes_ScreenInfo: { + const struct IAPFIDTokenValuesScreenInfoToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("screen info:"); + IAP_LOGF(" screen size(inch): %" PRIu16 "x%" PRIu16, swap_16(token->total_screen_width_inches), swap_16(token->total_screen_height_inches)); + IAP_LOGF(" screen size(pixel): %" PRIu16 "x%" PRIu16, swap_16(token->total_screen_width_pixels), swap_16(token->total_screen_height_pixels)); + IAP_LOGF(" ipod out size(pixel): %" PRIu16 "x%" PRIu16, swap_16(token->ipod_out_screen_width_pixels), swap_16(token->ipod_out_screen_height_pixels)); + IAP_LOGF(" feature: %" PRIX8, token->screen_feature_mask); + IAP_LOGF(" gamma: %" PRIu8, token->screen_gamma_value); + } break; + case IAPFIDTokenTypes_EAProtocolMetadata: { + const struct IAPFIDTokenValuesEAProtocolMetadataToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("ea protocol metadata %" PRIX8 "=%" PRIX8, token->protocol_index, token->metadata_type); + } break; + case IAPFIDTokenTypes_AccDigitalAudioSampleRates: { + const struct IAPFIDTokenValuesAccDigitalAudioSampleRatesToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("accessory supported audio sample rates:"); + while(token_span.size > 0) { + uint32_t rate; + check_act(iap_span_read_32(&token_span, &rate), return); + IAP_LOGF(" %" PRIu32, rate); + } + } break; + case IAPFIDTokenTypes_AccDigitalAudioVideoDelay: { + const struct IAPFIDTokenValuesAccDigitalAudioVideoDelayToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("accessory video delay: %" PRIu32, token->delay); + } break; + case IAPFIDTokenTypes_MicrophoneCaps: { + const struct IAPFIDTokenValuesMicrophoneCapsToken* token = iap_span_read(&token_span, sizeof(*token)); + check_act(token != NULL, return); + print("accessory microphone caps: %" PRIX32, token->caps_bits); + } break; + default: + print("unknown fid %04" PRIX16, token_header->type << 8 | token_header->subtype); + } + } + } break; + case IAPGeneralCommandID_EndIDPS: { + span_read(IAPEndIDPSPayload); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + case IAPGeneralCommandID_GetIPodOptionsForLingo: { + span_read(IAPGetIPodOptionsForLingoPayload); + IAP_LOGF(" lingo=%s", _iap_lingo_str(payload->lingo_id)); + } break; + case IAPGeneralCommandID_RetSupportedEventNotification: { + span_read(IAPRetSupportedEventNotificationPayload); + IAP_LOGF(" mask=0x%08" PRIX64, swap_64(payload->mask)); + } break; + case IAPGeneralCommandID_SetAvailableCurrent: { + span_read(IAPSetAvailableCurrentPayload); + IAP_LOGF(" current=%" PRIu16 "mA", swap_16(payload->current_limit_ma)); + } break; + case IAPGeneralCommandID_SetEventNotification: { + span_read(IAPSetEventNotificationPayload); + IAP_LOGF(" mask=0x%08" PRIX64, swap_64(payload->mask)); + } break; + } + break; + case IAPLingoID_SimpleRemote: + switch(command) { + case IAPSimpleRemoteCommandID_ContextButtonStatus: { + uint8_t bits; + check_ret(iap_span_read_8(&span, &bits), ); + IAP_LOGF(" bits0=0x%02" PRIX8, bits); + uint8_t index = 1; + while(span.size > 0) { + iap_span_read_8(&span, &bits); + IAP_LOGF(" bits%d=0x%02" PRIX8, index, bits); + index += 1; + } + } break; + case IAPSimpleRemoteCommandID_IPodAck: { + span_read(IAPIPodAckPayload); + IAP_LOGF(" id=%s", _iap_command_str(lingo, payload->id)); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + } + break; + case IAPLingoID_DisplayRemote: + switch(command) { + case IAPDisplayRemoteCommandID_IPodAck: { + span_read(IAPIPodAckPayload); + IAP_LOGF(" id=%s", _iap_command_str(lingo, payload->id)); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + case IAPDisplayRemoteCommandID_SetCurrentEQProfileIndex: { + span_read(IAPSetCurrentEQProfileIndexPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + IAP_LOGF(" roe=%" PRIu8, payload->restore_on_exit); + } break; + case IAPDisplayRemoteCommandID_SetRemoteEventNotification: { + span_read(IAPSetRemoteEventNotificationPayload); + IAP_LOGF(" mask=0x%04" PRIX32, swap_32(payload->mask)); + } break; + case IAPDisplayRemoteCommandID_RemoteEventNotification: { + check_ret(span.size >= 1, ); + IAP_LOGF(" type=0x%02" PRIX8, span.ptr[0]); + switch(span.ptr[0]) { + case IAPIPodStateType_TrackTimePositionMSec: { + span_read(IAPIPodStateTrackTimePositionMSecPayload); + IAP_LOGF(" position=%" PRIu32 "ms", swap_32(payload->position_ms)); + } break; + case IAPIPodStateType_TrackPlaybackIndex: { + span_read(IAPIPodStateTrackPlaybackIndexPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPIPodStateType_ChapterIndex: { + span_read(IAPIPodStateChapterIndexPayload); + IAP_LOGF(" chapter_index=%" PRIu16, swap_16(payload->chapter_index)); + IAP_LOGF(" chapter_count=%" PRIu16, swap_16(payload->chapter_count)); + } break; + case IAPIPodStateType_PlayStatus: { + span_read(IAPIPodStatePlayStatusPayload); + IAP_LOGF(" play_status=%" PRIu8, payload->status); + } break; + case IAPIPodStateType_Volume: { + span_read(IAPIPodStateVolumePayload); + IAP_LOGF(" mute=%" PRIu8, payload->mute_state); + IAP_LOGF(" volume=%" PRIu8, payload->ui_volume); + } break; + case IAPIPodStateType_Power: { + span_read(IAPIPodStatePowerPayload); + IAP_LOGF(" power_state=%" PRIu8, payload->power_state); + IAP_LOGF(" battery_level=%" PRIu8, payload->battery_level); + } break; + case IAPIPodStateType_EQSetting: { + span_read(IAPIPodStateEQSettingPayload); + IAP_LOGF(" eq_index=%" PRIu32, swap_32(payload->eq_index)); + } break; + case IAPIPodStateType_ShuffleSetting: { + span_read(IAPIPodStateShuffleSettingPayload); + IAP_LOGF(" shuffle_state=%" PRIu8, payload->shuffle_state); + } break; + case IAPIPodStateType_RepeatSetting: { + span_read(IAPIPodStateRepeatSettingPayload); + IAP_LOGF(" repeat_state=%" PRIu8, payload->repeat_state); + } break; + case IAPIPodStateType_DateTimeSetting: { + span_read(IAPIPodStateDateTimeSettingPayload); + IAP_LOGF(" time=%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 " %02" PRIu8 ":%02" PRIu8, swap_16(payload->year), payload->month, payload->day, payload->hour, payload->minute); + } break; + case IAPIPodStateType_AlarmSetting: { + span_read(IAPIPodStateAlarmSettingPayload); + IAP_LOGF(" alarm"); + } break; + case IAPIPodStateType_BacklightLevel: { + span_read(IAPIPodStateBacklightLevelPayload); + IAP_LOGF(" backlight_level=%" PRIu8, payload->level); + } break; + case IAPIPodStateType_HoldSwitchState: { + span_read(IAPIPodStateHoldSwitchStatePayload); + IAP_LOGF(" hold_switch_state=%" PRIu8, payload->state); + } break; + case IAPIPodStateType_SoundCheckState: { + span_read(IAPIPodStateSoundCheckStatePayload); + IAP_LOGF(" sound_check_state=%" PRIu8, payload->state); + } break; + case IAPIPodStateType_AudiobookSpeeed: { + span_read(IAPIPodStateAudiobookSpeeedPayload); + IAP_LOGF(" audio_book_speed=%" PRIu8, payload->speed); + } break; + case IAPIPodStateType_TrackTimePositionSec: { + span_read(IAPIPodStateTrackTimePositionSecPayload); + IAP_LOGF(" position=%us", swap_16(payload->position_s)); + } break; + case IAPIPodStateType_AbsoluteVolume: { + span_read(IAPIPodStateAbsoluteVolumePayload); + IAP_LOGF(" mute=%" PRIu8, payload->mute_state); + IAP_LOGF(" volume=%" PRIu8, payload->ui_volume); + IAP_LOGF(" absolute_volume=%" PRIu8, payload->absolute_volume); + } break; + case IAPIPodStateType_TrackCaps: { + span_read(IAPIPodStateTrackCapsPayload); + IAP_LOGF(" track_caps=0x%04" PRIX32, swap_32(payload->caps)); + } break; + case IAPIPodStateType_PlaybackEngineContents: { + span_read(IAPIPodStatePlaybackEngineContentsPayload); + IAP_LOGF(" playback_engine_contents=%" PRIu32, swap_32(payload->count)); + } break; + } + } break; + case IAPDisplayRemoteCommandID_GetIPodStateInfo: { + span_read(IAPGetIPodStateInfoPayload); + IAP_LOGF(" type=0x%02" PRIX8, payload->type); + } break; + case IAPDisplayRemoteCommandID_RetIPodStateInfo: { + check_ret(span.size > 1, ); + IAP_LOGF(" type=0x%02" PRIX8, span.ptr[0]); + switch(span.ptr[0]) { + case IAPIPodStateType_TrackTimePositionMSec: { + span_read(IAPIPodStateTrackTimePositionMSecPayload); + IAP_LOGF(" position=%" PRIu32 "ms", swap_32(payload->position_ms)); + } break; + case IAPIPodStateType_TrackPlaybackIndex: { + span_read(IAPIPodStateTrackPlaybackIndexPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPIPodStateType_ChapterIndex: { + span_read(IAPIPodStateChapterIndexPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + IAP_LOGF(" cindex=%" PRIu16, swap_16(payload->chapter_index)); + IAP_LOGF(" ccount=%" PRIu16, swap_16(payload->chapter_count)); + } break; + case IAPIPodStateType_PlayStatus: { + span_read(IAPIPodStatePlayStatusPayload); + IAP_LOGF(" status=%" PRIu8, payload->status); + } break; + case IAPIPodStateType_Volume: { + span_read(IAPIPodStateVolumePayload); + IAP_LOGF(" ui_volume=%" PRIu8, payload->ui_volume); + IAP_LOGF(" mute_state=%" PRIu8, payload->mute_state); + } break; + case IAPIPodStateType_Power: { + span_read(IAPIPodStatePowerPayload); + IAP_LOGF(" power_state=0x%02" PRIX8, payload->power_state); + IAP_LOGF(" battery_level=%" PRIu8, payload->battery_level); + } break; + case IAPIPodStateType_EQSetting: { + span_read(IAPIPodStateEQSettingPayload); + IAP_LOGF(" eq_index=%" PRIu32, swap_32(payload->eq_index)); + } break; + case IAPIPodStateType_ShuffleSetting: { + span_read(IAPIPodStateShuffleSettingPayload); + IAP_LOGF(" shuffle_state=%" PRIu8, payload->shuffle_state); + } break; + case IAPIPodStateType_RepeatSetting: { + span_read(IAPIPodStateRepeatSettingPayload); + IAP_LOGF(" repeat_state=%" PRIu8, payload->repeat_state); + } break; + case IAPIPodStateType_DateTimeSetting: { + span_read(IAPIPodStateDateTimeSettingPayload); + IAP_LOGF(" time=%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 " %02" PRIu8 ":%02" PRIu8, swap_16(payload->year), payload->month, payload->day, payload->hour, payload->minute); + } break; + case IAPIPodStateType_AlarmSetting: { + } break; + case IAPIPodStateType_BacklightLevel: { + span_read(IAPIPodStateBacklightLevelPayload); + IAP_LOGF(" level=%" PRIu8, payload->level); + } break; + case IAPIPodStateType_HoldSwitchState: { + span_read(IAPIPodStateHoldSwitchStatePayload); + IAP_LOGF(" state=%" PRIu8, payload->state); + } break; + case IAPIPodStateType_SoundCheckState: { + span_read(IAPIPodStateSoundCheckStatePayload); + IAP_LOGF(" state=%" PRIu8, payload->state); + } break; + case IAPIPodStateType_AudiobookSpeeed: { + span_read(IAPIPodStateAudiobookSpeeedPayload); + IAP_LOGF(" speed=%" PRIu8, payload->speed); + } break; + case IAPIPodStateType_TrackTimePositionSec: { + span_read(IAPIPodStateTrackTimePositionSecPayload); + IAP_LOGF(" position=%" PRIu16 "s", swap_16(payload->position_s)); + } break; + case IAPIPodStateType_AbsoluteVolume: { + span_read(IAPIPodStateAbsoluteVolumePayload); + IAP_LOGF(" mute=%" PRIu8, payload->mute_state); + IAP_LOGF(" ui_volume=%" PRIu8, payload->ui_volume); + IAP_LOGF(" absolute_volume=%" PRIu8, payload->absolute_volume); + } break; + case IAPIPodStateType_TrackCaps: { + span_read(IAPIPodStateTrackCapsPayload); + IAP_LOGF(" caps=0x%04" PRIX32, swap_32(payload->caps)); + } break; + case IAPIPodStateType_PlaybackEngineContents: { + span_read(IAPIPodStatePlaybackEngineContentsPayload); + IAP_LOGF(" count=%" PRIu32, swap_32(payload->count)); + } break; + } + } break; + case IAPDisplayRemoteCommandID_RetPlayStatus: { + span_read(IAPRetPlayStatusPayload); + IAP_LOGF(" state=0x%02" PRIX8, payload->state); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" pos=%" PRIu32 "ms", swap_32(payload->track_pos_ms)); + IAP_LOGF(" total=%" PRIu32 "ms", swap_32(payload->track_total_ms)); + } break; + case IAPDisplayRemoteCommandID_GetIndexedPlayingTrackInfo: { + span_read(IAPGetIndexedPlayingTrackInfoPayload); + IAP_LOGF(" type=0x%02" PRIX8, payload->type); + IAP_LOGF(" track=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" chapter=%" PRIu16, swap_16(payload->chapter_index)); + } break; + case IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo: { + check_ret(span.size > 1, ); + IAP_LOGF(" type=0x%02" PRIX8, span.ptr[0]); + switch(span.ptr[0]) { + case IAPIndexedPlayingTrackInfoType_TrackCapsInfo: { + span_read(IAPRetIndexedPlayingTrackInfoTrackCapsInfoPayload); + IAP_LOGF(" caps=0x%04" PRIX32, swap_32(payload->track_caps)); + IAP_LOGF(" total=%" PRIu32 "ms", swap_32(payload->track_total_ms)); + IAP_LOGF(" chapter_count=%" PRIu16, swap_16(payload->chapter_count)); + } break; + case IAPIndexedPlayingTrackInfoType_ChapterTimeName: { + span_read(IAPRetIndexedPlayingTrackInfoChapterTimeNamePayload); + IAP_LOGF(" offset=%" PRIu32 "ms", swap_32(payload->offset_ms)); + IAP_LOGF(" name=%s", _iap_span_as_str(&span)); + } break; + case IAPIndexedPlayingTrackInfoType_ArtistName: + case IAPIndexedPlayingTrackInfoType_AlbumName: + case IAPIndexedPlayingTrackInfoType_GenreName: + case IAPIndexedPlayingTrackInfoType_ComposerName: { + IAP_LOGF(" str=%s", _iap_span_as_str(&span)); + } break; + case IAPIndexedPlayingTrackInfoType_Lyrics: { + span_read(IAPRetIndexedPlayingTrackInfoLyricsPayload); + IAP_LOGF(" info=0x%02" PRIX8, payload->info_bits); + IAP_LOGF(" index=%" PRIu16, swap_16(payload->index)); + IAP_LOGF(" lyrics=%s", _iap_span_as_str(&span)); + } break; + case IAPIndexedPlayingTrackInfoType_ArtworkCount: { + span_read(IAPRetIndexedPlayingTrackInfoArtworkCountPayload); + while(span.size >= sizeof(struct IAPArtworkCount)) { + const struct IAPArtworkCount* count = iap_span_read(&span, sizeof(*count)); + IAP_LOGF(" format=%" PRIu16 ", count=%" PRIu16, swap_16(count->format), swap_16(count->count)); + } + } break; + } + } break; + case IAPDisplayRemoteCommandID_RetNumPlayingTracks: { + span_read(IAPRetNumPlayingTracksPayload); + IAP_LOGF(" tracks=%" PRIu32, swap_32(payload->num_playing_tracks)); + } break; + case IAPDisplayRemoteCommandID_RetArtworkFormats: { + while(span.size >= sizeof(struct IAPArtworkFormat)) { + const struct IAPArtworkFormat* format = iap_span_read(&span, sizeof(*format)); + IAP_LOGF(" id=%" PRIu16 ", format=%" PRIu8 ", width=%" PRIu16 ", height=%" PRIu16, swap_16(format->format_id), format->pixel_format, swap_16(format->image_width), swap_16(format->image_height)); + } + } break; + case IAPDisplayRemoteCommandID_GetTrackArtworkData: { + span_read(IAPGetTrackArtworkDataPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" format=%" PRIu8, payload->format_id); + IAP_LOGF(" offset=%" PRIu32 "ms", swap_32(payload->offset_ms)); + } break; + case IAPDisplayRemoteCommandID_RetTrackArtworkData: { + uint16_t index; + check_ret(iap_span_peek_16(&span, &index), ); + if(index == 0) { + span_read(IAPRetTrackArtworkDataFirstPayload); + IAP_LOGF(" index=%" PRIu16, swap_16(payload->index)); + IAP_LOGF(" format=%" PRIu16, swap_16(payload->pixel_format)); + IAP_LOGF(" width=%" PRIu16, swap_16(payload->pixel_width)); + IAP_LOGF(" height=%" PRIu16, swap_16(payload->pixel_height)); + } else { + span_read(IAPRetTrackArtworkDataSubsequenctPayload); + IAP_LOGF(" index=%" PRIu16, swap_16(payload->index)); + } + } break; + case IAPDisplayRemoteCommandID_RetPowerBatteryState: { + span_read(IAPRetPowerBatteryStatePayload); + IAP_LOGF(" power_state=%" PRIu8, payload->power_state); + IAP_LOGF(" battery_level=%" PRIu8, payload->battery_level); + } break; + case IAPDisplayRemoteCommandID_GetTrackArtworkTimes: { + span_read(IAPGetTrackArtworkTimesPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" format=%" PRIu16, swap_16(payload->format_id)); + IAP_LOGF(" artwork_index=%" PRIu32, swap_32(payload->artwork_index)); + IAP_LOGF(" artwork_count=%" PRIu32, swap_32(payload->artwork_count)); + } break; + case IAPDisplayRemoteCommandID_RetTrackArtworkTimes: { + while(span.size >= sizeof(uint16_t)) { + uint16_t time; + iap_span_read_16(&span, &time); + IAP_LOGF(" time=%" PRIu16, time); + } + } break; + } + break; + case IAPLingoID_ExtendedInterface: + switch(command) { + case IAPExtendedInterfaceCommandID_IPodAck: { + span_read(IAPExtendedIPodAckPayload); + IAP_LOGF(" id=%s", _iap_command_str(lingo, swap_16(payload->id))); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + case IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackChapterInfo: { + span_read(IAPReturnCurrentPlayingTrackChapterInfoPayload); + IAP_LOGF(" count=%" PRIu32, swap_32(payload->count)); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPExtendedInterfaceCommandID_RetAudiobookSpeed: { + span_read(IAPRetAudiobookSpeedPayload); + IAP_LOGF(" speed=0x%02" PRIX8, payload->speed); + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackInfo: { + span_read(IAPExtendedGetIndexedPlayingTrackInfoPayload); + IAP_LOGF(" type=0x%02" PRIX8, payload->type); + IAP_LOGF(" track=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" chapter=%" PRIu16, swap_16(payload->chapter_index)); + } break; + case IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo: { + check_ret(span.size > 1, ); + IAP_LOGF(" type=0x%02" PRIX8, span.ptr[0]); + switch(span.ptr[0]) { + case IAPExtendedIndexedPlayingTrackInfoType_TrackCapsInfo: { + span_read(IAPExtendedRetIndexedPlayingTrackInfoTrackCapsInfoPayload); + IAP_LOGF(" caps=0x%04" PRIX32, swap_32(payload->track_caps)); + IAP_LOGF(" total=%" PRIu32 "ms", swap_32(payload->track_total_ms)); + IAP_LOGF(" chapter_count=%" PRIu16, swap_16(payload->chapter_count)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_PodcastName: { + IAP_LOGF(" str=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackReleaseDate: { + span_read(IAPExtendedRetIndexedPlayingTrackInfoTrackReleaseDatePayload); + IAP_LOGF(" release=%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 " %02" PRIu8 ":%02" PRIu8 ".%02" PRIu8, swap_16(payload->year), payload->month, payload->day, payload->hours, payload->minutes, payload->seconds); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackDescription: { + span_read(IAPExtendedRetIndexedPlayingTrackInfoTrackDescriptionPayload); + IAP_LOGF(" info=0x%02" PRIX8, payload->info_bits); + IAP_LOGF(" index=%" PRIu8, swap_16(payload->index)); + IAP_LOGF(" desc=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackSongLyrics: { + span_read(IAPExtendedRetIndexedPlayingTrackInfoTrackSongLyricsPayload); + IAP_LOGF(" info=0x%02" PRIX8, payload->info_bits); + IAP_LOGF(" index=%u" PRIu16, swap_16(payload->index)); + IAP_LOGF(" lyrics=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackGenre: { + IAP_LOGF(" str=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackComposer: { + IAP_LOGF(" str=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackArtworkCount: { + span_read(IAPRetIndexedPlayingTrackInfoArtworkCountPayload); + while(span.size >= sizeof(struct IAPArtworkCount)) { + const struct IAPArtworkCount* count = iap_span_read(&span, sizeof(*count)); + IAP_LOGF(" format=%" PRIu16 ", count=%" PRIu16, swap_16(count->format), swap_16(count->count)); + } + } break; + } + } break; + case IAPExtendedInterfaceCommandID_RetArtworkFormats: { + while(span.size >= sizeof(struct IAPArtworkFormat)) { + const struct IAPArtworkFormat* format = iap_span_read(&span, sizeof(*format)); + IAP_LOGF(" id=%" PRIu16 ", format=%" PRIu8 ", width=%" PRIu16 ", height=%" PRIu16, swap_16(format->format_id), format->pixel_format, swap_16(format->image_width), swap_16(format->image_height)); + } + } break; + case IAPExtendedInterfaceCommandID_GetTrackArtworkData: { + span_read(IAPGetTrackArtworkDataPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->track_index)); + IAP_LOGF(" format=%" PRIu8, payload->format_id); + IAP_LOGF(" offset=%" PRIu32 "ms", swap_32(payload->offset_ms)); + } break; + case IAPExtendedInterfaceCommandID_RetTrackArtworkData: { + uint16_t index; + check_ret(iap_span_peek_16(&span, &index), ); + if(index == 0) { + span_read(IAPRetTrackArtworkDataFirstPayload); + IAP_LOGF(" index=%" PRIu16, swap_16(payload->index)); + IAP_LOGF(" format=%" PRIu16, swap_16(payload->pixel_format)); + IAP_LOGF(" width=%" PRIu16, swap_16(payload->pixel_width)); + IAP_LOGF(" height=%" PRIu16, swap_16(payload->pixel_height)); + } else { + span_read(IAPRetTrackArtworkDataSubsequenctPayload); + IAP_LOGF(" index=%" PRIu16, swap_16(payload->index)); + } + } break; + case IAPExtendedInterfaceCommandID_GetNumberCategorizedDBRecords: { + span_read(IAPGetNumberCategorizedDBRecordsPayload); + IAP_LOGF(" type=0x%02" PRIX8, payload->type); + } break; + case IAPExtendedInterfaceCommandID_ReturnNumberCategorizedDBRecords: { + span_read(IAPReturnNumberCategorizedDBRecordsPayload); + IAP_LOGF(" count=%" PRIu32, swap_32(payload->count)); + } break; + case IAPExtendedInterfaceCommandID_ReturnPlayStatus: { + span_read(IAPExtendedRetPlayStatusPayload); + IAP_LOGF(" state=0x%02" PRIX8, payload->state); + IAP_LOGF(" pos=%" PRIu32 "ms", swap_32(payload->track_pos_ms)); + IAP_LOGF(" total=%" PRIu32 "ms", swap_32(payload->track_total_ms)); + } break; + case IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackIndex: { + span_read(IAPReturnCurrentPlayingTrackIndexPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackTitle: + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackArtistName: + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackAlbumName: { + span_read(IAPGetIndexedPlayingTrackStringPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackTitle: + case IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackArtistName: + case IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackAlbumName: { + IAP_LOGF(" str=%s", _iap_span_as_str(&span)); + } break; + case IAPExtendedInterfaceCommandID_SetPlayStatusChangeNotification: { + if(span.size == sizeof(struct IAPSetPlayStatusChangeNotification1BytePayload)) { + span_read(IAPSetPlayStatusChangeNotification1BytePayload); + IAP_LOGF(" enable=%" PRIu8, payload->enable); + } else { + span_read(IAPSetPlayStatusChangeNotification4BytesPayload); + IAP_LOGF(" mask=0x%08" PRIX32, swap_32(payload->mask)); + } + } break; + case IAPExtendedInterfaceCommandID_PlayControl: { + span_read(IAPPlayControlPayload); + IAP_LOGF(" code=0x%02" PRIX8, payload->code); + } break; + case IAPExtendedInterfaceCommandID_ReturnShuffle: { + span_read(IAPReturnShufflePayload); + IAP_LOGF(" mode=%" PRIu8, payload->mode); + } break; + case IAPExtendedInterfaceCommandID_SetShuffle: { + span_read(IAPSetShufflePayload); + IAP_LOGF(" mode=%" PRIu8, payload->mode); + IAP_LOGF(" roe=%" PRIu8, payload->restore_on_exit); + } break; + case IAPExtendedInterfaceCommandID_ReturnRepeat: { + span_read(IAPReturnRepeatPayload); + IAP_LOGF(" mode=%" PRIu8, payload->mode); + } break; + case IAPExtendedInterfaceCommandID_SetRepeat: { + span_read(IAPSetRepeatPayload); + IAP_LOGF(" mode=%" PRIu8, payload->mode); + IAP_LOGF(" roe=%" PRIu8, payload->restore_on_exit); + } break; + case IAPExtendedInterfaceCommandID_SetDisplayImage: { + uint16_t index; + check_ret(iap_span_peek_16(&span, &index), ); + IAP_LOGF(" index=%" PRIu16, index); + if(index == 0) { + span_read(IAPSetDisplayImageFirstPayload); + IAP_LOGF(" format=%02" PRIX8, payload->pixel_format); + IAP_LOGF(" width=%" PRIu16, swap_16(payload->pixel_width)); + IAP_LOGF(" height=%" PRIu16, swap_16(payload->pixel_height)); + } + } break; + case IAPExtendedInterfaceCommandID_ReturnNumPlayingTracks: { + span_read(IAPRetNumPlayingTracksPayload); + IAP_LOGF(" tracks=%" PRIu32, swap_32(payload->num_playing_tracks)); + } break; + case IAPExtendedInterfaceCommandID_SetCurrentPlayingTrack: { + span_read(IAPSetCurrentPlayingTrackPayload); + IAP_LOGF(" index=%" PRIu32, swap_32(payload->index)); + } break; + case IAPExtendedInterfaceCommandID_ReturnColorDisplayImageLimits: { + span_read(IAPColorDisplayImageLimit); + IAP_LOGF(" width=%" PRIu16, swap_16(payload->max_width)); + IAP_LOGF(" height=%" PRIu16, swap_16(payload->max_height)); + IAP_LOGF(" format=0x%02" PRIX8, payload->pixel_format); + } break; + } + break; + case IAPLingoID_DigitalAudio: + switch(command) { + case IAPDigitalAudioCommandID_AccessoryAck: { + span_read(IAPAccAckPayload); + IAP_LOGF(" id=%s", _iap_command_str(lingo, payload->id)); + IAP_LOGF(" status=0x%02" PRIX8, payload->status); + } break; + case IAPDigitalAudioCommandID_RetAccessorySampleRateCaps: { + while(span.size >= sizeof(uint32_t)) { + uint32_t sample_rate; + iap_span_read_32(&span, &sample_rate); + IAP_LOGF(" rate=%" PRIu32, sample_rate); + } + } break; + } + break; + } +#undef span_read +} +#else +void _iap_dump_packet(uint8_t lingo, uint16_t command, int32_t trans_id, struct IAPSpan span) { + (void)lingo; + (void)command; + (void)trans_id; + (void)span; +} +#endif diff --git a/firmware/usbstack/iap/libiap/endian.h b/firmware/usbstack/iap/libiap/endian.h new file mode 100644 index 0000000000..1a3f011ee8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/endian.h @@ -0,0 +1,28 @@ +#pragma once +#include + +__attribute__((unused)) static uint8_t swap_8(uint8_t num) { + return num; +} + +__attribute__((unused)) static uint16_t swap_16(uint16_t num) { + return (num >> 8) | (num << 8); +} + +__attribute__((unused)) static uint32_t swap_32(uint32_t num) { + return (num & 0xFF000000) >> 24 | + (num & 0x00FF0000) >> 8 | + (num & 0x0000FF00) << 8 | + (num & 0x000000FF) << 24; +} + +__attribute__((unused)) static uint64_t swap_64(uint64_t num) { + return (num & 0xFF00000000000000) >> 56 | + (num & 0x00FF000000000000) >> 40 | + (num & 0x0000FF0000000000) << 24 | + (num & 0x000000FF00000000) << 8 | + (num & 0x000000000FF00000) << 8 | + (num & 0x00000000000FF000) << 24 | + (num & 0x000000000000FF00) << 40 | + (num & 0x00000000000000FF) << 56; +} diff --git a/firmware/usbstack/iap/libiap/fid-token-values.c b/firmware/usbstack/iap/libiap/fid-token-values.c new file mode 100644 index 0000000000..eeca46533b --- /dev/null +++ b/firmware/usbstack/iap/libiap/fid-token-values.c @@ -0,0 +1,141 @@ +#include "endian.h" +#include "iap.h" +#include "macros.h" +#include "span.h" +#include "spec/iap.h" + +#define pack_accepted(Ack) \ + struct Ack* const ack = iap_span_alloc(response, sizeof(*ack)); \ + check_ret(ack != NULL, -IAPAckStatus_EOutOfResource); \ + ack->length = sizeof(struct Ack) - 1; \ + ack->type = token_header->type; \ + ack->subtype = token_header->subtype; \ + ack->status = IAPFIDTokenValuesIdentifyAckStatus_Accepted; + +int _iap_hanlde_set_fid_token_values(struct IAPSpan* request, struct IAPSpan* response) { + const struct IAPSetFIDTokenValuesPayload* request_payload = iap_span_read(request, sizeof(*request_payload)); + check_ret(request_payload != NULL, -IAPAckStatus_EBadParameter); + check_ret(iap_span_write_8(response, request_payload->num_token_values), -IAPAckStatus_EOutOfResource); + for(int i = 0; i < request_payload->num_token_values; i += 1) { + check_ret(request->size > sizeof(struct IAPFIDTokenValuesToken), -IAPAckStatus_EBadParameter); + struct IAPFIDTokenValuesToken* token_header = (void*)request->ptr; + struct IAPSpan token_span = { + request->ptr, + token_header->length + 1 /* length does not include sizeof itself */, + }; + check_ret(request->size >= token_span.size, -IAPAckStatus_EBadParameter); + iap_span_read(request, token_span.size); + + switch(token_header->type << 8 | token_header->subtype) { + case IAPFIDTokenTypes_Identify: { + /* IAPFIDTokenValuesIdentifyToken contains vla, need to parse manually */ + const struct IAPFIDTokenValuesIdentifyTokenHead* token_head = iap_span_read(&token_span, sizeof(*token_head)); + check_ret(token_head != NULL, -IAPAckStatus_EBadParameter); + for(int i = 0; i < token_head->num_lingoes; i += 1) { + uint8_t lingo_id; + check_ret(iap_span_read_8(&token_span, &lingo_id), -IAPAckStatus_EBadParameter); + } + const struct IAPFIDTokenValuesIdentifyTokenTail* token_tail = iap_span_read(&token_span, sizeof(*token_tail)); + check_ret(token_tail != NULL, -IAPAckStatus_EBadParameter); + const uint32_t opt = swap_32(token_tail->device_option); + const uint32_t id = swap_32(token_tail->device_id); + pack_accepted(IAPFIDTokenValuesIdentifyAck); + if(opt != IAPIdentifyDeviceLingoesOptions_ImmediateAuth) { + ack->status = IAPFIDTokenValuesIdentifyAckStatus_RequiredFailed; + } + (void)id; + } break; + case IAPFIDTokenTypes_AccCaps: { + const struct IAPFIDTokenValuesAccCapsToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + const uint64_t caps = swap_64(token->caps_bits); + (void)caps; + pack_accepted(IAPFIDTokenValuesAccCapsAck); + } break; + case IAPFIDTokenTypes_AccInfo: { + const struct IAPFIDTokenValuesAccInfoToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + switch(token->info_type) { + case IAPFIDTokenValuesAccInfoTypes_AccName: + break; + case IAPFIDTokenValuesAccInfoTypes_FirmwareVersion: + check_ret(token_span.size == 3, -IAPAckStatus_EBadParameter); + break; + case IAPFIDTokenValuesAccInfoTypes_HardwareVersion: + check_ret(token_span.size == 3, -IAPAckStatus_EBadParameter); + break; + case IAPFIDTokenValuesAccInfoTypes_Manufacture: + break; + case IAPFIDTokenValuesAccInfoTypes_ModelNumber: + break; + case IAPFIDTokenValuesAccInfoTypes_SerialNumber: + break; + case IAPFIDTokenValuesAccInfoTypes_MaxPayloadSize: { + uint16_t val; + check_ret(iap_span_read_16(&token_span, &val), -IAPAckStatus_EBadParameter); + } break; + case IAPFIDTokenValuesAccInfoTypes_AccStatus: { + uint32_t val; + check_ret(iap_span_read_32(&token_span, &val), -IAPAckStatus_EBadParameter); + } break; + case IAPFIDTokenValuesAccInfoTypes_RFCerts: { + uint32_t val; + check_ret(iap_span_read_32(&token_span, &val), -IAPAckStatus_EBadParameter); + } break; + } + pack_accepted(IAPFIDTokenValuesAccInfoAck); + ack->info_type = token->info_type; + } break; + case IAPFIDTokenTypes_IPodPreference: { + const struct IAPFIDTokenValuesIPodPreferenceToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesIPodPreferenceAck); + ack->class_id = token->class_id; + } break; + case IAPFIDTokenTypes_EAProtocol: { + const struct IAPFIDTokenValuesEAProtocolToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesEAProtocolAck); + ack->protocol_index = token->protocol_index; + } break; + case IAPFIDTokenTypes_BundleSeedIDPref: { + const struct IAPFIDTokenValuesBundleSeedIDPrefToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesBundleSeedIDPrefAck); + } break; + case IAPFIDTokenTypes_ScreenInfo: { + const struct IAPFIDTokenValuesScreenInfoToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesScreenInfoAck); + } break; + case IAPFIDTokenTypes_EAProtocolMetadata: { + const struct IAPFIDTokenValuesEAProtocolMetadataToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesEAProtocolMetadataAck); + } break; + case IAPFIDTokenTypes_AccDigitalAudioSampleRates: { + const struct IAPFIDTokenValuesAccDigitalAudioSampleRatesToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + while(token_span.size > 0) { + uint32_t rate; + check_ret(iap_span_read_32(&token_span, &rate), -IAPAckStatus_EBadParameter); + } + pack_accepted(IAPFIDTokenValuesAccDigitalAudioSampleRatesAck); + } break; + case IAPFIDTokenTypes_AccDigitalAudioVideoDelay: { + const struct IAPFIDTokenValuesAccDigitalAudioVideoDelayToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesAccDigitalAudioVideoDelayAck); + } break; + case IAPFIDTokenTypes_MicrophoneCaps: { + const struct IAPFIDTokenValuesMicrophoneCapsToken* token = iap_span_read(&token_span, sizeof(*token)); + check_ret(token != NULL, -IAPAckStatus_EBadParameter); + pack_accepted(IAPFIDTokenValuesMicrophoneCapsAck); + } break; + default: + warn("unknown fid %04X", token_header->type << 8 | token_header->subtype); + return -IAPAckStatus_EBadParameter; + } + } + return 0; +} diff --git a/firmware/usbstack/iap/libiap/hid.c b/firmware/usbstack/iap/libiap/hid.c new file mode 100644 index 0000000000..4be2ff389c --- /dev/null +++ b/firmware/usbstack/iap/libiap/hid.c @@ -0,0 +1,181 @@ +#include + +#include "constants.h" +#include "iap.h" +#include "macros.h" +#include "platform.h" +#include "spec/hid.h" + +struct ReportSize { + uint8_t id; + uint16_t size; /* including link control byte */ +}; + +/* must match to in-driver hid report descriptor */ + +static struct ReportSize input_report_size_table_fs[] = { + {.id = 0x01, .size = 0x0C}, + {.id = 0x02, .size = 0x0E}, + {.id = 0x03, .size = 0x14}, + {.id = 0x04, .size = 0x3F}, +}; + +static struct ReportSize output_report_size_table_fs[] = { + {.id = 0x05, .size = 0x08}, + {.id = 0x06, .size = 0x0A}, + {.id = 0x07, .size = 0x0E}, + {.id = 0x08, .size = 0x14}, + {.id = 0x09, .size = 0x3F}, +}; + +static struct ReportSize input_report_size_table_hs[] = { + {.id = 0x01, .size = 0x0005}, + {.id = 0x02, .size = 0x0009}, + {.id = 0x03, .size = 0x000D}, + {.id = 0x04, .size = 0x0011}, + {.id = 0x05, .size = 0x0019}, + {.id = 0x06, .size = 0x0031}, + {.id = 0x07, .size = 0x005F}, + {.id = 0x08, .size = 0x00C1}, + {.id = 0x09, .size = 0x0101}, + {.id = 0x0A, .size = 0x0181}, + {.id = 0x0B, .size = 0x0201}, + {.id = 0x0C, .size = 0x02FF}, +}; + +static struct ReportSize output_report_size_table_hs[] = { + {.id = 0x0D, .size = 0x05}, + {.id = 0x0E, .size = 0x09}, + {.id = 0x1F, .size = 0x0D}, + {.id = 0x10, .size = 0x11}, + {.id = 0x11, .size = 0x19}, + {.id = 0x12, .size = 0x31}, + {.id = 0x13, .size = 0x5F}, + {.id = 0x14, .size = 0xC1}, + {.id = 0x15, .size = 0xFF}, +}; + +static int find_output_report_size(struct IAPContext* ctx, uint8_t id) { + const struct ReportSize* ptr = ctx->opts.usb_highspeed ? output_report_size_table_hs : output_report_size_table_fs; + const size_t len = ctx->opts.usb_highspeed ? array_size(output_report_size_table_hs) : array_size(output_report_size_table_fs); + for(size_t i = 0; i < len; i += 1) { + if(ptr[i].id == id) { + return ptr[i].size; + } + } + return -1; +} + +IAPBool iap_feed_hid_report(struct IAPContext* ctx, const uint8_t* const data, const size_t size) { + check_ret(size > sizeof(struct IAPHIDReport), iap_false); + struct IAPHIDReport* report = (struct IAPHIDReport*)data; + + int report_size; + if(ctx->opts.ignore_hid_report_id) { + report_size = size - 1; + } else { + report_size = find_output_report_size(ctx, report->report_id); + check_ret(report_size == (int)size - 1, iap_false, "%d != %d", report_size, (int)size - 1); + } + + const uint8_t payload_size = report_size - 1; + check_act(ctx->hid_recv_buf_cursor + payload_size <= HID_BUFFER_SIZE, { ctx->hid_recv_buf_cursor = 0; return iap_false; }, "hid buffer overflow"); + if(!(report->link_control & IAPHIDReportLinkControlBits_Continue)) { + /* not continue, first packet */ + if(ctx->hid_recv_buf_cursor != 0) { + warn("not continue and cursor was set"); + ctx->hid_recv_buf_cursor = 0; + } + } else { + check_ret(ctx->hid_recv_buf_cursor > 0, iap_false); + } + memcpy(ctx->hid_recv_buf + ctx->hid_recv_buf_cursor, report->data, payload_size); + ctx->hid_recv_buf_cursor += payload_size; + if(!(report->link_control & IAPHIDReportLinkControlBits_MoreToFollow)) { + /* no more to follow, last packet */ + const IAPBool ret = _iap_feed_packet(ctx, ctx->hid_recv_buf, ctx->hid_recv_buf_cursor); + ctx->hid_recv_buf_cursor = 0; + check_ret(ret, iap_false); + } + return iap_true; +} + +IAPBool iap_notify_send_complete(struct IAPContext* ctx) { + ctx->send_busy = iap_false; + check_ret(_iap_send_next_report(ctx), iap_false); + return iap_true; +} + +IAPBool _iap_send_hid_reports(struct IAPContext* ctx, size_t begin, size_t end) { + if(ctx->send_buf_sending_cursor < ctx->send_buf_sending_range_end) { + warn("another transmission in progress, aborting it"); + } + ctx->send_buf_sending_cursor = begin; + ctx->send_buf_sending_range_begin = begin; + ctx->send_buf_sending_range_end = end; + if(!ctx->send_busy) { + check_ret(_iap_send_next_report(ctx), iap_false); + } + return iap_true; +} + +static const struct ReportSize* find_optimal_report_size(struct IAPContext* ctx, size_t size) { + const struct ReportSize* ptr = ctx->opts.usb_highspeed ? input_report_size_table_hs : input_report_size_table_fs; + const size_t len = ctx->opts.usb_highspeed ? array_size(input_report_size_table_hs) : array_size(input_report_size_table_fs); + for(size_t i = 0; i < len; i += 1) { + if(ptr[i].size >= size + 1 /* link control byte*/) { + return &ptr[i]; + } + } + return &ptr[len - 1]; +} + +IAPBool _iap_send_next_report(struct IAPContext* ctx) { + if(ctx->send_buf_sending_cursor >= ctx->send_buf_sending_range_end) { + if(ctx->on_send_complete != NULL) { + IAPOnSendComplete cb = ctx->on_send_complete; + ctx->on_send_complete = NULL; + check_ret(cb(ctx), iap_false); + } else if(ctx->active_events[0].callback != NULL) { + struct IAPActiveEvent event = ctx->active_events[0]; + /* shift queue */ + size_t i = 0; + for(; i < array_size(ctx->active_events) - 1 && ctx->active_events[i + 1].callback != NULL; i += 1) { + ctx->active_events[i] = ctx->active_events[i + 1]; + } + ctx->active_events[i].callback = NULL; + /* process event */ + check_ret(event.callback(ctx, &event), iap_false); + } else if(ctx->flushing_notifications) { + check_ret(_iap_flush_notification(ctx), iap_false); + } + return iap_true; + } + + check_ret(!ctx->send_busy, iap_false); + + const size_t send_buf_left = ctx->send_buf_sending_range_end - ctx->send_buf_sending_cursor; + const struct ReportSize* const report_size = find_optimal_report_size(ctx, send_buf_left); + const size_t take_size = min((size_t)report_size->size - 1 /* link control */, send_buf_left); + + struct IAPHIDReport* const report = (struct IAPHIDReport*)ctx->hid_send_staging_buf; + + report->report_id = report_size->id; + + const IAPBool is_first = ctx->send_buf_sending_cursor == ctx->send_buf_sending_range_begin; + const IAPBool is_last = ctx->send_buf_sending_cursor + take_size == ctx->send_buf_sending_range_end; + report->link_control = + (!is_first ? IAPHIDReportLinkControlBits_Continue : 0) | + (!is_last ? IAPHIDReportLinkControlBits_MoreToFollow : 0); + + memcpy(report->data, ctx->send_buf + ctx->send_buf_sending_cursor, take_size); + memset(report->data + take_size, 0, report_size->size - 1 - take_size); /* clear rest */ + + ctx->send_buf_sending_cursor += take_size; + ctx->send_busy = iap_true; + + const size_t send_size = 1 + report_size->size; + check_ret(iap_platform_send_hid_report(ctx, report, send_size) == (int)send_size, iap_false); + + return iap_true; +} diff --git a/firmware/usbstack/iap/libiap/iap.c b/firmware/usbstack/iap/libiap/iap.c new file mode 100644 index 0000000000..b5f700b0a2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/iap.c @@ -0,0 +1,1361 @@ +#include +#include + +#include "constants.h" +#include "endian.h" +#include "iap.h" +#include "macros.h" +#include "pack-util.h" +#include "platform.h" +#include "span.h" +#include "spec/iap.h" +#include "unaligned.h" + +enum TransIDSupport { + TransIDUnknown, + TransIDSupported, + TransIDNotSupported, +}; + +static struct IAPContextButtons parse_context_button_bits(uint8_t bits[4], struct IAPContextButtons* current) { + struct IAPContextButtons new; + struct IAPContextButtons released; + +#define process(field, Mask, bits) \ + new.field = !!(bits & IAPContextButtonStatusButtons_##Mask); \ + released.field = current->field && !new.field; + + process(play_pause, PlayPause, bits[0]); + process(volume_up, VolumeUp, bits[0]); + process(volume_down, VolumeDown, bits[0]); + process(next_track, NextTrack, bits[0]); + process(prev_track, PrevTrack, bits[0]); + process(next_album, NextAlbum, bits[0]); + process(prev_album, PrevAlbum, bits[0]); + process(stop, Stop, bits[0]); + process(play, PlayResume, bits[1]); + process(pause, Pause, bits[1]); + process(mute_toggle, MuteToggle, bits[1]); + process(next_chapter, NextChapter, bits[1]); + process(prev_chapter, PrevChapter, bits[1]); + process(next_playlist, NextPlaylist, bits[1]); + process(prev_playlist, PrevPlaylist, bits[1]); + process(shuffle_advance, ShuffleSettingAdvance, bits[1]); + process(repeat_advance, RepeatSettingAdvance, bits[2]); + +#undef process + + *current = new; + return released; +} + +IAPBool iap_init_ctx(struct IAPContext* ctx, struct IAPOpts opts, void* platform) { + const uint16_t max_input_hid_desc_size = opts.usb_highspeed ? 0x02FF : 0x3F; + + memset(ctx, 0, sizeof(*ctx)); + ctx->opts = opts; + ctx->platform = platform; + + ctx->hid_recv_buf = iap_platform_malloc(ctx, HID_BUFFER_SIZE, 0); + check_ret(ctx->hid_recv_buf != NULL, iap_false); + ctx->send_buf = iap_platform_malloc(ctx, SEND_BUFFER_SIZE, 0); + check_ret(ctx->send_buf != NULL, iap_false); + ctx->handling_trans_id = -1; + ctx->trans_id_support = TransIDUnknown; + ctx->hid_send_staging_buf = iap_platform_malloc(ctx, max_input_hid_desc_size + 1 /* report id */, IAPPlatformMallocFlags_Uncached); + check_ret(ctx->hid_send_staging_buf != NULL, iap_false); + ctx->phase = IAPPhase_Connected; + return iap_true; +} + +IAPBool iap_deinit_ctx(struct IAPContext* ctx) { + if(ctx->artwork.valid) { + iap_platform_close_artwork(ctx, &ctx->artwork); + } + iap_platform_free(ctx, ctx->hid_send_staging_buf); + iap_platform_free(ctx, ctx->hid_recv_buf); + iap_platform_free(ctx, ctx->send_buf); + return iap_true; +} + +#define read_request(Type) \ + const struct Type* request = iap_span_read(request_span, sizeof(*request)); \ + check_ret(request != NULL, -IAPAckStatus_EBadParameter); + +#define alloc_response_extra(Type, extra) \ + struct Type* response = iap_span_alloc(response_span, sizeof(*response) + extra); \ + check_ret(response != NULL, -IAPAckStatus_EOutOfResource); + +#define alloc_response(Type) alloc_response_extra(Type, 0) + +static uint32_t play_stage_change_notification_set_mask_to_type_mask(uint32_t mask) { + uint32_t ret = 0; + if(mask & IAPStatusChangeNotificationBits_Basic) { + ret |= 1 << IAPStatusChangeNotificationType_PlaybackStopped | + 1 << IAPStatusChangeNotificationType_PlaybackFEWSeekStop | + 1 << IAPStatusChangeNotificationType_PlaybackREWSeekStop; + } + if(mask & IAPStatusChangeNotificationBits_Extended) { + ret |= 1 << IAPStatusChangeNotificationType_PlaybackStatusExtended; + } + if(mask & IAPStatusChangeNotificationBits_TrackIndex) { + ret |= 1 << IAPStatusChangeNotificationType_TrackIndex; + } + if(mask & IAPStatusChangeNotificationBits_TrackTimeOffsetMSec) { + ret |= 1 << IAPStatusChangeNotificationType_TrackTimeOffsetMSec; + } + if(mask & IAPStatusChangeNotificationBits_TrackTimeOffsetSec) { + ret |= 1 << IAPStatusChangeNotificationType_TrackTimeOffsetSec; + } + if(mask & IAPStatusChangeNotificationBits_PlaybackEngineContentsChanged) { + ret |= 1 << IAPStatusChangeNotificationType_PlaybackEngineContentsChanged; + } + return ret; +} + +static IAPBool send_artwork_chunk_cb(struct IAPContext* ctx) { + struct IAPSpan request = _iap_get_buffer_for_send_payload(ctx); + if(ctx->artwork_chunk_index == 0) { + struct IAPRetTrackArtworkDataFirstPayload* payload = iap_span_alloc(&request, sizeof(*payload)); + + payload->index = swap_16(ctx->artwork_chunk_index); + payload->pixel_format = ctx->artwork.color ? IAPArtworkPixelFormats_RGB565LE : IAPArtworkPixelFormats_Mono; + payload->pixel_width = swap_16(ctx->artwork.width); + payload->pixel_height = swap_16(ctx->artwork.height); + payload->inset_top_left_x = 0; + payload->inset_top_left_y = 0; + payload->inset_bottom_right_x = payload->pixel_width; + payload->inset_bottom_right_y = payload->pixel_height; + payload->stride = swap_32(ctx->artwork.width * 2); /* TODO: support stride */ + } else { + struct IAPRetTrackArtworkDataSubsequenctPayload* payload = iap_span_alloc(&request, sizeof(*payload)); + + payload->index = swap_16(ctx->artwork_chunk_index); + } + struct IAPSpan artwork; + size_t copy_size = 0; + if(!ctx->opts.artwork_single_report || ctx->artwork_chunk_index != 0) { + check_ret(iap_platform_get_artwork_ptr(ctx, &ctx->artwork, &artwork), iap_false); + check_ret(iap_span_read(&artwork, ctx->artwork_cursor) != NULL, iap_false); /* skip already read chunk */ + copy_size = min((ctx->opts.artwork_single_report ? 48 : request.size), artwork.size); + memcpy(iap_span_alloc(&request, copy_size), iap_span_read(&artwork, copy_size), copy_size); + } + check_ret(_iap_send_packet(ctx, ctx->artwork_data_lingo, ctx->artwork_data_command, ctx->artwork_trans_id, request.ptr), iap_false); + if(artwork.size > 0) { + /* more to send, ask to call again */ + ctx->artwork_cursor += copy_size; + ctx->artwork_chunk_index += 1; + ctx->on_send_complete = send_artwork_chunk_cb; + print("track artwork left %zu bytes", artwork.size); + } else { + /* finished, free artwork */ + check_ret(iap_platform_close_artwork(ctx, &ctx->artwork), iap_false); + ctx->artwork.valid = iap_false; + print("track artwork done"); + } + return iap_true; +} + +static int32_t start_artwork_data(struct IAPContext* ctx, struct IAPSpan* request_span, IAPBool ext) { + read_request(IAPGetTrackArtworkDataPayload); + check_ret(request->format_id == 0, -IAPAckStatus_EBadParameter); + check_ret(request->offset_ms == 0, -IAPAckStatus_EBadParameter); + check_ret(!ctx->artwork.valid, -IAPAckStatus_EBadParameter); + + check_ret(iap_platform_open_artwork(ctx, swap_32(request->track_index), &ctx->artwork), -IAPAckStatus_EBadParameter); + ctx->artwork.valid = iap_true; + ctx->artwork_cursor = 0; + ctx->artwork_chunk_index = 0; + ctx->artwork_trans_id = ctx->handling_trans_id; + if(ext) { + ctx->artwork_data_lingo = IAPLingoID_ExtendedInterface; + ctx->artwork_data_command = IAPExtendedInterfaceCommandID_RetTrackArtworkData; + } else { + ctx->artwork_data_lingo = IAPLingoID_DisplayRemote; + ctx->artwork_data_command = IAPDisplayRemoteCommandID_RetTrackArtworkData; + } + check_ret(send_artwork_chunk_cb(ctx), -IAPAckStatus_ECommandFailed); + return 0; +} + +static int32_t ipod_ack(uint16_t command, enum IAPAckStatus status, struct IAPSpan* response_span, uint16_t ret) { + alloc_response(IAPIPodAckPayload); + response->status = status; + response->id = command; + return ret; +} + +static int32_t ipod_ack_ext(uint16_t command, enum IAPAckStatus status, struct IAPSpan* response_span) { + alloc_response(IAPExtendedIPodAckPayload); + response->id = swap_16(command); + response->status = status; + return IAPExtendedInterfaceCommandID_IPodAck; +} + +static int32_t handle_command(struct IAPContext* ctx, uint8_t lingo, uint16_t command, struct IAPSpan* request_span, struct IAPSpan* response_span) { + switch(lingo) { + case IAPLingoID_General: + switch(command) { + case IAPGeneralCommandID_RequestExtendedInterfaceMode: { + alloc_response(IAPReturnExtendedInterfaceModePayload); + response->is_ext_mode = 1; + return IAPGeneralCommandID_ReturnExtendedInterfaceMode; + } break; + case IAPGeneralCommandID_RequestIPodSoftwareVersion: { + alloc_response(IAPReturnIPodSoftwareVersionPayload); + response->major = 18; + response->minor = 7; + response->revision = 2; + return IAPGeneralCommandID_ReturnIPodSoftwareVersion; + } break; + case IAPGeneralCommandID_RequestIPodSerialNum: { + check_ret(iap_platform_get_ipod_serial_num(ctx, response_span), -IAPAckStatus_ECommandFailed); + return IAPGeneralCommandID_ReturnIPodSerialNum; + } break; + case IAPGeneralCommandID_RequestIPodModelNum: { + static const char* model_num = "MTAY2J/A"; + check_ret(iap_span_append(response_span, model_num, strlen(model_num) + 1), -IAPAckStatus_EOutOfResource); + return IAPGeneralCommandID_ReturnIPodModelNum; + } break; + case IAPGeneralCommandID_RequestTransportMaxPayloadSize: { + alloc_response(IAPReturnTransportMaxPayloadSizePayload); + response->max_payload_size = swap_16(HID_BUFFER_SIZE - 1 /*sync*/ - 1 /*sof*/ - 3 /*length*/ - 1 /*checksum*/); + return IAPGeneralCommandID_ReturnTransportMaxPayloadSize; + } break; + case IAPGeneralCommandID_RequestLingoProtocolVersion: { + static const struct { + uint8_t major; + uint8_t minor; + } table[] = { + [IAPLingoID_General] = {1, 9}, + [IAPLingoID_Microphone] = {1, 1}, + [IAPLingoID_SimpleRemote] = {1, 4}, + [IAPLingoID_DisplayRemote] = {1, 5}, + [IAPLingoID_ExtendedInterface] = {1, 14}, + [IAPLingoID_AccessoryPower] = {1, 1}, + [IAPLingoID_USBHostMode] = {1, 0}, + [IAPLingoID_RFTuner] = {1, 1}, + [IAPLingoID_AccessoryEqualizer] = {1, 0}, + [IAPLingoID_Sports] = {1, 1}, + [IAPLingoID_DigitalAudio] = {1, 3}, + [IAPLingoID_Storage] = {1, 2}, + [IAPLingoID_IPodOut] = {1, 0}, + [IAPLingoID_Location] = {1, 0}, + }; + + read_request(IAPRequestLingoProtocolVersionPayload); + check_ret(request->lingo < array_size(table), -IAPAckStatus_EBadParameter); + + alloc_response(IAPReturnLingoProtocolVersionPayload); + response->lingo = request->lingo; + response->major = table[request->lingo].major; + response->minor = table[request->lingo].minor; + return IAPGeneralCommandID_ReturnLingoProtocolVersion; + } break; + case IAPGeneralCommandID_GetIPodOptions: { + alloc_response(IAPRetIPodOptionsPayload); + response->state = 0; + return IAPGeneralCommandID_RetIPodOptions; + } break; + case IAPGeneralCommandID_GetIPodPreferences: { + read_request(IAPGetIPodPreferencesPayload); + alloc_response(IAPRetIPodPreferencesPayload); + response->class_id = request->class_id; + response->setting_id = 0; /* TODO: return actual value */ + return IAPGeneralCommandID_RetIPodPreferences; + } break; + case IAPGeneralCommandID_SetIPodPreferences: { + read_request(IAPSetIPodPreferencesPayload); + /* TODO: handle preferences */ + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + case IAPGeneralCommandID_SetUIMode: { + read_request(IAPSetUIModePayload); + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + case IAPGeneralCommandID_GetIPodOptionsForLingo: { + read_request(IAPGetIPodOptionsForLingoPayload); + alloc_response(IAPRetIPodOptionsForLingoPayload); + response->lingo_id = request->lingo_id; + switch(request->lingo_id) { + case IAPLingoID_SimpleRemote: + response->bits = swap_64(IAPRetIPodOptionsForLingoSimpleRemoteBits_ContextSpecificControls); + break; + case IAPLingoID_General: + case IAPLingoID_DisplayRemote: + case IAPLingoID_ExtendedInterface: + case IAPLingoID_DigitalAudio: + case IAPLingoID_Storage: /* TODO: this is not supported */ + response->bits = 0; + break; + case IAPLingoID_USBHostMode: + case IAPLingoID_RFTuner: + case IAPLingoID_Sports: + case IAPLingoID_IPodOut: + case IAPLingoID_Location: { /* not supported */ + return -IAPAckStatus_EBadParameter; + } + } + return IAPGeneralCommandID_RetIPodOptionsForLingo; + } break; + case IAPGeneralCommandID_GetSupportedEventNotification: { + alloc_response(IAPRetSupportedEventNotificationPayload); + response->mask = swap_64(IAPSetEventNotificationEvents_FlowControl); + return IAPGeneralCommandID_RetSupportedEventNotification; + } break; + case IAPGeneralCommandID_SetAvailableCurrent: { + read_request(IAPSetAvailableCurrentPayload); + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + case IAPGeneralCommandID_SetEventNotification: { + read_request(IAPSetEventNotificationPayload); + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + } + break; + case IAPLingoID_SimpleRemote: + switch(command) { + case IAPSimpleRemoteCommandID_ContextButtonStatus: { + response_span->ptr = NULL; + + uint8_t bits[4] = {0}; + for(int i = 0; i < 4 && request_span->size > 0; i += 1) { + iap_span_read_8(request_span, &bits[i]); + } + const struct IAPContextButtons released = parse_context_button_bits(bits, &ctx->context_button_state); + const struct IAPPlatformPendingControl pending = { + .req_command = command, + .ack_command = -1, + .trans_id = ctx->handling_trans_id, + .lingo = lingo, + }; + const struct { + IAPBool released; + enum IAPPlatformControl control; + } table[] = { + {released.play_pause, IAPPlatformControl_TogglePlayPause}, + {released.volume_up, IAPPlatformControl_VolumeUp}, + {released.volume_down, IAPPlatformControl_VolumeDown}, + {released.next_track, IAPPlatformControl_Next}, + {released.prev_track, IAPPlatformControl_Prev}, + {released.next_album, IAPPlatformControl_Next}, + {released.prev_album, IAPPlatformControl_Prev}, + {released.stop, IAPPlatformControl_Stop}, + {released.play, IAPPlatformControl_Play}, + {released.pause, IAPPlatformControl_Pause}, + {released.mute_toggle, IAPPlatformControl_ToggleMute}, + {released.next_chapter, IAPPlatformControl_Next}, + {released.prev_chapter, IAPPlatformControl_Prev}, + {released.next_playlist, IAPPlatformControl_Next}, + {released.prev_playlist, IAPPlatformControl_Prev}, + }; + for(size_t i = 0; i < array_size(table); i += 1) { + if(table[i].released) { + iap_platform_control(ctx, table[i].control, pending); + } + } + if(released.shuffle_advance) { + uint8_t current; + check_act(iap_platform_get_shuffle_setting(ctx, ¤t), return 0); + current = (current + 1) % IAPIPodStateShuffleSettingState_Albums; + check_act(iap_platform_set_shuffle_setting(ctx, current), return 0); + } + if(released.repeat_advance) { + uint8_t current; + check_act(iap_platform_get_repeat_setting(ctx, ¤t), return 0); + current = (current + 1) % IAPIPodStateRepeatSettingState_All; + check_act(iap_platform_set_repeat_setting(ctx, current), return 0); + } + return 0; + } break; + } + break; + case IAPLingoID_DisplayRemote: + switch(command) { + case IAPDisplayRemoteCommandID_SetCurrentEQProfileIndex: { + read_request(IAPSetCurrentEQProfileIndexPayload); + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPDisplayRemoteCommandID_IPodAck); + } break; + case IAPDisplayRemoteCommandID_SetRemoteEventNotification: { + read_request(IAPSetRemoteEventNotificationPayload); + ctx->notifications_3 = 0; + ctx->enabled_notifications_3 = swap_32(request->mask); + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPDisplayRemoteCommandID_IPodAck); + } break; + case IAPDisplayRemoteCommandID_GetIPodStateInfo: { + read_request(IAPGetIPodStateInfoPayload); + check_ret(response_span->size >= sizeof(struct IAPIPodStatePayload), -IAPAckStatus_EOutOfResource); + ((struct IAPIPodStatePayload*)response_span->ptr)->type = request->type; + switch(request->type) { + case IAPIPodStateType_TrackTimePositionMSec: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateTrackTimePositionMSecPayload); + response->position_ms = swap_32(status.track_pos_ms); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_TrackPlaybackIndex: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateTrackPlaybackIndexPayload); + response->index = swap_32(status.track_index); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_ChapterIndex: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateChapterIndexPayload); + response->index = swap_32(status.track_index); + /* no chapters */ + response->chapter_count = 0; + response->chapter_index = -1; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_PlayStatus: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStatePlayStatusPayload); + response->status = status.state; /* TODO: convert enum */ + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_Volume: { + struct IAPPlatformVolumeStatus status; + check_ret(iap_platform_get_volume(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateVolumePayload); + response->mute_state = status.muted; + response->ui_volume = status.volume; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_Power: { + struct IAPPlatformPowerStatus status; + check_ret(iap_platform_get_power_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStatePowerPayload); + response->power_state = status.state; /* TODO: convert enum */ + response->battery_level = status.battery_level; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_EQSetting: { + alloc_response(IAPIPodStateEQSettingPayload); + /* no eq setting support yet */ + response->eq_index = 0; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_ShuffleSetting: { + alloc_response(IAPIPodStateShuffleSettingPayload); + check_ret(iap_platform_get_shuffle_setting(ctx, &response->shuffle_state), -IAPAckStatus_ECommandFailed); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_RepeatSetting: { + alloc_response(IAPIPodStateRepeatSettingPayload); + check_ret(iap_platform_get_repeat_setting(ctx, &response->repeat_state), -IAPAckStatus_ECommandFailed); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_DateTimeSetting: { + struct IAPDateTime time; + check_ret(iap_platform_get_date_time(ctx, &time), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateDateTimeSettingPayload); + response->year = swap_16(time.year); + response->month = time.month; + response->day = time.day; + response->hour = time.hour; + response->minute = time.minute; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_AlarmSetting: { + alloc_response(IAPIPodStateAlarmSettingPayload); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_BacklightLevel: { + alloc_response(IAPIPodStateBacklightLevelPayload); + check_ret(iap_platform_get_backlight_level(ctx, &response->level), -IAPAckStatus_ECommandFailed); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_HoldSwitchState: { + IAPBool state; + check_ret(iap_platform_get_hold_switch_state(ctx, &state), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateHoldSwitchStatePayload); + response->state = state; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_SoundCheckState: { + alloc_response(IAPIPodStateSoundCheckStatePayload); + response->state = 0; /* no sound check */ + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_AudiobookSpeeed: { + alloc_response(IAPIPodStateAudiobookSpeeedPayload); + response->speed = IAPIPodStateAudiobookSpeeed_Normal; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_TrackTimePositionSec: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateTrackTimePositionSecPayload); + response->position_s = swap_16(status.track_pos_ms / 1000); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_AbsoluteVolume: { + struct IAPPlatformVolumeStatus status; + check_ret(iap_platform_get_volume(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateAbsoluteVolumePayload); + response->mute_state = status.muted; + response->ui_volume = status.volume; + response->absolute_volume = status.volume; + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_TrackCaps: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPIPodStateTrackCapsPayload); + response->caps = swap_32(status.track_caps); + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + case IAPIPodStateType_PlaybackEngineContents: { + alloc_response(IAPIPodStatePlaybackEngineContentsPayload); + response->count = 0; /* TODO: shoud be supported? */ + return IAPDisplayRemoteCommandID_RetIPodStateInfo; + } break; + default: + warn("invalid request type 0x%02" PRIX8, request->type); + return -IAPAckStatus_EBadParameter; + } + } break; + case IAPDisplayRemoteCommandID_GetPlayStatus: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPRetPlayStatusPayload); + response->state = status.state; /* TODO: convert enum */ + response->track_index = swap_32(status.track_index); + response->track_pos_ms = swap_32(status.track_pos_ms); + response->track_total_ms = swap_32(status.track_total_ms); + return IAPDisplayRemoteCommandID_RetPlayStatus; + } break; + case IAPDisplayRemoteCommandID_GetIndexedPlayingTrackInfo: { + read_request(IAPGetIndexedPlayingTrackInfoPayload); + check_ret(response_span->size > sizeof(struct IAPRetIndexedPlayingTrackInfoPayload), -IAPAckStatus_EBadParameter); + ((struct IAPRetIndexedPlayingTrackInfoPayload*)response_span->ptr)->type = request->type; + switch(request->type) { + case IAPIndexedPlayingTrackInfoType_TrackCapsInfo: { + uint32_t length; + uint32_t caps; + struct IAPPlatformTrackInfo info = {.total_ms = &length, .caps = &caps}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + alloc_response(IAPRetIndexedPlayingTrackInfoTrackCapsInfoPayload); + response->track_caps = swap_32(caps); + response->track_total_ms = swap_32(length); + response->chapter_count = 0; + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_ChapterTimeName: { + return -IAPAckStatus_EBadParameter; + } break; + case IAPIndexedPlayingTrackInfoType_ArtistName: { + alloc_response(IAPRetIndexedPlayingTrackInfoArtistNamePayload); + struct IAPPlatformTrackInfo info = {.artist = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_AlbumName: { + alloc_response(IAPRetIndexedPlayingTrackInfoAlbumNamePayload); + struct IAPPlatformTrackInfo info = {.album = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_GenreName: { + alloc_response_extra(IAPRetIndexedPlayingTrackInfoGenreNamePayload, 1); + response->name[0] = '\0'; + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_TrackTitle: { + alloc_response(IAPRetIndexedPlayingTrackInfoTrackTitlePayload); + struct IAPPlatformTrackInfo info = {.title = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_ComposerName: { + alloc_response(IAPRetIndexedPlayingTrackInfoComposerNamePayload); + struct IAPPlatformTrackInfo info = {.composer = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_Lyrics: { + alloc_response_extra(IAPRetIndexedPlayingTrackInfoLyricsPayload, 1); + response->info_bits = 0; + response->index = 0; + response->lyrics[0] = '\0'; + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + case IAPIndexedPlayingTrackInfoType_ArtworkCount: { + alloc_response_extra(IAPRetIndexedPlayingTrackInfoArtworkCountPayload, sizeof(struct IAPArtworkCount)); + response->data[0].format = 0; + response->data[0].count = swap_16(1); + return IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo; + } break; + } + } break; + case IAPDisplayRemoteCommandID_GetArtworkFormats: { + alloc_response_extra(IAPArtworkFormat, sizeof(struct IAPArtworkFormat)); + response->format_id = 0; + response->pixel_format = IAP_COLOR_ARTWORK ? IAPArtworkPixelFormats_RGB565LE : IAPArtworkPixelFormats_Mono; + response->image_width = swap_16(IAP_ARTWORK_WIDTH); + response->image_height = swap_16(IAP_ARTWORK_HEIGHT); + return IAPDisplayRemoteCommandID_RetArtworkFormats; + } break; + case IAPDisplayRemoteCommandID_GetNumPlayingTracks: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPRetNumPlayingTracksPayload); + response->num_playing_tracks = swap_32(status.track_count); + return IAPDisplayRemoteCommandID_RetNumPlayingTracks; + } break; + case IAPDisplayRemoteCommandID_GetTrackArtworkData: { + const int32_t ret = start_artwork_data(ctx, request_span, iap_false); + check_ret(ret == 0, ret); + /* responded in send_artwork_chunk_cb, no need to do it here */ + response_span->ptr = NULL; + return 0; + } break; + case IAPDisplayRemoteCommandID_GetPowerBatteryState: { + struct IAPPlatformPowerStatus status; + check_ret(iap_platform_get_power_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPRetPowerBatteryStatePayload); + response->power_state = status.state; /* TODO: convert enum */ + response->battery_level = status.battery_level; + return IAPDisplayRemoteCommandID_RetPowerBatteryState; + } break; + case IAPDisplayRemoteCommandID_GetTrackArtworkTimes: { + read_request(IAPGetTrackArtworkTimesPayload); + const uint16_t count = swap_16(request->artwork_count); + check_ret(count == 0 || count == 1, -IAPAckStatus_ECommandFailed, "not implemented"); + + void* payload = iap_span_alloc(response_span, sizeof(uint32_t) * count); + check_ret(payload != NULL, iap_false); + memset(payload, 0, sizeof(uint32_t) * count); + return IAPDisplayRemoteCommandID_RetTrackArtworkTimes; + } break; + } + break; + case IAPLingoID_ExtendedInterface: + switch(command) { + case IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackChapterInfo: { + alloc_response(IAPReturnCurrentPlayingTrackChapterInfoPayload); + /* no chapters */ + response->count = 0; + response->index = -1; + return IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackChapterInfo; + } break; + case IAPExtendedInterfaceCommandID_GetAudiobookSpeed: { + alloc_response(IAPRetAudiobookSpeedPayload); + response->speed = IAPIPodStateAudiobookSpeeed_Normal; + return IAPExtendedInterfaceCommandID_RetAudiobookSpeed; + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackInfo: { + read_request(IAPExtendedGetIndexedPlayingTrackInfoPayload); + check_ret(response_span->size > sizeof(struct IAPExtendedRetIndexedPlayingTrackInfoPayload), -IAPAckStatus_EBadParameter); + ((struct IAPExtendedRetIndexedPlayingTrackInfoPayload*)response_span->ptr)->type = request->type; + switch(request->type) { + case IAPExtendedIndexedPlayingTrackInfoType_TrackCapsInfo: { + uint32_t length; + uint32_t caps; + struct IAPPlatformTrackInfo info = {.total_ms = &length, .caps = &caps}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + alloc_response(IAPExtendedRetIndexedPlayingTrackInfoTrackCapsInfoPayload); + response->track_caps = swap_32(caps); + response->track_total_ms = swap_32(length); + response->chapter_count = 0; + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_PodcastName: { + alloc_response_extra(IAPExtendedRetIndexedPlayingTrackInfoPodcastNamePayload, 1); + response->name[0] = '\0'; + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackReleaseDate: { + struct IAPDateTime time; + struct IAPPlatformTrackInfo info = {.release_date = &time}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + alloc_response(IAPExtendedRetIndexedPlayingTrackInfoTrackReleaseDatePayload); + response->seconds = time.seconds; + response->minutes = time.minute; + response->hours = time.hour; + response->day = time.day; + response->month = time.month; + response->year = swap_16(time.year); + response->weekday = 0; /* TODO: set weekday? */ + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackDescription: { + alloc_response_extra(IAPExtendedRetIndexedPlayingTrackInfoTrackDescriptionPayload, 1); + response->info_bits = 0; + response->index = 0; + response->description[0] = '\0'; + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackSongLyrics: { + alloc_response_extra(IAPExtendedRetIndexedPlayingTrackInfoTrackSongLyricsPayload, 1); + response->info_bits = 0; + response->index = 0; + response->lyrics[0] = '\0'; + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackGenre: { + alloc_response_extra(IAPExtendedRetIndexedPlayingTrackInfoTrackGenrePayload, 1); + response->genre[0] = '\0'; + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackComposer: { + alloc_response(IAPExtendedRetIndexedPlayingTrackInfoTrackComposerPayload); + struct IAPPlatformTrackInfo info = {.composer = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->track_index), &info), -IAPAckStatus_EBadParameter); + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo; + } break; + case IAPExtendedIndexedPlayingTrackInfoType_TrackArtworkCount: { + // IAPArtworkPixelFormats_RGB565LE; + // struct IAPExtendedRetIndexedPlayingTrackInfoTrackArtworkCountPayload payload = { + // .}; + warn("artwork not implemented"); + return -IAPAckStatus_ECommandFailed; + } break; + default: + warn("invalid request type 0x%02" PRIX8, request->type); + return -IAPAckStatus_EBadParameter; + } + } break; + case IAPExtendedInterfaceCommandID_GetArtworkFormats: { + /* same as DisplayRemote::GetArtworkFormats */ + const int32_t ret = handle_command(ctx, IAPLingoID_DisplayRemote, IAPDisplayRemoteCommandID_GetArtworkFormats, request_span, response_span); + check_ret(ret == IAPDisplayRemoteCommandID_RetArtworkFormats, ret); + return IAPExtendedInterfaceCommandID_RetArtworkFormats; + } break; + case IAPExtendedInterfaceCommandID_GetTrackArtworkData: { + const int32_t ret = start_artwork_data(ctx, request_span, iap_true); + check_ret(ret == 0, ret); + /* responded in send_artwork_chunk_cb, no need to do it here */ + response_span->ptr = NULL; + return 0; + } break; + case IAPExtendedInterfaceCommandID_ResetDBSelection: { + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_GetNumberCategorizedDBRecords: { + read_request(IAPGetNumberCategorizedDBRecordsPayload); + uint32_t count; + switch(request->type) { + case IAPDatabaseType_Playlist: { + /* TODO: implement platform callback */ + count = 99; + } break; + case IAPDatabaseType_Track: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + /* track_count is invalid while stopped. + * return non-zero dummy value in this case, because reporting zero tracks + * may cause empty library error. */ + /* TODO: maybe add dedicated platform callback? */ + count = status.state == IAPIPodStatePlayStatus_PlaybackStopped ? 99 : status.track_count; + } break; + default: { + warn("unsupported type 0x%02" PRIX8, request->type); + count = 0; + } break; + } + + alloc_response(IAPReturnNumberCategorizedDBRecordsPayload); + response->count = swap_32(count); + return IAPExtendedInterfaceCommandID_ReturnNumberCategorizedDBRecords; + } break; + case IAPExtendedInterfaceCommandID_GetPlayStatus: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPExtendedRetPlayStatusPayload); + response->state = status.state; /* TODO: convert enum */ + response->track_pos_ms = swap_32(status.track_pos_ms); + response->track_total_ms = swap_32(status.track_total_ms); + return IAPExtendedInterfaceCommandID_ReturnPlayStatus; + } break; + case IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackIndex: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPReturnCurrentPlayingTrackIndexPayload); + response->index = swap_32(status.state == IAPIPodStatePlayStatus_PlaybackStopped ? (uint32_t)-1 : status.track_index); + return IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackIndex; + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackTitle: { + read_request(IAPGetIndexedPlayingTrackStringPayload); + struct IAPPlatformTrackInfo info = {.title = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->index), &info), -IAPAckStatus_ECommandFailed); + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackTitle; + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackArtistName: { + read_request(IAPGetIndexedPlayingTrackStringPayload); + struct IAPPlatformTrackInfo info = {.artist = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->index), &info), -IAPAckStatus_ECommandFailed); + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackArtistName; + } break; + case IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackAlbumName: { + read_request(IAPGetIndexedPlayingTrackStringPayload); + struct IAPPlatformTrackInfo info = {.album = response_span}; + check_ret(iap_platform_get_indexed_track_info(ctx, swap_32(request->index), &info), -IAPAckStatus_ECommandFailed); + return IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackAlbumName; + } break; + case IAPExtendedInterfaceCommandID_SetPlayStatusChangeNotification: { + if(request_span->size == sizeof(struct IAPSetPlayStatusChangeNotification1BytePayload)) { + read_request(IAPSetPlayStatusChangeNotification1BytePayload); + ctx->notifications_4 = 0; + if(request->enable) { + ctx->enabled_notifications_4 = 1 << IAPStatusChangeNotificationType_PlaybackStopped | + 1 << IAPStatusChangeNotificationType_TrackIndex | + 1 << IAPStatusChangeNotificationType_PlaybackFEWSeekStop | + 1 << IAPStatusChangeNotificationType_PlaybackREWSeekStop | + 1 << IAPStatusChangeNotificationType_TrackTimeOffsetMSec | + 1 << IAPStatusChangeNotificationType_ChapterIndex; + } else { + ctx->enabled_notifications_4 = 0; + } + } else if(request_span->size == sizeof(struct IAPSetPlayStatusChangeNotification4BytesPayload)) { + read_request(IAPSetPlayStatusChangeNotification4BytesPayload); + ctx->enabled_notifications_4 = play_stage_change_notification_set_mask_to_type_mask(swap_32(request->mask)); + } + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_PlayCurrentSelection: { + read_request(IAPPlayCurrentSelectionPayload); + const struct IAPPlatformPendingControl pending = { + .req_command = command, + .ack_command = IAPExtendedInterfaceCommandID_IPodAck, + .trans_id = ctx->handling_trans_id, + .lingo = lingo, + }; + iap_platform_control(ctx, IAPPlatformControl_Play, pending); + response_span->ptr = NULL; + return 0; + } break; + case IAPExtendedInterfaceCommandID_PlayControl: { + read_request(IAPPlayControlPayload); + static const int enum_table[][2] = { + {IAPPlayControlCode_TogglePlayPause, IAPPlatformControl_TogglePlayPause}, + {IAPPlayControlCode_Stop, IAPPlatformControl_Stop}, + {IAPPlayControlCode_NextTrack, IAPPlatformControl_Next}, + {IAPPlayControlCode_PrevTrack, IAPPlatformControl_Prev}, + {IAPPlayControlCode_StartFF, -1}, + {IAPPlayControlCode_StartRew, -1}, + {IAPPlayControlCode_EndFFRew, -1}, + {IAPPlayControlCode_Next, IAPPlatformControl_Next}, + {IAPPlayControlCode_Prev, IAPPlatformControl_Prev}, + {IAPPlayControlCode_Play, IAPPlatformControl_Play}, + {IAPPlayControlCode_Pause, IAPPlatformControl_Pause}, + {IAPPlayControlCode_NextChapter, IAPPlatformControl_Next}, + {IAPPlayControlCode_PrevChapter, IAPPlatformControl_Prev}, + {IAPPlayControlCode_ResumeIPod, -1}, + }; + int control = -1; + for(size_t i = 0; i < array_size(enum_table); i += 1) { + if(enum_table[i][0] == request->code) { + control = enum_table[i][1]; + break; + } + } + if(control >= 0) { + const struct IAPPlatformPendingControl pending = { + .req_command = command, + .ack_command = IAPExtendedInterfaceCommandID_IPodAck, + .trans_id = ctx->handling_trans_id, + .lingo = lingo, + }; + iap_platform_control(ctx, control, pending); + response_span->ptr = NULL; + return 0; + } + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_GetShuffle: { + alloc_response(IAPReturnShufflePayload); + check_ret(iap_platform_get_shuffle_setting(ctx, &response->mode), -IAPAckStatus_ECommandFailed); + return IAPExtendedInterfaceCommandID_ReturnShuffle; + } break; + case IAPExtendedInterfaceCommandID_SetShuffle: { + read_request(IAPSetShufflePayload); + check_ret(iap_platform_set_shuffle_setting(ctx, request->mode), -IAPAckStatus_ECommandFailed); + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_GetRepeat: { + alloc_response(IAPReturnRepeatPayload); + check_ret(iap_platform_get_repeat_setting(ctx, &response->mode), -IAPAckStatus_ECommandFailed); + return IAPExtendedInterfaceCommandID_ReturnRepeat; + } break; + case IAPExtendedInterfaceCommandID_SetRepeat: { + read_request(IAPSetRepeatPayload); + check_ret(iap_platform_set_repeat_setting(ctx, request->mode), -IAPAckStatus_ECommandFailed); + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_SetDisplayImage: { + /* TODO: pass downloaded image to user */ + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_GetNumPlayingTracks: { + struct IAPPlatformPlayStatus status; + check_ret(iap_platform_get_play_status(ctx, &status), -IAPAckStatus_ECommandFailed); + alloc_response(IAPRetNumPlayingTracksPayload); + response->num_playing_tracks = swap_32(status.track_count); + return IAPExtendedInterfaceCommandID_ReturnNumPlayingTracks; + } break; + case IAPExtendedInterfaceCommandID_SetCurrentPlayingTrack: { + read_request(IAPSetCurrentPlayingTrackPayload); + check_ret(iap_platform_set_playing_track(ctx, swap_32(request->index)), -IAPAckStatus_ECommandFailed); + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_SelectSortDBRecord: { + /* ignored */ + return ipod_ack_ext(command, IAPAckStatus_Success, response_span); + } break; + case IAPExtendedInterfaceCommandID_GetColorDisplayImageLimits: { + alloc_response(IAPColorDisplayImageLimit); + response->max_width = 0; + response->max_height = 0; + response->pixel_format = IAPArtworkPixelFormats_RGB565LE; + return IAPExtendedInterfaceCommandID_ReturnColorDisplayImageLimits; + } break; + } + break; + case IAPLingoID_DigitalAudio: + switch(command) { + case IAPDigitalAudioCommandID_AccessoryAck: { + read_request(IAPAccAckPayload); + response_span->ptr = NULL; + if(request->id == IAPDigitalAudioCommandID_TrackNewAudioAttributes) { + check_ret(ctx->waiting_for_audio_attrs_ack, 0, "unexpected ack"); + ctx->waiting_for_audio_attrs_ack = iap_false; + } + check_ret(request->status == IAPAckStatus_Success, 0); + return 0; + } break; + case IAPDigitalAudioCommandID_RetAccessorySampleRateCaps: { + check_ret(iap_platform_on_acc_samprs_received(ctx, request_span), -IAPAckStatus_ECommandFailed); + response_span->ptr = NULL; /* no response */ + return 0; + } break; + } + break; + } + + return -IAPAckStatus_EUnknownID; +} + +static IAPBool transition_idps_to_auth_cb(struct IAPContext* ctx) { + print("starting accessory authentication"); + ctx->phase = IAPPhase_Auth; + check_ret(_iap_send_packet(ctx, IAPLingoID_General, IAPGeneralCommandID_GetAccessoryAuthenticationInfo, _iap_next_trans_id(ctx), _iap_get_buffer_for_send_payload(ctx).ptr), iap_false); + return iap_true; +} + +static int32_t handle_in_connected(struct IAPContext* ctx, uint8_t lingo, uint16_t command, struct IAPSpan* request_span, struct IAPSpan* response_span) { + switch(lingo) { + case IAPLingoID_General: + switch(command) { + case IAPGeneralCommandID_IdentifyDeviceLingoes: { + read_request(IAPIdentifyDeviceLingoesPayload); + switch(swap_32(request->options)) { + case IAPIdentifyDeviceLingoesOptions_NoAuth: + break; + case IAPIdentifyDeviceLingoesOptions_DeferAuth: + warn("unsupported option 0x%04" PRIX32, swap_32(request->options)); + return -IAPAckStatus_EBadParameter; + case IAPIdentifyDeviceLingoesOptions_ImmediateAuth: + ctx->on_send_complete = transition_idps_to_auth_cb; + break; + } + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + case IAPGeneralCommandID_StartIDPS: { + ctx->phase = IAPPhase_IDPS; + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } break; + } + break; + } + return -IAPAckStatus_EUnknownID; +} + +static int32_t handle_in_idps(struct IAPContext* ctx, uint8_t lingo, uint16_t command, struct IAPSpan* request_span, struct IAPSpan* response_span) { + switch(lingo) { + case IAPLingoID_General: + switch(command) { + case IAPGeneralCommandID_SetFIDTokenValues: { + const int ret = _iap_hanlde_set_fid_token_values(request_span, response_span); + check_ret(ret == 0, ret); + return IAPGeneralCommandID_AckFIDTokenValues; + } break; + case IAPGeneralCommandID_EndIDPS: { + read_request(IAPEndIDPSPayload); + check_ret(request->status == IAPEndIDPSStatus_Success, -IAPAckStatus_ECommandFailed); + ctx->on_send_complete = transition_idps_to_auth_cb; + alloc_response(IAPIDPSStatusPayload); + response->status = IAPIDPSStatus_Success; + return IAPGeneralCommandID_IDPSStatus; + } break; + } + break; + } + return iap_false; +} + +static IAPBool send_auth_challenge_sig_cb(struct IAPContext* ctx) { + check_ret(ctx->phase == IAPPhase_Auth, iap_false); + struct IAPSpan request_span = _iap_get_buffer_for_send_payload(ctx); + struct IAPGetAccAuthSigPayload2p0* request = iap_span_alloc(&request_span, sizeof(*request)); + check_ret(request != NULL, iap_false); + request->retry = 1; + check_ret(_iap_send_packet(ctx, IAPLingoID_General, IAPGeneralCommandID_GetAccessoryAuthenticationSignature, _iap_next_trans_id(ctx), request_span.ptr), iap_false); + return iap_true; +} + +static IAPBool send_sample_rate_caps_cb(struct IAPContext* ctx) { + struct IAPSpan request = _iap_get_buffer_for_send_payload(ctx); + check_ret(_iap_send_packet(ctx, IAPLingoID_DigitalAudio, IAPDigitalAudioCommandID_GetAccessorySampleRateCaps, _iap_next_trans_id(ctx), request.ptr), iap_false); + return iap_true; +} + +static int32_t handle_in_auth(struct IAPContext* ctx, uint8_t lingo, uint16_t command, struct IAPSpan* request_span, struct IAPSpan* response_span) { + switch(lingo) { + case IAPLingoID_General: + switch(command) { + case IAPGeneralCommandID_RetAccessoryAuthenticationInfo: { + read_request(IAPRetAccAuthInfoPayload2p0); + print("accessory cert %" PRIu8 "/%" PRIu8, request->cert_current_section_index, request->cert_max_section_index); + /* iap_platform_dump_hex(request->ptr, request->size); */ + if(request->cert_current_section_index < request->cert_max_section_index) { + return ipod_ack(command, IAPAckStatus_Success, response_span, IAPGeneralCommandID_IPodAck); + } else { + ctx->on_send_complete = send_auth_challenge_sig_cb; + alloc_response(IAPAckAccAuthInfoPayload); + response->status = IAPAckAccAuthInfoStatus_Supported; + return IAPGeneralCommandID_AckAccessoryAuthenticationInfo; + } + } break; + case IAPGeneralCommandID_RetAccessoryAuthenticationSignature: { + print("accessory signature"); + /* iap_platform_dump_hex(request->ptr, request->size); */ + + alloc_response(IAPAckAccAuthSigPayload); + response->status = IAPAckStatus_Success; + + ctx->phase = IAPPhase_Authed; + ctx->on_send_complete = send_sample_rate_caps_cb; + + return IAPGeneralCommandID_AckAccessoryAuthenticationStatus; + } break; + } + break; + } + return -IAPAckStatus_EUnknownID; +} + +static int32_t build_ipod_ack_response(uint8_t lingo, uint16_t command, uint8_t status, struct IAPSpan* response_span) { + static int32_t ack_commmand_ids[] = { + IAPGeneralCommandID_IPodAck, + -1, + IAPSimpleRemoteCommandID_IPodAck, + IAPDisplayRemoteCommandID_IPodAck, + IAPExtendedInterfaceCommandID_IPodAck, + -1, + IAPUSBHostModeCommandID_IPodAck, + -1, + -1, + IAPSportsCommandID_IPodAck, + IAPDigitalAudioCommandID_IPodAck, + -1, + IAPStorageCommandID_IPodAck, + IAPIPodOutCommandID_IPodAck, + IAPLocationCommandID_IPodAck, + }; + check_ret(lingo < array_size(ack_commmand_ids) && ack_commmand_ids[lingo] >= 0, -1); + switch(lingo) { + case IAPLingoID_General: + case IAPLingoID_SimpleRemote: + case IAPLingoID_DisplayRemote: + case IAPLingoID_USBHostMode: + case IAPLingoID_Sports: + case IAPLingoID_DigitalAudio: + case IAPLingoID_IPodOut: + case IAPLingoID_Location: { + alloc_response(IAPIPodAckPayload); + response->id = command; + response->status = status; + } break; + case IAPLingoID_ExtendedInterface: { + alloc_response(IAPExtendedIPodAckPayload); + response->id = swap_16(command); + response->status = status; + } break; + case IAPLingoID_Storage: { + alloc_response(IAPStorageIPodAckPayload); + response->id = command; + response->status = status; + response->handle = -1; /* TODO: set proper handle */ + } break; + } + return ack_commmand_ids[lingo]; +} + +IAPBool _iap_feed_packet(struct IAPContext* ctx, const uint8_t* const data, const size_t size) { + union { + uint8_t u8; + uint16_t u16; + } buf; + struct IAPSpan span = {(uint8_t*)data, size}; + + /* read sof byte */ + check_ret(iap_span_read_8(&span, &buf.u8), iap_false); + if(buf.u8 == IAP_SYNC_BYTE) { + /* skip sync byte */ + check_ret(iap_span_read_8(&span, &buf.u8), iap_false); + } + check_ret(buf.u8 == IAP_SOF_BYTE, iap_false, "%x != %x", buf.u8, IAP_SOF_BYTE); + const uint8_t* const checksum_range_begin = span.ptr; + /* read size */ + check_ret(iap_span_read_8(&span, &buf.u8), iap_false); + size_t length; + if(buf.u8 == 0) { + /* long packet */ + check_ret(iap_span_read_16(&span, &buf.u16), iap_false); + length = buf.u16; + } else { + length = buf.u8; + } + /* we have length, strip span so that it contains lingo,command,payload */ + check_ret(span.size >= length + 1 /* checksum */, iap_false); + span.size = length; + /* verify checksum */ + const uint8_t* const checksum_range_end = span.ptr + span.size + 1 /* checksum */; + uint8_t checksum = 0; + for(const uint8_t* ptr = checksum_range_begin; ptr < checksum_range_end; ptr += 1) { + checksum += *ptr; + } + check_ret(checksum == 0, iap_false); + /* read lingo id */ + check_ret(iap_span_read_8(&span, &buf.u8), iap_false); + uint8_t lingo = buf.u8; + /* read command id */ + uint16_t command; + if(lingo == IAPLingoID_ExtendedInterface) { + check_ret(iap_span_read_16(&span, &buf.u16), iap_false); + command = buf.u16; + } else { + check_ret(iap_span_read_8(&span, &buf.u8), iap_false); + command = buf.u8; + } + + /* now span contains only payload */ + + /* request handling */ + if(ctx->trans_id_support == TransIDUnknown) { + check_ret(lingo == IAPLingoID_General, iap_false); + if(command == IAPGeneralCommandID_StartIDPS) { + ctx->trans_id_support = TransIDSupported; + } else if(command == IAPGeneralCommandID_IdentifyDeviceLingoes) { + ctx->trans_id_support = TransIDNotSupported; + ctx->handling_trans_id = -1; + } else { + warn("the first command(%02X:%04X) must be StartIDPS or IdentifyDeviceLingoes", lingo, command); + return iap_false; + } + } + if(ctx->trans_id_support == TransIDSupported) { + check_ret(iap_span_read_16(&span, &buf.u16), iap_false); + ctx->handling_trans_id = buf.u16; + } + + if(ctx->opts.enable_packet_dump) { + IAP_LOGF("==== acc ===="); + _iap_dump_packet(lingo, command, ctx->handling_trans_id, span); + } + + struct IAPSpan response = _iap_get_buffer_for_send_payload(ctx); + int32_t ret = handle_command(ctx, lingo, command, &span, &response); + if(response.ptr == NULL) { + /* handler disabled response */ + return iap_true; + } + if(ret >= 0) { + /* handled successfully */ + goto respond; + } + if(ret != -IAPAckStatus_EUnknownID) { + /* handled, but error */ + goto error_ack; + } + + /* not a standard request, try authentication handlers */ + ret = -IAPAckStatus_EBadParameter; + response = _iap_get_buffer_for_send_payload(ctx); + switch(ctx->phase) { + case IAPPhase_Connected: + ret = handle_in_connected(ctx, lingo, command, &span, &response); + break; + case IAPPhase_IDPS: + ret = handle_in_idps(ctx, lingo, command, &span, &response); + break; + case IAPPhase_Auth: + ret = handle_in_auth(ctx, lingo, command, &span, &response); + break; + } + if(response.ptr == NULL) { + /* handler disabled response */ + return iap_true; + } + if(ret >= 0) { + /* handled successfully */ + goto respond; + } +error_ack: + /* handling failed, replace response with ipod ack */ + warn("command handling failed 0x%02X(%s):0x%04X", lingo, _iap_lingo_str(lingo), command); + response = _iap_get_buffer_for_send_payload(ctx); + ret = build_ipod_ack_response(lingo, command, -ret, &response); + check_ret(ret >= 0, iap_false); +respond: + check_ret(_iap_send_packet(ctx, lingo, ret, ctx->handling_trans_id, response.ptr), iap_false); + return iap_true; +} + +struct IAPSpan _iap_get_buffer_for_send_payload(struct IAPContext* ctx) { + const size_t header_size = 1 /* sof */ + + 3 /* long format length */ + + 1 /* lingo */ + + 2 /* largest command id */ + + 2 /* trans id */; + const size_t footer_size = 1 /* checksum */; + + struct IAPSpan buf = {ctx->send_buf + header_size, SEND_BUFFER_SIZE - header_size - footer_size}; + return buf; +} + +int32_t _iap_next_trans_id(struct IAPContext* ctx) { + if(ctx->trans_id_support == TransIDSupported) { + return ctx->trans_id += 1; + } else { + return -1; + } +} + +IAPBool _iap_send_packet(struct IAPContext* ctx, uint8_t lingo, uint16_t command, int32_t trans_id, uint8_t* final_ptr) { + uint8_t* ptr = _iap_get_buffer_for_send_payload(ctx).ptr; + size_t payload_size = final_ptr - ptr; + + if(ctx->opts.enable_packet_dump) { + IAP_LOGF("==== dev ===="); + struct IAPSpan payload_span = { + .ptr = _iap_get_buffer_for_send_payload(ctx).ptr, + .size = payload_size, + }; + _iap_dump_packet(lingo, command, trans_id, payload_span); + } + +#define pack_8(val) \ + ptr -= 1; \ + *(uint8_t*)ptr = val; +#define pack_16(val) \ + ptr -= 2; \ + *(uu16*)ptr = swap_16(val); + + /* fill header in reverse order */ + /* trans id */ + if(trans_id >= 0) { + pack_16(trans_id); + } + /* command id */ + if(lingo == IAPLingoID_ExtendedInterface) { + pack_16(command); + } else { + pack_8(command); + } + /* lingo */ + pack_8(lingo); + /* length */ + const uint16_t length = 1 /*lingo*/ + (lingo == IAPLingoID_ExtendedInterface ? 2 : 1) /*command*/ + (trans_id >= 0 ? 2 : 0) + payload_size; + if(length <= 0xFC) { + pack_8(length); + } else { + pack_16(length); + pack_8(0); + } + /* sof */ + pack_8(IAP_SOF_BYTE); + + /* set checksum */ + uint8_t checksum = 0; + for(uint8_t* p = ptr + 1 /* exclude sof byte */; p < final_ptr; p += 1) { + checksum += *p; + } + checksum *= -1; + *final_ptr = checksum; + + check_ret(_iap_send_hid_reports(ctx, ptr - ctx->send_buf, final_ptr - ctx->send_buf + 1 /* include checksum */), iap_false); + return iap_true; +} + +static IAPBool push_active_event(struct IAPContext* ctx, struct IAPActiveEvent event) { + if(!ctx->send_busy) { + check_ret(event.callback(ctx, &event), iap_false); + return iap_true; + } + + for(size_t i = 0; i < array_size(ctx->active_events); i += 1) { + if(ctx->active_events[i].callback == NULL) { + ctx->active_events[i] = event; + return iap_true; + } + } + return iap_false; +} + +static IAPBool process_select_sampr(struct IAPContext* ctx, struct IAPActiveEvent* event) { + if(ctx->waiting_for_audio_attrs_ack) { + IAP_ERRORF("another sampr request pending"); + } + ctx->waiting_for_audio_attrs_ack = iap_true; + + struct IAPSpan request_span = _iap_get_buffer_for_send_payload(ctx); + struct IAPTrackNewAudioAttributesPayload* request = iap_span_alloc(&request_span, sizeof(*request)); + request->sample_rate = swap_32(event->sampr); + request->sound_check = 0; + request->volume_adjustment = 0; + check_ret(_iap_send_packet(ctx, IAPLingoID_DigitalAudio, IAPDigitalAudioCommandID_TrackNewAudioAttributes, _iap_next_trans_id(ctx), request_span.ptr), iap_false); + return iap_true; +} + +IAPBool iap_select_sampr(struct IAPContext* ctx, uint32_t sampr) { + struct IAPActiveEvent event = { + .callback = process_select_sampr, + .sampr = sampr, + }; + check_ret(push_active_event(ctx, event), iap_false); + return iap_true; +} + +static IAPBool process_control_response(struct IAPContext* ctx, struct IAPActiveEvent* event) { + struct IAPSpan request_span = _iap_get_buffer_for_send_payload(ctx); + const struct IAPPlatformPendingControl* ctrl = &event->control_response.control; + const uint8_t status = event->control_response.result ? IAPAckStatus_Success : IAPAckStatus_ECommandFailed; + if(ctrl->lingo == IAPLingoID_ExtendedInterface) { + ipod_ack_ext(ctrl->req_command, status, &request_span); + } else { + ipod_ack(ctrl->req_command, status, &request_span, ctrl->ack_command); + } + check_ret(_iap_send_packet(ctx, ctrl->lingo, ctrl->ack_command, ctrl->trans_id, request_span.ptr), iap_false); + return iap_true; +} + +IAPBool iap_control_response(struct IAPContext* ctx, struct IAPPlatformPendingControl pending, IAPBool result) { + struct IAPActiveEvent event = { + .callback = process_control_response, + .control_response = {pending, result}, + }; + if(pending.lingo == IAPLingoID_SimpleRemote && pending.req_command == IAPSimpleRemoteCommandID_ContextButtonStatus) { + /* no response */ + return iap_true; + } + check_ret(push_active_event(ctx, event), iap_false); + + return iap_true; +} diff --git a/firmware/usbstack/iap/libiap/iap.h b/firmware/usbstack/iap/libiap/iap.h new file mode 100644 index 0000000000..86ad0412e1 --- /dev/null +++ b/firmware/usbstack/iap/libiap/iap.h @@ -0,0 +1,55 @@ +#pragma once +#include "context.h" +#include "span.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* iap.c */ +IAPBool iap_init_ctx(struct IAPContext* ctx, struct IAPOpts opts, void* platform); +IAPBool iap_deinit_ctx(struct IAPContext* ctx); +IAPBool _iap_feed_packet(struct IAPContext* ctx, const uint8_t* data, size_t size); +struct IAPSpan _iap_get_buffer_for_send_payload(struct IAPContext* ctx); +int32_t _iap_next_trans_id(struct IAPContext* ctx); +IAPBool _iap_send_packet(struct IAPContext* ctx, uint8_t lingo, uint16_t command, int32_t trans_id, uint8_t* final_ptr); +/* must be called after iap_platform_on_acc_samprs_received */ +IAPBool iap_select_sampr(struct IAPContext* ctx, uint32_t sampr); +IAPBool iap_control_response(struct IAPContext* ctx, struct IAPPlatformPendingControl pending, IAPBool result); + +/* hid.c */ +IAPBool iap_feed_hid_report(struct IAPContext* ctx, const uint8_t* data, size_t size); +IAPBool iap_notify_send_complete(struct IAPContext* ctx); +IAPBool _iap_send_hid_reports(struct IAPContext* ctx, size_t begin, size_t end); /* data is passed by ctx->send_buf */ +IAPBool _iap_send_next_report(struct IAPContext* ctx); + +/* fid-token-values.c */ +int _iap_hanlde_set_fid_token_values(struct IAPSpan* request, struct IAPSpan* response); + +/* notification.c */ +void iap_notify_track_time_position(struct IAPContext* ctx, uint32_t pos_ms); +void iap_notify_track_playback_index(struct IAPContext* ctx, uint32_t index); +void iap_notify_track_caps(struct IAPContext* ctx, uint32_t caps); +void iap_notify_tracks_count(struct IAPContext* ctx, uint32_t count); +void iap_notify_play_status(struct IAPContext* ctx, uint8_t status /* IAPIPodStatePlayStatus */); +void iap_notify_volume(struct IAPContext* ctx, uint8_t volume, IAPBool muted); +void iap_notify_power_state(struct IAPContext* ctx, uint8_t state /* IAPIPodStatePowerState */, uint8_t battery_level); +void iap_notify_shuffle_state(struct IAPContext* ctx, uint8_t state /* IAPIPodStateShuffleSettingState */); +void iap_notify_repeat_state(struct IAPContext* ctx, uint8_t state /* IAPIPodStateRepeatSettingState */); +void iap_notify_time_setting(struct IAPContext* ctx, const struct IAPDateTime* time); +void iap_notify_hold_switch_state(struct IAPContext* ctx, uint8_t state); + +IAPBool iap_periodic_tick(struct IAPContext* ctx); /* call every 100ms */ + +IAPBool _iap_flush_notification(struct IAPContext* ctx); + +/* debug.c */ +const char* _iap_lingo_str(uint8_t lingo); +const char* _iap_command_str(uint8_t lingo, uint16_t command); +IAPBool _iap_span_is_str(const struct IAPSpan* span); +const char* _iap_span_as_str(const struct IAPSpan* span); +void _iap_dump_packet(uint8_t lingo, uint16_t command, int32_t trans_id, struct IAPSpan span); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/usbstack/iap/libiap/macros.h b/firmware/usbstack/iap/libiap/macros.h new file mode 100644 index 0000000000..f2cab9bb67 --- /dev/null +++ b/firmware/usbstack/iap/libiap/macros.h @@ -0,0 +1,27 @@ +#pragma once +#include "../platform-macros.h" + +#if !defined(IAP_LOGF) +#define IAP_LOGF_MUTED +#define IAP_LOGF(...) +#endif +#define print(fmt, ...) IAP_LOGF("%s:%d: " fmt, __func__, __LINE__ __VA_OPT__(, __VA_ARGS__)); + +#if !defined(IAP_ERRORF) +#define IAP_ERRORF_MUTED +#define IAP_ERRORF(...) +#endif +#define warn(fmt, ...) IAP_ERRORF("%s:%d: " fmt, __func__, __LINE__ __VA_OPT__(, __VA_ARGS__)); + +#define check_act(cond, act, ...) \ + if(!(cond)) { \ + warn("assertion failed" __VA_OPT__(": " __VA_ARGS__)); \ + act; \ + } + +#define check_ret(cond, ret, ...) check_act(cond, return ret, __VA_ARGS__) + +#define array_size(arr) (sizeof(arr) / sizeof(arr[0])) + +#define max(a, b) (a > b ? a : b) +#define min(a, b) (a < b ? a : b) diff --git a/firmware/usbstack/iap/libiap/notification.c b/firmware/usbstack/iap/libiap/notification.c new file mode 100644 index 0000000000..2d12f4ca35 --- /dev/null +++ b/firmware/usbstack/iap/libiap/notification.c @@ -0,0 +1,200 @@ +#include "context.h" +#include "endian.h" +#include "iap.h" +#include "macros.h" +#include "spec/iap.h" + +void iap_notify_track_time_position(struct IAPContext* ctx, uint32_t pos_ms) { + ctx->notification_data.track_time_position_ms = pos_ms; + ctx->notifications_3 |= 1 << IAPIPodStateType_TrackTimePositionMSec; + ctx->notifications_3 |= 1 << IAPIPodStateType_TrackTimePositionSec; + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_TrackTimeOffsetMSec; + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_TrackTimeOffsetSec; +} + +void iap_notify_track_playback_index(struct IAPContext* ctx, uint32_t index) { + ctx->notification_data.track_playback_index = index; + ctx->notifications_3 |= 1 << IAPIPodStateType_TrackPlaybackIndex; + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_TrackIndex; +} + +void iap_notify_track_caps(struct IAPContext* ctx, uint32_t caps) { + ctx->notification_data.track_caps = caps; + ctx->notifications_3 |= 1 << IAPIPodStateType_TrackCaps; +} + +void iap_notify_tracks_count(struct IAPContext* ctx, uint32_t count) { + ctx->notification_data.tracks_count = count; + ctx->notifications_3 |= 1 << IAPIPodStateType_PlaybackEngineContents; + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_PlaybackEngineContentsChanged; +} + +void iap_notify_play_status(struct IAPContext* ctx, uint8_t status) { + ctx->notification_data.play_status = status; + ctx->notifications_3 |= 1 << IAPIPodStateType_PlayStatus; + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_PlaybackStatusExtended; + if(status == IAPIPodStatePlayStatus_PlaybackStopped) { + ctx->notifications_4 |= 1 << IAPStatusChangeNotificationType_PlaybackStopped; + } + /* should set Playback{FEW,REW}SeekStop, but no way to find which from EndFastForwardRewind */ +} + +void iap_notify_volume(struct IAPContext* ctx, uint8_t volume, IAPBool muted) { + ctx->notification_data.volume = volume; + ctx->notification_data.mute_state = muted; + ctx->notifications_3 |= 1 << IAPIPodStateType_Volume; +} + +void iap_notify_power_state(struct IAPContext* ctx, uint8_t state, uint8_t battery_level) { + ctx->notification_data.power_state = state; + ctx->notification_data.battery_level = battery_level; + ctx->notifications_3 |= 1 << IAPIPodStateType_Power; +} + +void iap_notify_shuffle_state(struct IAPContext* ctx, uint8_t state) { + ctx->notification_data.shuffle_state = state; + ctx->notifications_3 |= 1 << IAPIPodStateType_ShuffleSetting; +} + +void iap_notify_repeat_state(struct IAPContext* ctx, uint8_t state) { + ctx->notification_data.repeat_state = state; + ctx->notifications_3 |= 1 << IAPIPodStateType_RepeatSetting; +} + +void iap_notify_time_setting(struct IAPContext* ctx, const struct IAPDateTime* time) { + ctx->notification_data.time_setting = *time; + ctx->notifications_3 |= 1 << IAPIPodStateType_DateTimeSetting; +} + +void iap_notify_hold_switch_state(struct IAPContext* ctx, uint8_t state) { + ctx->notification_data.hold_switch_state = state; + ctx->notifications_3 |= 1 << IAPIPodStateType_HoldSwitchState; +} + +IAPBool iap_periodic_tick(struct IAPContext* ctx) { + if(ctx->waiting_for_audio_attrs_ack) { + return iap_true; + } + + ctx->flushing_notifications = iap_true; + ctx->notification_tick += 1; + if(!ctx->send_busy) { + check_ret(_iap_flush_notification(ctx), iap_false); + } + return iap_true; +} + +static uint8_t play_status_to_extended(uint8_t status) { + switch(status) { + case IAPIPodStatePlayStatus_PlaybackStopped: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Stopped; + case IAPIPodStatePlayStatus_Playing: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Playing; + case IAPIPodStatePlayStatus_PlaybackPaused: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Paused; + case IAPIPodStatePlayStatus_FastForward: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_FFWSeekStarted; + case IAPIPodStatePlayStatus_FastRewind: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_REWSeekStarted; + case IAPIPodStatePlayStatus_EndFastForwardRewind: + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_FFWREWSeekStopped; + } + /* unreachable */ + return IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Stopped; +} + +#define send_notify_3(PayloadType, StateType, set) \ + if(ctx->enabled_notifications_3 & ctx->notifications_3 & (1 << StateType)) { \ + struct PayloadType* payload = iap_span_alloc(&request, sizeof(*payload)); \ + check_ret(payload != NULL, iap_false); \ + payload->type = StateType; \ + set; \ + check_ret(_iap_send_packet(ctx, IAPLingoID_DisplayRemote, IAPDisplayRemoteCommandID_RemoteEventNotification, _iap_next_trans_id(ctx), request.ptr), iap_false); \ + ctx->notifications_3 &= ~(1 << StateType); \ + return iap_true; \ + } + +#define send_notify_4(PayloadType, StateType, set) \ + if(ctx->enabled_notifications_4 & ctx->notifications_4 & (1 << StateType)) { \ + print("notification " #StateType); \ + struct PayloadType* payload = iap_span_alloc(&request, sizeof(*payload)); \ + check_ret(payload != NULL, iap_false); \ + payload->type = StateType; \ + set; \ + check_ret(_iap_send_packet(ctx, IAPLingoID_ExtendedInterface, IAPExtendedInterfaceCommandID_PlayStatusChangeNotification, _iap_next_trans_id(ctx), request.ptr), iap_false); \ + ctx->notifications_4 &= ~(1 << StateType); \ + return iap_true; \ + } + +IAPBool _iap_flush_notification(struct IAPContext* ctx) { + struct IAPSpan request = _iap_get_buffer_for_send_payload(ctx); + + /* [1] P.257: + * Notifications for enabled events are sent every 500 ms, + * with the exception of volume change notifications, which are sent every 100 ms. + */ + if(ctx->notification_tick % 5 != 0) { + goto freq_events; + } + + send_notify_3(IAPIPodStateTrackTimePositionMSecPayload, + IAPIPodStateType_TrackTimePositionMSec, + payload->position_ms = swap_32(ctx->notification_data.track_time_position_ms)); + send_notify_3(IAPIPodStateTrackPlaybackIndexPayload, + IAPIPodStateType_TrackPlaybackIndex, + payload->index = swap_32(ctx->notification_data.track_playback_index)); + send_notify_3(IAPIPodStateTrackCapsPayload, + IAPIPodStateType_TrackCaps, + payload->caps = swap_32(ctx->notification_data.track_caps)); + send_notify_3(IAPIPodStateTrackTimePositionSecPayload, + IAPIPodStateType_TrackTimePositionSec, + payload->position_s = swap_16(ctx->notification_data.track_time_position_ms / 1000)); + send_notify_3(IAPIPodStatePlaybackEngineContentsPayload, + IAPIPodStateType_PlaybackEngineContents, + payload->count = swap_32(ctx->notification_data.tracks_count)); + send_notify_3(IAPIPodStatePlayStatusPayload, + IAPIPodStateType_PlayStatus, + payload->status = ctx->notification_data.play_status); + send_notify_3(IAPIPodStatePowerPayload, + IAPIPodStateType_Power, + payload->power_state = ctx->notification_data.power_state; + payload->battery_level = ctx->notification_data.battery_level); + send_notify_3(IAPIPodStateShuffleSettingPayload, + IAPIPodStateType_ShuffleSetting, + payload->shuffle_state = ctx->notification_data.shuffle_state); + send_notify_3(IAPIPodStateRepeatSettingPayload, + IAPIPodStateType_RepeatSetting, + payload->repeat_state = ctx->notification_data.repeat_state); + send_notify_3(IAPIPodStateDateTimeSettingPayload, + IAPIPodStateType_DateTimeSetting, + payload->year = swap_16(ctx->notification_data.time_setting.year); + payload->month = ctx->notification_data.time_setting.month; + payload->day = ctx->notification_data.time_setting.day; + payload->hour = ctx->notification_data.time_setting.hour; + payload->minute = ctx->notification_data.time_setting.minute); + send_notify_4(IAPPlayStatusChangeNotificationPlaybackStoppedPayload, + IAPStatusChangeNotificationType_PlaybackStopped, ); + send_notify_4(IAPPlayStatusChangeNotificationTrackIndexPayload, + IAPStatusChangeNotificationType_TrackIndex, + payload->index = swap_32(ctx->notification_data.track_playback_index)); + send_notify_4(IAPPlayStatusChangeNotificationTrackTimeOffsetMSecPayload, + IAPStatusChangeNotificationType_TrackTimeOffsetMSec, + payload->offset_ms = swap_32(ctx->notification_data.track_time_position_ms)); + send_notify_4(IAPPlayStatusChangeNotificationPlaybackStatusExtendedPayload, + IAPStatusChangeNotificationType_PlaybackStatusExtended, + payload->state = play_status_to_extended(ctx->notification_data.play_status)); + send_notify_4(IAPPlayStatusChangeNotificationTrackTimeOffsetSecPayload, + IAPStatusChangeNotificationType_TrackTimeOffsetSec, + payload->offset_s = swap_32(ctx->notification_data.track_time_position_ms / 1000)); + send_notify_4(IAPPlayStatusChangeNotificationPlaybackEngineContentsChangedPayload, + IAPStatusChangeNotificationType_PlaybackEngineContentsChanged, + payload->count = swap_32(ctx->notification_data.tracks_count)); +freq_events: + send_notify_3(IAPIPodStateVolumePayload, + IAPIPodStateType_Volume, + payload->mute_state = ctx->notification_data.mute_state; + payload->ui_volume = ctx->notification_data.volume); + + ctx->flushing_notifications = iap_false; + return iap_true; +} diff --git a/firmware/usbstack/iap/libiap/notification.h b/firmware/usbstack/iap/libiap/notification.h new file mode 100644 index 0000000000..977f380b09 --- /dev/null +++ b/firmware/usbstack/iap/libiap/notification.h @@ -0,0 +1,20 @@ +#pragma once +#include + +#include "datetime.h" + +struct _IAPNotifyState { + uint32_t track_time_position_ms; + uint32_t track_playback_index; + uint32_t track_caps; /* IAPIPodStateTrackCapBits */ + uint32_t tracks_count; + uint8_t play_status; /* IAPIPodStatePlayStatus */ + uint8_t mute_state; + uint8_t volume; + uint8_t power_state; /* IAPIPodStatePowerState */ + uint8_t battery_level; + uint8_t shuffle_state; /* IAPIPodStateShuffleSettingState */ + uint8_t repeat_state; /* IAPIPodStateRepeatSettingState */ + struct IAPDateTime time_setting; + uint8_t hold_switch_state; +}; diff --git a/firmware/usbstack/iap/libiap/pack-util.h b/firmware/usbstack/iap/libiap/pack-util.h new file mode 100644 index 0000000000..bc5dae8fc8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/pack-util.h @@ -0,0 +1,41 @@ +#include +#include + +#include "bool.h" +#include "macros.h" + +__attribute__((unused)) static IAPBool pack_u8(uint8_t** data, size_t* size, uint8_t value) { + check_ret(*size >= 1, iap_false); + (*data)[0] = value; + *data += 1; + *size -= 1; + return iap_true; +} + +__attribute__((unused)) static IAPBool pack_u16(uint8_t** data, size_t* size, uint16_t value) { + check_ret(*size >= 2, iap_false); + (*data)[0] = value >> 8; + (*data)[1] = value; + *data += 2; + *size -= 2; + return iap_true; +} + +__attribute__((unused)) static IAPBool pack_u32(uint8_t** data, size_t* size, uint32_t value) { + check_ret(*size >= 2, iap_false); + (*data)[0] = value >> 24; + (*data)[1] = value >> 16; + (*data)[2] = value >> 8; + (*data)[3] = value; + *data += 4; + *size -= 4; + return iap_true; +} + +__attribute__((unused)) static IAPBool pack_data(uint8_t** data, size_t* size, const void* payload, size_t payload_size) { + check_ret(*size >= payload_size, iap_false); + memcpy(*data, payload, payload_size); + *data += payload_size; + *size -= payload_size; + return iap_true; +} diff --git a/firmware/usbstack/iap/libiap/platform.h b/firmware/usbstack/iap/libiap/platform.h new file mode 100644 index 0000000000..880f6f4c58 --- /dev/null +++ b/firmware/usbstack/iap/libiap/platform.h @@ -0,0 +1,116 @@ +#pragma once +#include +#include + +#include "datetime.h" +#include "span.h" +#include "spec/iap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct IAPContext; + +/* iap_platform_malloc */ +enum IAPPlatformMallocFlags { + IAPPlatformMallocFlags_Uncached = 1 << 0, +}; + +/* iap_platform_get_play_status */ +struct IAPPlatformPlayStatus { + uint32_t track_total_ms; + uint32_t track_pos_ms; + uint32_t track_index; + uint32_t track_count; + uint32_t track_caps; /* IAPIPodStateTrackCapBits */ + uint8_t state; /* IAPIPodStatePlayStatus */ +}; + +/* iap_platform_control */ +enum IAPPlatformControl { + IAPPlatformControl_TogglePlayPause, + IAPPlatformControl_Play, + IAPPlatformControl_Pause, + IAPPlatformControl_Stop, + IAPPlatformControl_Next, + IAPPlatformControl_Prev, + IAPPlatformControl_VolumeUp, + IAPPlatformControl_VolumeDown, + IAPPlatformControl_ToggleMute, +}; + +struct IAPPlatformPendingControl { + uint16_t req_command; + uint16_t ack_command; + int32_t trans_id; + uint8_t lingo; +}; + +/* iap_platform_get_volume */ +struct IAPPlatformVolumeStatus { + uint8_t volume; + IAPBool muted; +}; + +/* iap_platform_get_power_status */ +struct IAPPlatformPowerStatus { + uint8_t state; /* IAPIPodStatePowerState */ + uint8_t battery_level; +}; + +/* iap_platform_get_indexed_track_info */ +struct IAPPlatformTrackInfo { + uint32_t* total_ms; + uint32_t* caps; /* IAPIPodStateTrackCapBits */ + struct IAPDateTime* release_date; + struct IAPSpan* artist; + struct IAPSpan* composer; + struct IAPSpan* album; + struct IAPSpan* title; +}; + +/* iap_platform_open_artwork */ +struct IAPPlatformArtwork { + uint16_t width; /* user */ + uint16_t height; /* user */ + IAPBool color; /* user */ + IAPBool valid; /* library */ + uintptr_t opaque; /* user */ +}; + +/* library routines */ +void* iap_platform_malloc(struct IAPContext* iap_ctx, size_t size, int flags); +void iap_platform_free(struct IAPContext* iap_ctx, void* ptr); +int iap_platform_send_hid_report(struct IAPContext* iap_ctx, const void* ptr, size_t size); + +/* system info */ +IAPBool iap_platform_get_ipod_serial_num(struct IAPContext* iap_ctx, struct IAPSpan* serial); + +/* audio controls */ +IAPBool iap_platform_get_play_status(struct IAPContext* iap_ctx, struct IAPPlatformPlayStatus* status); +void iap_platform_control(struct IAPContext* iap_ctx, enum IAPPlatformControl control, struct IAPPlatformPendingControl pending); +IAPBool iap_platform_get_volume(struct IAPContext* iap_ctx, struct IAPPlatformVolumeStatus* status); +IAPBool iap_platform_get_power_status(struct IAPContext* iap_ctx, struct IAPPlatformPowerStatus* status); +IAPBool iap_platform_get_shuffle_setting(struct IAPContext* iap_ctx, uint8_t* status /* IAPIPodStateShuffleSettingState */); +IAPBool iap_platform_set_shuffle_setting(struct IAPContext* iap_ctx, uint8_t status /* IAPIPodStateShuffleSettingState */); +IAPBool iap_platform_get_repeat_setting(struct IAPContext* iap_ctx, uint8_t* status /* IAPIPodStateRepeatSettingState */); +IAPBool iap_platform_set_repeat_setting(struct IAPContext* iap_ctx, uint8_t status /* IAPIPodStateRepeatSettingState */); +IAPBool iap_platform_get_date_time(struct IAPContext* iap_ctx, struct IAPDateTime* time); +IAPBool iap_platform_get_backlight_level(struct IAPContext* iap_ctx, uint8_t* level); +IAPBool iap_platform_get_hold_switch_state(struct IAPContext* iap_ctx, IAPBool* state); +IAPBool iap_platform_get_indexed_track_info(struct IAPContext* iap_ctx, uint32_t index, struct IAPPlatformTrackInfo* info); +IAPBool iap_platform_set_playing_track(struct IAPContext* iap_ctx, uint32_t index); +IAPBool iap_platform_open_artwork(struct IAPContext* iap_ctx, uint32_t index, struct IAPPlatformArtwork* artwork); +IAPBool iap_platform_get_artwork_ptr(struct IAPContext* iap_ctx, struct IAPPlatformArtwork* artwork, struct IAPSpan* span); +IAPBool iap_platform_close_artwork(struct IAPContext* iap_ctx, struct IAPPlatformArtwork* artwork); + +/* other callbacks */ +IAPBool iap_platform_on_acc_samprs_received(struct IAPContext* iap_ctx, struct IAPSpan* samprs); + +/* debugging */ +void iap_platform_dump_hex(const void* ptr, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/usbstack/iap/libiap/span.c b/firmware/usbstack/iap/libiap/span.c new file mode 100644 index 0000000000..98c16989ac --- /dev/null +++ b/firmware/usbstack/iap/libiap/span.c @@ -0,0 +1,46 @@ +#include + +#include "endian.h" +#include "macros.h" +#include "span.h" +#include "unaligned.h" + +const void* iap_span_read(struct IAPSpan* span, size_t count) { + check_ret(span->size >= count, NULL); + span->ptr += count; + span->size -= count; + return span->ptr - count; +} + +void* iap_span_alloc(struct IAPSpan* span, size_t count) __attribute__((alias("iap_span_read"))); + +IAPBool iap_span_append(struct IAPSpan* span, const void* ptr, size_t size) { + void* dest = iap_span_alloc(span, size); + check_ret(dest != NULL, iap_false); + memcpy(dest, ptr, size); + return iap_true; +} + +#define iap_span_template(width) \ + IAPBool iap_span_peek_##width(struct IAPSpan* span, uint##width##_t* value) { \ + check_ret(span->size >= width / 8, iap_false); \ + *value = swap_##width(*(uu##width*)span->ptr); \ + return iap_true; \ + } \ + IAPBool iap_span_read_##width(struct IAPSpan* span, uint##width##_t* value) { \ + const uu##width* ptr = iap_span_read(span, width / 8); \ + check_ret(ptr != NULL, iap_false); \ + *value = swap_##width(*ptr); \ + return iap_true; \ + } \ + IAPBool iap_span_write_##width(struct IAPSpan* span, uint##width##_t value) { \ + uint##width##_t* ptr = iap_span_alloc(span, width / 8); \ + check_ret(ptr != NULL, iap_false); \ + *ptr = swap_##width(value); \ + return iap_true; \ + } +iap_span_template(8); +iap_span_template(16); +iap_span_template(32); +iap_span_template(64); +#undef iap_span_template diff --git a/firmware/usbstack/iap/libiap/span.h b/firmware/usbstack/iap/libiap/span.h new file mode 100644 index 0000000000..ddb5b084f0 --- /dev/null +++ b/firmware/usbstack/iap/libiap/span.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bool.h" + +struct IAPSpan { + uint8_t* ptr; + size_t size; +}; + +const void* iap_span_read(struct IAPSpan* span, size_t count); +void* iap_span_alloc(struct IAPSpan* span, size_t count); +IAPBool iap_span_append(struct IAPSpan* span, const void* ptr, size_t size); + +#define iap_span_template(width) \ + IAPBool iap_span_peek_##width(struct IAPSpan* span, uint##width##_t* value); \ + IAPBool iap_span_read_##width(struct IAPSpan* span, uint##width##_t* value); \ + IAPBool iap_span_write_##width(struct IAPSpan* span, uint##width##_t value) +iap_span_template(8); +iap_span_template(16); +iap_span_template(32); +iap_span_template(64); +#undef iap_span_template + +#ifdef __cplusplus +} +#endif diff --git a/firmware/usbstack/iap/libiap/spec/hid.h b/firmware/usbstack/iap/libiap/spec/hid.h new file mode 100644 index 0000000000..a2c79b0fba --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/hid.h @@ -0,0 +1,14 @@ +#pragma once +#include + +/* [2] P.56 Table 3-2 Link control byte usage */ +enum IAPHIDReportLinkControlBits { + IAPHIDReportLinkControlBits_Continue = 1 << 0, + IAPHIDReportLinkControlBits_MoreToFollow = 1 << 1, +}; + +struct IAPHIDReport { + uint8_t report_id; + uint8_t link_control; /* IAPHIDReportLinkControlBits */ + uint8_t data[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/iap.h b/firmware/usbstack/iap/libiap/spec/iap.h new file mode 100644 index 0000000000..fe1d51ebb8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/iap.h @@ -0,0 +1,54 @@ +#pragma once +/* References: + * [1]: MFi Accessory Firmware Specification R46 + * [2]: MFI Accessory Hardware Specification R9 + * [3]: MFi Accessory Interface Specification For Apple Devices R2 + */ + +#define IAP_SYNC_BYTE 0xFF +#define IAP_SOF_BYTE 0x55 + +/* [1] P.109 Table 2-10 iAP command packet format + * | name | size | description | + * | sync | 0 or 1 | IAP_SYNC_BYTE, exists if UART transport is used | + * | sof | 1 | IAP_SOF_BYTE | + * | length | 1 or 3 | 0xNN or 0x00NNNN. sum of length of {lingo,command,trans}_id,payload | + * | lingo_id | 1 | IAP_LINGO_ID, lingo identifier | + * | command_id | 1 or 2 | 2 bytes long if lingo == 4 | + * | trans_id | 0 or 2 | exists for some commands | + * | payload | N | data | + * | checksum | 1 | crc checksum | + */ + +/* [1] P.211 Table 4-1 Additional iAP lingoes */ +enum IAPLingoID { + IAPLingoID_General = 0x00, + IAPLingoID_Microphone = 0x01, + IAPLingoID_SimpleRemote = 0x02, + IAPLingoID_DisplayRemote = 0x03, + IAPLingoID_ExtendedInterface = 0x04, + IAPLingoID_AccessoryPower = 0x05, + IAPLingoID_USBHostMode = 0x06, + IAPLingoID_RFTuner = 0x07, + IAPLingoID_AccessoryEqualizer = 0x08, + IAPLingoID_Sports = 0x09, + IAPLingoID_DigitalAudio = 0x0A, + IAPLingoID_Storage = 0x0C, + IAPLingoID_IPodOut = 0x0D, + IAPLingoID_Location = 0x0E, +}; + +#include "lingoes/accessory-equalizer.h" +#include "lingoes/accessory-power.h" +#include "lingoes/digital-audio.h" +#include "lingoes/display-remote.h" +#include "lingoes/extended-interface.h" +#include "lingoes/general.h" +#include "lingoes/ipod-out.h" +#include "lingoes/location.h" +#include "lingoes/microphone.h" +#include "lingoes/rf-tuner.h" +#include "lingoes/simple-remote.h" +#include "lingoes/sports.h" +#include "lingoes/storage.h" +#include "lingoes/usb-host-mode.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer.h b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer.h new file mode 100644 index 0000000000..f34386a5ae --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.331 Table 4-200 Accessory Equalizer lingo command summary */ +enum IAPAccessoryEqualizerCommandID { + IAPAccessoryEqualizerCommandID_AccessoryAck = 0x00, /* from acc, general/acc-ack.h */ + IAPAccessoryEqualizerCommandID_GetCurrentEQIndex = 0x01, /* from dev, no payload */ + IAPAccessoryEqualizerCommandID_RetCurrentEQIndex = 0x02, /* from acc, current-eq-index.h */ + IAPAccessoryEqualizerCommandID_SetCurrentEQIndex = 0x03, /* from dev, current-eq-index.h */ + IAPAccessoryEqualizerCommandID_GetEQSettingCount = 0x04, /* from dev, no payload */ + IAPAccessoryEqualizerCommandID_RetEQSettingCount = 0x05, /* from acc, eq-setting-count.h */ + IAPAccessoryEqualizerCommandID_GetEQIndexName = 0x06, /* from dev, */ + IAPAccessoryEqualizerCommandID_RetEQIndexName = 0x07, /* from acc, */ +}; + +#include "accessory-equalizer/current-eq-index.h" +#include "accessory-equalizer/eq-index-name.h" +#include "accessory-equalizer/eq-setting-count.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/current-eq-index.h b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/current-eq-index.h new file mode 100644 index 0000000000..4cd2fc0845 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/current-eq-index.h @@ -0,0 +1,10 @@ +#pragma once +#include + +struct IAPRetCurrentEQIndexPayload { + uint8_t index; +} __attribute__((packed)); + +struct IAPSetCurrentEQIndexPayload { + uint8_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-index-name.h b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-index-name.h new file mode 100644 index 0000000000..4d7f0a4cc5 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-index-name.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAGetEQIndexNamePayload { + uint8_t index; +} __attribute__((packed)); + +struct IARetEQIndexNamePayload { + uint8_t index; + char name[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-setting-count.h b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-setting-count.h new file mode 100644 index 0000000000..b798803c6e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-equalizer/eq-setting-count.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPRetEQSettingCountPayload { + uint8_t count; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/accessory-power.h b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-power.h new file mode 100644 index 0000000000..040040a9ee --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/accessory-power.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* [1] P.548 Table C-37 Accessory Power lingo command summary */ +enum IAPAccessoryPowerCommandID { + IAPAccessoryPowerCommandID_BeginHighPower = 0x02, + IAPAccessoryPowerCommandID_EndHighPower = 0x03, +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio.h b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio.h new file mode 100644 index 0000000000..804c7db444 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio.h @@ -0,0 +1,16 @@ +#pragma once +#include + +/* [1] P.346 Table 4-232 Digital Audio lingo command summary */ +enum IAPDigitalAudioCommandID { + IAPDigitalAudioCommandID_AccessoryAck = 0x00, /* from acc, general/acc-ack.h */ + IAPDigitalAudioCommandID_IPodAck = 0x01, /* from dev, general/ipod-ack.h */ + IAPDigitalAudioCommandID_GetAccessorySampleRateCaps = 0x02, /* from dev, no payload */ + IAPDigitalAudioCommandID_RetAccessorySampleRateCaps = 0x03, /* from acc, accessory-sample-rate-caps.h */ + IAPDigitalAudioCommandID_TrackNewAudioAttributes = 0x04, /* from acc, track-new-audio-attributes.h */ + IAPDigitalAudioCommandID_SetVideoDelay = 0x05, /* from acc, set-video-delay.h */ +}; + +#include "digital-audio/accessory-sample-rate-caps.h" +#include "digital-audio/set-video-delay.h" +#include "digital-audio/track-new-audio-attributes.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/accessory-sample-rate-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/accessory-sample-rate-caps.h new file mode 100644 index 0000000000..4cda0456b2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/accessory-sample-rate-caps.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* +struct IAPRetAccessorySampleRateCapsPayload { + uint32_t sample_rates[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/set-video-delay.h b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/set-video-delay.h new file mode 100644 index 0000000000..851040d3cd --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/set-video-delay.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPSetVideoDelayPayload { + uint32_t delay_ms; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/track-new-audio-attributes.h b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/track-new-audio-attributes.h new file mode 100644 index 0000000000..e43638c016 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/digital-audio/track-new-audio-attributes.h @@ -0,0 +1,8 @@ +#pragma once +#include + +struct IAPTrackNewAudioAttributesPayload { + uint32_t sample_rate; + uint32_t sound_check; + uint32_t volume_adjustment; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote.h new file mode 100644 index 0000000000..62185f3b8f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote.h @@ -0,0 +1,56 @@ +#pragma once +#include + +/* [1] P.249 Table 4-48 Display Remote lingo command summary */ +enum IAPDisplayRemoteCommandID { + IAPDisplayRemoteCommandID_IPodAck = 0x00, /* from acc, general/ipod-ack.h */ + IAPDisplayRemoteCommandID_GetCurrentEQProfileIndex = 0x01, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetCurrentEQProfileIndex = 0x02, /* from dev, current-eq-profile-index.h */ + IAPDisplayRemoteCommandID_SetCurrentEQProfileIndex = 0x03, /* from acc, current-eq-profile-index.h */ + IAPDisplayRemoteCommandID_GetNumEQProfiles = 0x04, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetNumEQProfiles = 0x05, /* from dev, num-eq-profiles.h */ + IAPDisplayRemoteCommandID_GetIndexedEQProfileName = 0x06, /* from acc, indexed-eq-profile-name.h */ + IAPDisplayRemoteCommandID_RetIndexedEQProfileName = 0x07, /* from dev, indexed-eq-profile-name.h */ + IAPDisplayRemoteCommandID_SetRemoteEventNotification = 0x08, /* from acc, ipod-state.h */ + IAPDisplayRemoteCommandID_RemoteEventNotification = 0x09, /* from dev, ipod-state.h */ + IAPDisplayRemoteCommandID_GetRemoteEventStatus = 0x0A, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetRemoteEventStatus = 0x0B, /* from dev, remote-event-status.h */ + IAPDisplayRemoteCommandID_GetIPodStateInfo = 0x0C, /* from acc, ipod-state.h */ + IAPDisplayRemoteCommandID_RetIPodStateInfo = 0x0D, /* from dev, ipod-state.h */ + IAPDisplayRemoteCommandID_SetIPodStateInfo = 0x0E, /* from acc, ipod-state.h */ + IAPDisplayRemoteCommandID_GetPlayStatus = 0x0F, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetPlayStatus = 0x10, /* from dev, play-status.h */ + IAPDisplayRemoteCommandID_SetCurrentPlayingTrack = 0x11, /* from acc, set-current-playing-track.h */ + IAPDisplayRemoteCommandID_GetIndexedPlayingTrackInfo = 0x12, /* from acc, indexed-playing-track-info.h */ + IAPDisplayRemoteCommandID_RetIndexedPlayingTrackInfo = 0x13, /* from dev, indexed-playing-track-info.h */ + IAPDisplayRemoteCommandID_GetNumPlayingTracks = 0x14, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetNumPlayingTracks = 0x15, /* from dev, num-playing-tracks.h */ + IAPDisplayRemoteCommandID_GetArtworkFormats = 0x16, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetArtworkFormats = 0x17, /* from dev, artwork-formats.h */ + IAPDisplayRemoteCommandID_GetTrackArtworkData = 0x18, /* from acc, track-artwork-data.h */ + IAPDisplayRemoteCommandID_RetTrackArtworkData = 0x19, /* from dev, track-artwork-data.h */ + IAPDisplayRemoteCommandID_GetPowerBatteryState = 0x1A, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetPowerBatteryState = 0x1B, /* from dev, power-battery-state.h */ + IAPDisplayRemoteCommandID_GetSoundCheckState = 0x1C, /* from acc, no payload */ + IAPDisplayRemoteCommandID_RetSoundCheckState = 0x1D, /* from dev, sound-check-status.h */ + IAPDisplayRemoteCommandID_SetSoundCheckState = 0x1E, /* from acc, sound-check-status.h */ + IAPDisplayRemoteCommandID_GetTrackArtworkTimes = 0x1F, /* from acc, track-artwork-times.h */ + IAPDisplayRemoteCommandID_RetTrackArtworkTimes = 0x20, /* from dev, track-artwork-times.h */ + IAPDisplayRemoteCommandID_CreateGeniusPlaylist = 0x21, /* from acc, genius_playlist.h */ + IAPDisplayRemoteCommandID_IsGeniusAvailableForTrack = 0x22, /* from acc, genius_playlist.h */ +}; + +#include "display-remote/artwork-formats.h" +#include "display-remote/current-eq-profile-index.h" +#include "display-remote/genius-playlist.h" +#include "display-remote/indexed-eq-profile-name.h" +#include "display-remote/indexed-playing-track-info.h" +#include "display-remote/ipod-state.h" +#include "display-remote/num-eq-profiles.h" +#include "display-remote/num-playing-tracks.h" +#include "display-remote/play-status.h" +#include "display-remote/power-battery-state.h" +#include "display-remote/remote-event-status.h" +#include "display-remote/set-current-playing-track.h" +#include "display-remote/track-artwork-data.h" +#include "display-remote/track-artwork-times.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/artwork-formats.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/artwork-formats.h new file mode 100644 index 0000000000..5c4b072532 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/artwork-formats.h @@ -0,0 +1,21 @@ +#pragma once +#include + +enum IAPArtworkPixelFormats { + IAPArtworkPixelFormats_Mono = 0x01, + IAPArtworkPixelFormats_RGB565LE = 0x02, + IAPArtworkPixelFormats_RGB565BE = 0x03, +}; + +struct IAPArtworkFormat { + uint16_t format_id; + uint8_t pixel_format; /* IAPArtworkPixelFormats */ + uint16_t image_width; + uint16_t image_height; +} __attribute__((packed)); + +/* +struct IAPRetArtworkFormatsPayload { + struct IAPArtworkFormat formats[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/current-eq-profile-index.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/current-eq-profile-index.h new file mode 100644 index 0000000000..3c00f06f28 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/current-eq-profile-index.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPRetCurrentEQProfileIndexPayload { + uint32_t index; +} __attribute__((packed)); + +struct IAPSetCurrentEQProfileIndexPayload { + uint32_t index; + uint8_t restore_on_exit; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/genius-playlist.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/genius-playlist.h new file mode 100644 index 0000000000..b1da0f9f2f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/genius-playlist.h @@ -0,0 +1,10 @@ +#pragma once +#include + +struct IAPCreateGeniusPlaylistPayload { + uint32_t playback_index; +} __attribute__((packed)); + +struct IAPIsGeniusAvailableForTrackPayload { + uint32_t playback_index; +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-eq-profile-name.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-eq-profile-name.h new file mode 100644 index 0000000000..d526e1df10 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-eq-profile-name.h @@ -0,0 +1,12 @@ +#pragma once +#include + +struct IAPGetIndexedEQProfileNamePayload { + uint32_t index; +} __attribute__((packed)); + +/* +struct IAPRetIndexedEQProfileNamePayload { + char name[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-playing-track-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-playing-track-info.h new file mode 100644 index 0000000000..4baeeed66c --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/indexed-playing-track-info.h @@ -0,0 +1,85 @@ +#pragma once +#include + +enum IAPIndexedPlayingTrackInfoType { + IAPIndexedPlayingTrackInfoType_TrackCapsInfo = 0x00, + IAPIndexedPlayingTrackInfoType_ChapterTimeName = 0x01, + IAPIndexedPlayingTrackInfoType_ArtistName = 0x02, + IAPIndexedPlayingTrackInfoType_AlbumName = 0x03, + IAPIndexedPlayingTrackInfoType_GenreName = 0x04, + IAPIndexedPlayingTrackInfoType_TrackTitle = 0x05, + IAPIndexedPlayingTrackInfoType_ComposerName = 0x06, + IAPIndexedPlayingTrackInfoType_Lyrics = 0x07, + IAPIndexedPlayingTrackInfoType_ArtworkCount = 0x08, +}; + +struct IAPGetIndexedPlayingTrackInfoPayload { + uint8_t type; /* IAPIndexedPlayingTrackInfoType */ + uint32_t track_index; + uint16_t chapter_index; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoPayload { + uint8_t type; /* IAPIndexedPlayingTrackInfoType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoTrackCapsInfoPayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_TrackCapsInfo */ + uint32_t track_caps; /* IAPIPodStateTrackCapBits */ + uint32_t track_total_ms; + uint16_t chapter_count; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoChapterTimeNamePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_ChapterTimeName */ + uint32_t offset_ms; + char name[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoArtistNamePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_ArtistName */ + char name[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoAlbumNamePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_AlbumName */ + char name[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoGenreNamePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_GenreName */ + char name[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoTrackTitlePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_TrackTitle */ + char name[]; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoComposerNamePayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_ComposerName */ + char name[]; +} __attribute__((packed)); + +enum IAPIndexedPlayingTrackInfoLyricsInfoBits { + IAPIndexedPlayingTrackInfoLyricsInfoBits_Series = 1 << 0, + IAPIndexedPlayingTrackInfoLyricsInfoBits_Last = 1 << 1, +}; + +struct IAPRetIndexedPlayingTrackInfoLyricsPayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_Lyrics */ + uint8_t info_bits; /* IAPIndexedPlayingTrackInfoLyricsInfoBits */ + uint16_t index; + char lyrics[]; +} __attribute__((packed)); + +struct IAPArtworkCount { + uint16_t format; /* IAPArtworkPixelFormats */ + uint16_t count; +} __attribute__((packed)); + +struct IAPRetIndexedPlayingTrackInfoArtworkCountPayload { + uint8_t type; /* = IAPIndexedPlayingTrackInfoType_ArtworkCount */ + struct IAPArtworkCount data[]; /* or 0x08 to indicate no counts */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/ipod-state.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/ipod-state.h new file mode 100644 index 0000000000..a861ec3667 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/ipod-state.h @@ -0,0 +1,204 @@ +#pragma once +#include + +/* common state types */ + +/* [1] P.266 Table 4-74 Apple device state data */ +enum IAPIPodStateType { + IAPIPodStateType_TrackTimePositionMSec = 0x00, + IAPIPodStateType_TrackPlaybackIndex = 0x01, + IAPIPodStateType_ChapterIndex = 0x02, + IAPIPodStateType_PlayStatus = 0x03, + IAPIPodStateType_Volume = 0x04, + IAPIPodStateType_Power = 0x05, + IAPIPodStateType_EQSetting = 0x06, + IAPIPodStateType_ShuffleSetting = 0x07, + IAPIPodStateType_RepeatSetting = 0x08, + IAPIPodStateType_DateTimeSetting = 0x09, + IAPIPodStateType_AlarmSetting = 0x0A, + IAPIPodStateType_BacklightLevel = 0x0B, + IAPIPodStateType_HoldSwitchState = 0x0C, + IAPIPodStateType_SoundCheckState = 0x0D, + IAPIPodStateType_AudiobookSpeeed = 0x0E, + IAPIPodStateType_TrackTimePositionSec = 0x0F, + IAPIPodStateType_AbsoluteVolume = 0x10, + IAPIPodStateType_TrackCaps = 0x11, + IAPIPodStateType_PlaybackEngineContents = 0x12, +}; + +struct IAPIPodStatePayload { + uint8_t type; /* IAPIPodStateType */ + uint8_t data[]; +} __attribute__((packed)); + +/* [1] P.257 Table 4-61 Event notification data */ + +struct IAPIPodStateTrackTimePositionMSecPayload { + uint8_t type; /* = IAPIPodStateType_TrackTimePositionMSec */ + uint32_t position_ms; +} __attribute__((packed)); + +struct IAPIPodStateTrackPlaybackIndexPayload { + uint8_t type; /* = IAPIPodStateType_TrackPlaybackIndex */ + uint32_t index; +} __attribute__((packed)); + +struct IAPIPodStateChapterIndexPayload { + uint8_t type; /* = IAPIPodStateType_ChapterIndex */ + uint32_t index; + uint16_t chapter_count; + uint16_t chapter_index; +} __attribute__((packed)); + +enum IAPIPodStatePlayStatus { + IAPIPodStatePlayStatus_PlaybackStopped = 0x00, + IAPIPodStatePlayStatus_Playing = 0x01, + IAPIPodStatePlayStatus_PlaybackPaused = 0x02, + IAPIPodStatePlayStatus_FastForward = 0x03, + IAPIPodStatePlayStatus_FastRewind = 0x04, + IAPIPodStatePlayStatus_EndFastForwardRewind = 0x05, +}; + +struct IAPIPodStatePlayStatusPayload { + uint8_t type; /* = IAPIPodStateType_PlayStatus */ + uint8_t status; /* IAPIPodStatePlayStatus */ +} __attribute__((packed)); + +struct IAPIPodStateVolumePayload { + uint8_t type; /* = IAPIPodStateType_Volume */ + uint8_t mute_state; + uint8_t ui_volume; +} __attribute__((packed)); + +enum IAPIPodStatePowerState { + IAPIPodStatePowerState_InternalLow = 0x00, + IAPIPodStatePowerState_Internal = 0x01, + IAPIPodStatePowerState_ExternalBattery = 0x02, + IAPIPodStatePowerState_External = 0x03, + IAPIPodStatePowerState_ExternalCharging = 0x04, + IAPIPodStatePowerState_ExternalCharged = 0x05, +}; + +struct IAPIPodStatePowerPayload { + uint8_t type; /* = IAPIPodStateType_Power */ + uint8_t power_state; /* IAPIPodStatePowerState */ + uint8_t battery_level; +} __attribute__((packed)); + +struct IAPIPodStateEQSettingPayload { + uint8_t type; /* = IAPIPodStateType_EQSetting */ + uint32_t eq_index; +} __attribute__((packed)); + +enum IAPIPodStateShuffleSettingState { + IAPIPodStateShuffleSettingState_Off = 0x00, + IAPIPodStateShuffleSettingState_Tracks = 0x01, + IAPIPodStateShuffleSettingState_Albums = 0x02, +}; + +struct IAPIPodStateShuffleSettingPayload { + uint8_t type; /* = IAPIPodStateType_ShuffleSetting */ + uint8_t shuffle_state; /* IAPIPodStateShuffleSettingState */ +} __attribute__((packed)); + +enum IAPIPodStateRepeatSettingState { + IAPIPodStateRepeatSettingState_Off = 0x00, + IAPIPodStateRepeatSettingState_One = 0x01, + IAPIPodStateRepeatSettingState_All = 0x02, +}; + +struct IAPIPodStateRepeatSettingPayload { + uint8_t type; /* = IAPIPodStateType_RepeatSetting */ + uint8_t repeat_state; /* IAPIPodStateRepeatSettingState */ +} __attribute__((packed)); + +struct IAPIPodStateDateTimeSettingPayload { + uint8_t type; /* = IAPIPodStateType_DateTimeSetting */ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; +} __attribute__((packed)); + +struct IAPIPodStateAlarmSettingPayload { + uint8_t type; /* = IAPIPodStateType_AlarmSetting */ + uint8_t deprecated[3]; +} __attribute__((packed)); + +struct IAPIPodStateBacklightLevelPayload { + uint8_t type; /* = IAPIPodStateType_BacklightLevel */ + uint8_t level; +} __attribute__((packed)); + +struct IAPIPodStateHoldSwitchStatePayload { + uint8_t type; /* = IAPIPodStateType_HoldSwitchState */ + uint8_t state; +} __attribute__((packed)); + +struct IAPIPodStateSoundCheckStatePayload { + uint8_t type; /* = IAPIPodStateType_SoundCheckState */ + uint8_t state; +} __attribute__((packed)); + +enum IAPIPodStateAudiobookSpeeed { + IAPIPodStateAudiobookSpeeed_Slower = 0xFF, + IAPIPodStateAudiobookSpeeed_Normal = 0x00, + IAPIPodStateAudiobookSpeeed_Faster = 0x01, +}; + +struct IAPIPodStateAudiobookSpeeedPayload { + uint8_t type; /* = IAPIPodStateType_AudiobookSpeeed */ + uint8_t speed; /* IAPIPodStateAudiobookSpeeed */ +} __attribute__((packed)); + +struct IAPIPodStateTrackTimePositionSecPayload { + uint8_t type; /* = IAPIPodStateType_TrackTimePositionSec */ + uint16_t position_s; +} __attribute__((packed)); + +struct IAPIPodStateAbsoluteVolumePayload { + uint8_t type; /* = IAPIPodStateType_AbsoluteVolume */ + uint8_t mute_state; + uint8_t ui_volume; + uint8_t absolute_volume; +} __attribute__((packed)); + +enum IAPIPodStateTrackCapBits { + IAPIPodStateTrackCapBits_IsAudiobook = 1 << 0, + IAPIPodStateTrackCapBits_HasChapters = 1 << 1, + IAPIPodStateTrackCapBits_HasAlbumArts = 1 << 2, + IAPIPodStateTrackCapBits_HasLyrics = 1 << 3, + IAPIPodStateTrackCapBits_IsPodcast = 1 << 4, + IAPIPodStateTrackCapBits_HasReleaseDate = 1 << 5, + IAPIPodStateTrackCapBits_HasDescription = 1 << 6, + IAPIPodStateTrackCapBits_HasVideo = 1 << 7, + IAPIPodStateTrackCapBits_IsQueued = 1 << 8, + IAPIPodStateTrackCapBits_GenerateGeniusPlaylist = 1 << 13, + IAPIPodStateTrackCapBits_IsITunesUEpisode = 1 << 14, +}; + +struct IAPIPodStateTrackCapsPayload { + uint8_t type; /* = IAPIPodStateType_TrackCaps */ + uint32_t caps; /* IAPIPodStateTrackCapBits */ +} __attribute__((packed)); + +struct IAPIPodStatePlaybackEngineContentsPayload { + uint8_t type; /* = IAPIPodStateType_PlaybackEngineContents */ + uint32_t count; +} __attribute__((packed)); + +/* event notification */ +struct IAPSetRemoteEventNotificationPayload { + uint32_t mask; /* (1 << IAPIPodStateType) | ... */ +} __attribute__((packed)); + +/* IAPRemoteEventNotificationPayload = IAPIPodStatePayload */ + +/* get/ret state info */ +struct IAPGetIPodStateInfoPayload { + uint8_t type; /* = IAPIPodStateType */ +} __attribute__((packed)); + +/* RetIPodStateInfoPayload = IAPIPodStatePayload */ +/* SetIPodStateInfoPayload = IAPIPodStatePayload */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-eq-profiles.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-eq-profiles.h new file mode 100644 index 0000000000..bb40c6dacf --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-eq-profiles.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPRetNumEQProfilesPayload { + uint32_t count; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-playing-tracks.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-playing-tracks.h new file mode 100644 index 0000000000..b8d7a59d6a --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/num-playing-tracks.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPRetNumPlayingTracksPayload { + uint32_t num_playing_tracks; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/play-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/play-status.h new file mode 100644 index 0000000000..6ddfd38d74 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/play-status.h @@ -0,0 +1,9 @@ +#pragma once +#include + +struct IAPRetPlayStatusPayload { + uint8_t state; /* IAPIPodStatePlayStatus */ + uint32_t track_index; + uint32_t track_total_ms; + uint32_t track_pos_ms; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/power-battery-state.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/power-battery-state.h new file mode 100644 index 0000000000..43f03f5e9f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/power-battery-state.h @@ -0,0 +1,7 @@ +#pragma once +#include + +struct IAPRetPowerBatteryStatePayload { + uint8_t power_state; /* IAPIPodStatePowerState */ + uint8_t battery_level; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/remote-event-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/remote-event-status.h new file mode 100644 index 0000000000..7b7e6a4a39 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/remote-event-status.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPRetRemoteEventStatusPayload { + uint32_t mask; /* (1 << IAPRemoteEventNotifications) | ... */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/set-current-playing-track.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/set-current-playing-track.h new file mode 100644 index 0000000000..b2522186a8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/set-current-playing-track.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPSetCurrentPlayingTrackPayload { + uint32_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-data.h new file mode 100644 index 0000000000..f540c63f26 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-data.h @@ -0,0 +1,26 @@ +#pragma once +#include + +struct IAPGetTrackArtworkDataPayload { + uint32_t track_index; + uint16_t format_id; + uint32_t offset_ms; +} __attribute__((packed)); + +struct IAPRetTrackArtworkDataFirstPayload { + uint16_t index; /* = 0x0000 */ + uint8_t pixel_format; /* IAPArtworkPixelFormats */ + uint16_t pixel_width; + uint16_t pixel_height; + uint16_t inset_top_left_x; + uint16_t inset_top_left_y; + uint16_t inset_bottom_right_x; + uint16_t inset_bottom_right_y; + uint32_t stride; + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetTrackArtworkDataSubsequenctPayload { + uint16_t index; /* > 0x0000 */ + uint8_t data[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-times.h b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-times.h new file mode 100644 index 0000000000..670b4b5732 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/display-remote/track-artwork-times.h @@ -0,0 +1,15 @@ +#pragma once +#include + +struct IAPGetTrackArtworkTimesPayload { + uint32_t track_index; + uint16_t format_id; + uint16_t artwork_index; + uint16_t artwork_count; +} __attribute__((packed)); + +/* +struct IAPRetTrackArtworkTimesPayload { + uint32_t offsets_ms[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface.h new file mode 100644 index 0000000000..b40c4342ea --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface.h @@ -0,0 +1,109 @@ +#pragma once +#include + +/* [1] P.398 Table 5-1 Extended Interface lingo command summary */ +enum IAPExtendedInterfaceCommandID { + IAPExtendedInterfaceCommandID_IPodAck = 0x0001, /* 1.00, NoAuth, from acc, ipod-ack.h */ + IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackChapterInfo = 0x0002, /* 1.06, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackChapterInfo = 0x0003, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_SetCurrentPlayingTrackChapter = 0x0004, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackChapterPlayStatus = 0x0005, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackChapterPlayStatus = 0x0006, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackChapterName = 0x0007, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackChapterName = 0x0008, /* 1.06, NoAuth, from dev, current-playing-track-chapter.h */ + IAPExtendedInterfaceCommandID_GetAudiobookSpeed = 0x0009, /* 1.06, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_RetAudiobookSpeed = 0x000A, /* 1.06, NoAuth, from dev, audiobook-speed.h */ + IAPExtendedInterfaceCommandID_SetAudiobookSpeed = 0x000B, /* 1.06, NoAuth, from acc, audiobook-speed.h */ + IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackInfo = 0x000C, /* 1.08, NoAuth, from acc, indexed-playing-track-info.h */ + IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackInfo = 0x000D, /* 1.08, NoAuth, from dev, indexed-playing-track-info.h */ + IAPExtendedInterfaceCommandID_GetArtworkFormats = 0x000E, /* 1.10, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_RetArtworkFormats = 0x000F, /* 1.10, NoAuth, from dev, display-remote/artwork-formats.h*/ + IAPExtendedInterfaceCommandID_GetTrackArtworkData = 0x0010, /* 1.10, NoAuth, from acc, display-remote/track-artwork-data.h*/ + IAPExtendedInterfaceCommandID_RetTrackArtworkData = 0x0011, /* 1.10, NoAuth, from dev, display-remote/track-artwork-data.h*/ + IAPExtendedInterfaceCommandID_RequestProtocolVersion = 0x0012, /* deprecated */ + IAPExtendedInterfaceCommandID_ReturnProtocolVersion = 0x0013, /* deprecated */ + IAPExtendedInterfaceCommandID_RequestIPodName = 0x0014, /* deprecated */ + IAPExtendedInterfaceCommandID_ReturnIPodName = 0x0015, /* deprecated */ + IAPExtendedInterfaceCommandID_ResetDBSelection = 0x0016, /* 1.00, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_SelectDBRecord = 0x0017, /* 1.00, NoAuth, from dev, database.h*/ + IAPExtendedInterfaceCommandID_GetNumberCategorizedDBRecords = 0x0018, /* 1.00, NoAuth, from acc, database.h */ + IAPExtendedInterfaceCommandID_ReturnNumberCategorizedDBRecords = 0x0019, /* 1.00, NoAuth, from dev, database.h */ + IAPExtendedInterfaceCommandID_RetrieveCategorizedDatabaseRecords = 0x001A, /* 1.00, NoAuth, from acc, database.h */ + IAPExtendedInterfaceCommandID_ReturnCategorizedDatabaseRecords = 0x001B, /* 1.00, NoAuth, from acc, database.h */ + IAPExtendedInterfaceCommandID_GetPlayStatus = 0x001C, /* 1.00, NoAuth, no payload */ + IAPExtendedInterfaceCommandID_ReturnPlayStatus = 0x001D, /* 1.00, NoAuth, play-status.h */ + IAPExtendedInterfaceCommandID_GetCurrentPlayingTrackIndex = 0x001E, /* 1.00, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnCurrentPlayingTrackIndex = 0x001F, /* 1.00, NoAuth, from dev, current-playing-index.h */ + IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackTitle = 0x0020, /* 1.00, NoAuth, from acc, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackTitle = 0x0021, /* 1.00, NoAuth, from dev, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackArtistName = 0x0022, /* 1.00, NoAuth, from acc, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackArtistName = 0x0023, /* 1.00, NoAuth, from dev, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_GetIndexedPlayingTrackAlbumName = 0x0024, /* 1.00, NoAuth, from acc, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_ReturnIndexedPlayingTrackAlbumName = 0x0025, /* 1.00, NoAuth, from dev, indexed-playing-track-string.h */ + IAPExtendedInterfaceCommandID_SetPlayStatusChangeNotification = 0x0026, /* 1.00, NoAuth, from acc, play-status-change-notification.h */ + IAPExtendedInterfaceCommandID_PlayStatusChangeNotification = 0x0027, /* 1.00, NoAuth, from dev, play-status-change-notification.h */ + IAPExtendedInterfaceCommandID_PlayCurrentSelection = 0x0028, /* 1.00, NoAuth, from acc, play-current-selection.h */ + IAPExtendedInterfaceCommandID_PlayControl = 0x0029, /* 1.00, NoAuth, from acc, play-control.h */ + IAPExtendedInterfaceCommandID_GetTrackArtworkTimes = 0x002A, /* 1.10, NoAuth, from acc, display-remote/track-artwork-times.h */ + IAPExtendedInterfaceCommandID_RetTrackArtworkTimes = 0x002B, /* 1.10, NoAuth, from dev, display-remote/track-artwork-times.h */ + IAPExtendedInterfaceCommandID_GetShuffle = 0x002C, /* 1.00, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnShuffle = 0x002D, /* 1.00, NoAuth, from dev, shuffle.h */ + IAPExtendedInterfaceCommandID_SetShuffle = 0x002E, /* 1.00, NoAuth, from acc, shuffle.h */ + IAPExtendedInterfaceCommandID_GetRepeat = 0x002F, /* 1.00, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnRepeat = 0x0030, /* 1.00, NoAuth, from dev, repeat.h */ + IAPExtendedInterfaceCommandID_SetRepeat = 0x0031, /* 1.00, NoAuth, from acc, repeat.h */ + IAPExtendedInterfaceCommandID_SetDisplayImage = 0x0032, /* 1.01, NoAuth, from acc, set-display-image.h */ + IAPExtendedInterfaceCommandID_GetMonoDisplayImageLimits = 0x0033, /* 1.01, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnMonoDisplayImageLimits = 0x0034, /* 1.01, NoAuth, from dev, mono-display-image-limits.h */ + IAPExtendedInterfaceCommandID_GetNumPlayingTracks = 0x0035, /* 1.01, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnNumPlayingTracks = 0x0036, /* 1.01, NoAuth, from dev, display-remote/num-playing-tracks.h */ + IAPExtendedInterfaceCommandID_SetCurrentPlayingTrack = 0x0037, /* 1.01, NoAuth, from acc, display-remote/set-current-playing-track.h */ + IAPExtendedInterfaceCommandID_SelectSortDBRecord = 0x0038, /* deprecated */ + IAPExtendedInterfaceCommandID_GetColorDisplayImageLimits = 0x0039, /* 1.09, NoAuth, from acc, no payload */ + IAPExtendedInterfaceCommandID_ReturnColorDisplayImageLimits = 0x003A, /* 1.09, NoAuth, from dev, color-display-image-limits.h */ + IAPExtendedInterfaceCommandID_ResetDBSelectionHierarchy = 0x003B, /* 1.11, Auth, from acc, db-selection-hierarchy.h */ + IAPExtendedInterfaceCommandID_GetDBITunesInfo = 0x003C, /* 1.13, Auth, from acc, db-itunes-info.h */ + IAPExtendedInterfaceCommandID_RetDBITunesInfo = 0x003D, /* 1.13, Auth, from dev, db-itunes-info.h */ + IAPExtendedInterfaceCommandID_GetUIDTrackInfo = 0x003E, /* 1.13, Auth, from acc, track-info.h */ + IAPExtendedInterfaceCommandID_RetUIDTrackInfo = 0x003F, /* 1.13, Auth, from dev, track-info.h */ + IAPExtendedInterfaceCommandID_GetDBTrackInfo = 0x0040, /* 1.13, Auth, from acc, track-info.h */ + IAPExtendedInterfaceCommandID_RetDBTrackInfo = 0x0041, /* 1.13, Auth, from dev, track-info.h */ + IAPExtendedInterfaceCommandID_GetPBTrackInfo = 0x0042, /* 1.13, Auth, from acc, track-info.h */ + IAPExtendedInterfaceCommandID_RetPBTrackInfo = 0x0043, /* 1.13, Auth, from dev, track-info.h */ + IAPExtendedInterfaceCommandID_CreateGeniusPlaylist = 0x0044, /* 1.13, Auth, from acc, genius-playlist.h */ + IAPExtendedInterfaceCommandID_RefreshGeniusPlaylist = 0x0045, /* 1.13, Auth, from acc, genius-playlist.h */ + IAPExtendedInterfaceCommandID_IsGeniusAvailableForTrack = 0x0047, /* 1.13, Auth, from acc, genius-playlist.h */ + IAPExtendedInterfaceCommandID_GetPlaylistInfo = 0x0048, /* 1.13, Auth, from acc, playlist-info.h */ + IAPExtendedInterfaceCommandID_RetPlaylistInfo = 0x0049, /* 1.13, Auth, from dev, playlist-info.h */ + IAPExtendedInterfaceCommandID_PrepareUIDList = 0x004A, /* 1.14, Auth, from dev, uid-list.h */ + IAPExtendedInterfaceCommandID_PlayPreparedUIDList = 0x004B, /* 1.14, Auth, from dev, uid-list.h */ + IAPExtendedInterfaceCommandID_GetArtworkTimes = 0x004C, /* 1.14, Auth, from acc, artwork-times.h */ + IAPExtendedInterfaceCommandID_RetArtworkTimes = 0x004D, /* 1.14, Auth, from dev, artwork-times.h */ + IAPExtendedInterfaceCommandID_GetArtworkData = 0x004E, /* 1.14, Auth, from acc, artwork-data.h */ + IAPExtendedInterfaceCommandID_RetArtworkData = 0x004F, /* 1.14, Auth, from dev, artwork-data.h */ +}; + +#include "extended-interface/artwork-data.h" +#include "extended-interface/artwork-times.h" +#include "extended-interface/audiobook-speed.h" +#include "extended-interface/color-display-image-limits.h" +#include "extended-interface/current-playing-track-chapter.h" +#include "extended-interface/current-playing-track-index.h" +#include "extended-interface/database.h" +#include "extended-interface/db-itunes-info.h" +#include "extended-interface/db-selection-hierarchy.h" +#include "extended-interface/genius-playlist.h" +#include "extended-interface/indexed-playing-track-info.h" +#include "extended-interface/indexed-playing-track-string.h" +#include "extended-interface/ipod-ack.h" +#include "extended-interface/mono-display-image-limits.h" +#include "extended-interface/play-control.h" +#include "extended-interface/play-current-selection.h" +#include "extended-interface/play-status-change-notification.h" +#include "extended-interface/play-status.h" +#include "extended-interface/playlist-info.h" +#include "extended-interface/repeat.h" +#include "extended-interface/set-display-image.h" +#include "extended-interface/shuffle.h" +#include "extended-interface/track-info.h" +#include "extended-interface/uid-list.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-data.h new file mode 100644 index 0000000000..67c0569750 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-data.h @@ -0,0 +1,58 @@ +#pragma once +#include + +/* [1] P.459 5.1.72 Command 0x004E: GetArtworkData */ + +struct IAPGetArtworkDataPayload { + uint8_t id_type; /* IAPGetArtworkTrackIDType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPGetArtworkDataUIDPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_UID */ + uint8_t uid[8]; + uint16_t format_id; + uint32_t offset_ms; +} __attribute__((packed)); + +struct IAPGetArtworkDataIndexPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_{PlaybackListIndex,DatabaseIndex} */ + uint32_t index; + uint16_t format_id; + uint32_t offset_ms; +} __attribute__((packed)); + +struct IAPRetArtworkDataPayload { + uint16_t current_sector; + uint16_t max_sectors; + uint8_t id_type; /* = IAPGetArtworkTrackIDType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetArtworkDataBody { + uint8_t pixel_format; /* IAPArtworkPixelFormats */ + uint16_t pixel_width; + uint16_t pixel_height; /* [1] P.461 "Table 5-104 imageDescriptionAndData format" misses this field, but should be here */ + uint16_t inset_top_left_x; + uint16_t inset_top_left_y; + uint16_t inset_bottom_right_x; + uint16_t inset_bottom_right_y; + uint32_t stride; + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetArtworkDataUIDPayload { + uint16_t current_sector; + uint16_t max_sectors; + uint8_t id_type; /* = IAPGetArtworkTrackIDType_UID */ + uint8_t uid[8]; + struct IAPRetArtworkDataBody body; +} __attribute__((packed)); + +struct IAPRetArtworkDataIndexPayload { + uint16_t current_sector; + uint16_t max_sectors; + uint8_t id_type; /* = IAPGetArtworkTrackIDType_{PlaybackListIndex,DatabaseIndex} */ + uint32_t index; + struct IAPRetArtworkDataBody body; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-times.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-times.h new file mode 100644 index 0000000000..41207efe5e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/artwork-times.h @@ -0,0 +1,48 @@ +#pragma once +#include + +/* [1] P.458 5.1.70 Command 0x004C: GetArtworkTimes */ + +enum IAPArtworkTrackIDType { + IAPGetArtworkTrackIDType_UID = 0x00, + IAPGetArtworkTrackIDType_PlaybackListIndex = 0x01, + IAPGetArtworkTrackIDType_DatabaseIndex = 0x02, +}; + +struct IAPGetArtworkTimesPayload { + uint8_t id_type; /* IAPGetArtworkTrackIDType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPGetArtworkTimesUIDPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_UID */ + uint8_t uid[8]; + uint16_t format_id; + uint16_t artwork_index; + uint16_t artwork_count; +} __attribute__((packed)); + +struct IAPGetArtworkTimesIndexPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_{PlaybackListIndex,DatabaseIndex} */ + uint32_t index; + uint16_t format_id; + uint16_t artwork_index; + uint16_t artwork_count; +} __attribute__((packed)); + +struct IAPRetArtworkTimesPayload { + uint8_t id_type; /* IAPGetArtworkTrackIDType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetArtworkTimesUIDPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_UID */ + uint8_t uid[8]; + uint32_t offsets_ms[]; +} __attribute__((packed)); + +struct IAPRetArtworkTimesIndexPayload { + uint8_t id_type; /* = IAPGetArtworkTrackIDType_{PlaybackListIndex,DatabaseIndex} */ + uint32_t index; + uint32_t offsets_ms[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/audiobook-speed.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/audiobook-speed.h new file mode 100644 index 0000000000..770cf06039 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/audiobook-speed.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPRetAudiobookSpeedPayload { + uint8_t speed; /* IAPIPodStateAudiobookSpeeed */ +} __attribute__((packed)); + +struct IAPSetAudiobookSpeedPayload { + uint8_t speed; /* IAPIPodStateAudiobookSpeeed */ + uint8_t restore_on_exit; /* optional */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/color-display-image-limits.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/color-display-image-limits.h new file mode 100644 index 0000000000..c5e75fd3b7 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/color-display-image-limits.h @@ -0,0 +1,16 @@ +#pragma once +#include + +/* [1] P.443 5.1.53 Command 0x003A: ReturnColorDisplayImageLimits */ + +struct IAPColorDisplayImageLimit { + uint16_t max_width; + uint16_t max_height; + uint8_t pixel_format; /* IAPArtworkPixelFormats */ +} __attribute__((packed)); + +/* +struct IAPReturnColorDisplayImageLimitsPayload { + struct IAPColorDisplayImageLimit limits[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-chapter.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-chapter.h new file mode 100644 index 0000000000..f059b53c49 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-chapter.h @@ -0,0 +1,30 @@ +#pragma once +#include + +struct IAPReturnCurrentPlayingTrackChapterInfoPayload { + uint32_t index; + uint32_t count; +} __attribute__((packed)); + +struct IAPSetCurrentPlayingTrackChapterPayload { + uint32_t index; +} __attribute__((packed)); + +struct IAPGetCurrentPlayingTrackChapterPlayStatusPayload { + uint32_t index; +} __attribute__((packed)); + +struct IAPReturnCurrentPlayingTrackChapterPlayStatusPayload { + uint32_t length_ms; + uint32_t elapsed_ms; +} __attribute__((packed)); + +struct IAPGetCurrentPlayingTrackChapterNamePayload { + uint32_t index; +} __attribute__((packed)); + +/* +struct IAPReturnCurrentPlayingTrackChapterNamePayload { + char name[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-index.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-index.h new file mode 100644 index 0000000000..1f86a18e83 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/current-playing-track-index.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPReturnCurrentPlayingTrackIndexPayload { + uint32_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/database.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/database.h new file mode 100644 index 0000000000..55a548ba7e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/database.h @@ -0,0 +1,42 @@ +#pragma once +#include + +enum IAPDatabaseType { + IAPDatabaseType_TopLevel = 0x00, /* 1.14 */ + IAPDatabaseType_Playlist = 0x01, /* 1.00 */ + IAPDatabaseType_Artist = 0x02, /* 1.00 */ + IAPDatabaseType_Album = 0x03, /* 1.00 */ + IAPDatabaseType_Genre = 0x04, /* 1.00 */ + IAPDatabaseType_Track = 0x05, /* 1.00 */ + IAPDatabaseType_Composer = 0x06, /* 1.00 */ + IAPDatabaseType_Audiobook = 0x07, /* 1.06 */ + IAPDatabaseType_Podcast = 0x08, /* 1.08 */ + IAPDatabaseType_NestedPlaylist = 0x09, /* 1.13 */ + IAPDatabaseType_GeniusMixes = 0x0A, /* 1.14 */ + IAPDatabaseType_ITunesU = 0x0B, /* 1.14 */ + +}; + +struct IAPSelectDBRecord { + uint8_t type; /* IAPDatabaseType */ + uint32_t index; +} __attribute__((packed)); + +struct IAPGetNumberCategorizedDBRecordsPayload { + uint8_t type; /* IAPDatabaseType */ +} __attribute__((packed)); + +struct IAPReturnNumberCategorizedDBRecordsPayload { + uint32_t count; +} __attribute__((packed)); + +struct IAPRetrieveCategorizedDatabaseRecordsPayload { + uint8_t type; /* IAPDatabaseType */ + uint32_t index; + uint32_t count; +} __attribute__((packed)); + +struct IAPReturnCategorizedDatabaseRecordsPayload { + uint32_t index; + char record[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-itunes-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-itunes-info.h new file mode 100644 index 0000000000..11618be6eb --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-itunes-info.h @@ -0,0 +1,42 @@ +#pragma once +#include + +/* [1] P.444 5.1.55 Command 0x003C: GetDBiTunesInfo */ + +enum IAPITunesMetadataType { + IAPITunesMetadataType_UID = 0x00, + IAPITunesMetadataType_LastSyncDate = 0x01, + IAPITunesMetadataType_AudioTrackCount = 0x02, + IAPITunesMetadataType_VideoTrackCount = 0x03, + IAPITunesMetadataType_AudiobookCount = 0x04, + IAPITunesMetadataType_PhotoCount = 0x05, +}; + +struct IAPGetDBITunesInfoPayload { + uint8_t metadata_type; /* IAPITunesMetadataType */ +} __attribute__((packed)); + +struct IAPRetDBITunesInfoPayload { + uint8_t metadata_type; /* = IAPITunesMetadataType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPRetDBITunesInfoUIDPayload { + uint8_t metadata_type; /* = IAPITunesMetadataType_UID */ + uint8_t uid[8]; +} __attribute__((packed)); + +struct IAPRetDBITunesInfoLastSyncDatePayload { + uint8_t metadata_type; /* = IAPITunesMetadataType_LastSyncDate */ + uint8_t seconds; + uint8_t minute; + uint8_t hour; + uint8_t day; + uint8_t month; + uint16_t year; +} __attribute__((packed)); + +struct IAPRetDBITunesInfoCountPayload { + uint8_t metadata_type; /* = IAPITunesMetadataType_{AudioTrack,VideoTrack,Audiobook,Photo}Count */ + uint32_t count; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-selection-hierarchy.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-selection-hierarchy.h new file mode 100644 index 0000000000..434b21bab3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/db-selection-hierarchy.h @@ -0,0 +1,13 @@ +#pragma once +#include + +/* [1] P.443 5.1.54 Command 0x003B: ResetDBSelectionHierarchy */ + +enum IAPResetDBSelectionHierarchySelection { + IAPResetDBSelectionHierarchySelection_Audio = 0x01, + IAPResetDBSelectionHierarchySelection_Video = 0x02, +}; + +struct IAPResetDBSelectionHierarchyPayload { + uint8_t selection; /* IAPResetDBSelectionHierarchySelection */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/genius-playlist.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/genius-playlist.h new file mode 100644 index 0000000000..81601886e4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/genius-playlist.h @@ -0,0 +1,23 @@ +#pragma once +#include + +/* [1] P.454 5.1.63 Command 0x0044: CreateGeniusPlaylist */ + +enum IAPCreateGeniusPlaylistIndexType { + IAPCreateGeniusPlaylistIndexType_DatabaseEngine = 0x00, + IAPCreateGeniusPlaylistIndexType_PlaybackEngine = 0x01, +}; + +struct IAPExtendedCreateGeniusPlaylistPayload { + uint8_t index_type; /* IAPCreateGeniusPlaylistIndexType */ + uint32_t index; +} __attribute__((packed)); + +struct IAPExtendedRefreshGeniusPlaylistPayload { + uint32_t index; +} __attribute__((packed)); + +struct IAPExtendedIsGeniusAvailableForTrackPayload { + uint8_t index_type; /* IAPCreateGeniusPlaylistIndexType */ + uint32_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-info.h new file mode 100644 index 0000000000..ed7615d1f0 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-info.h @@ -0,0 +1,81 @@ +#pragma once +#include + +/* [1] P.408 5.1.13 Command 0x000C: GetIndexedPlayingTrackInfo */ + +enum IAPExtendedIndexedPlayingTrackInfoType { + IAPExtendedIndexedPlayingTrackInfoType_TrackCapsInfo = 0x00, + IAPExtendedIndexedPlayingTrackInfoType_PodcastName = 0x01, + IAPExtendedIndexedPlayingTrackInfoType_TrackReleaseDate = 0x02, + IAPExtendedIndexedPlayingTrackInfoType_TrackDescription = 0x03, + IAPExtendedIndexedPlayingTrackInfoType_TrackSongLyrics = 0x04, + IAPExtendedIndexedPlayingTrackInfoType_TrackGenre = 0x05, + IAPExtendedIndexedPlayingTrackInfoType_TrackComposer = 0x06, + IAPExtendedIndexedPlayingTrackInfoType_TrackArtworkCount = 0x07, +}; + +struct IAPExtendedGetIndexedPlayingTrackInfoPayload { + uint8_t type; /* IAPExtendedIndexedPlayingTrackInfoType */ + uint32_t track_index; + uint16_t chapter_index; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoPayload { + uint8_t type; /* IAPExtendedIndexedPlayingTrackInfoType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackCapsInfoPayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackCapsInfo */ + uint32_t track_caps; /* IAPIPodStateTrackCapBits */ + uint32_t track_total_ms; + uint16_t chapter_count; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoPodcastNamePayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_PodcastName */ + char name[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackReleaseDatePayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackReleaseDate */ + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day; + uint8_t month; + uint16_t year; + uint8_t weekday; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackDescriptionPayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackDescription */ + uint8_t info_bits; /* IAPIndexedPlayingTrackInfoLyricsInfoBits */ + uint16_t index; + char description[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackSongLyricsPayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackSongLyrics */ + uint8_t info_bits; /* IAPIndexedPlayingTrackInfoLyricsInfoBits */ + uint16_t index; + char lyrics[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackGenrePayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackGenre */ + char genre[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackComposerPayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackComposer */ + char composer[]; +} __attribute__((packed)); + +struct IAPExtendedRetIndexedPlayingTrackInfoTrackArtworkCountPayload { + uint8_t type; /* = IAPExtendedRetIndexedPlayingTrackInfo_TrackArtworkCount */ + struct { + uint16_t format; /* IAPArtworkPixelFormats */ + uint16_t count; + } __attribute__((packed)) data[]; /* or 0x08 to indicate no counts */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-string.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-string.h new file mode 100644 index 0000000000..895fa2eba3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/indexed-playing-track-string.h @@ -0,0 +1,20 @@ +#pragma once +#include + +struct IAPGetIndexedPlayingTrackStringPayload { + uint32_t index; +} __attribute__((packed)); + +/* IAPGetIndexedPlayingTrackTitlePayload = IAPGetIndexedPlayingTrackStringPayload */ +/* IAPGetIndexedPlayingTrackArtistNamePayload = IAPGetIndexedPlayingTrackStringPayload */ +/* IAPGetIndexedPlayingTrackAlbumNamePayload = IAPGetIndexedPlayingTrackStringPayload */ + +/* +struct IAPReturnIndexedPlayingTrackStringPayload { + char string[]; +} __attribute__((packed)); +*/ + +/* IAPReturnIndexedPlayingTrackTitlePayload = IAPReturnIndexedPlayingTrackStringPayload */ +/* IAPReturnIndexedPlayingTrackArtistNamePayload = IAPReturnIndexedPlayingTrackStringPayload */ +/* IAPReturnIndexedPlayingTrackAlbumNamePayload = IAPReturnIndexedPlayingTrackStringPayload */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/ipod-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/ipod-ack.h new file mode 100644 index 0000000000..bcaf1e2203 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/ipod-ack.h @@ -0,0 +1,7 @@ +#pragma once +#include + +struct IAPExtendedIPodAckPayload { + uint8_t status; /* IAPAckStatus */ + uint16_t id; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/mono-display-image-limits.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/mono-display-image-limits.h new file mode 100644 index 0000000000..6f8d8ac375 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/mono-display-image-limits.h @@ -0,0 +1,10 @@ +#pragma once +#include + +/* [1] P.440 5.1.48 Command 0x0034: ReturnMonoDisplayImageLimits */ + +struct IAPReturnMonoDisplayImageLimitsPayload { + uint16_t max_width; + uint16_t max_height; + uint8_t pixel_format; /* IAPArtworkPixelFormats */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-control.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-control.h new file mode 100644 index 0000000000..0360026daa --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-control.h @@ -0,0 +1,25 @@ +#pragma once +#include + +/* [1] P.428 5.1.37 Command 0x0029: PlayControl */ + +enum IAPPlayControlCode { + IAPPlayControlCode_TogglePlayPause = 0x01, /* 1.00 */ + IAPPlayControlCode_Stop = 0x02, /* 1.00 */ + IAPPlayControlCode_NextTrack = 0x03, /* 1.00 */ + IAPPlayControlCode_PrevTrack = 0x04, /* 1.00 */ + IAPPlayControlCode_StartFF = 0x05, /* 1.00 */ + IAPPlayControlCode_StartRew = 0x06, /* 1.00 */ + IAPPlayControlCode_EndFFRew = 0x07, /* 1.00 */ + IAPPlayControlCode_Next = 0x08, /* 1.06 */ + IAPPlayControlCode_Prev = 0x09, /* 1.06 */ + IAPPlayControlCode_Play = 0x0A, /* 1.13 */ + IAPPlayControlCode_Pause = 0x0B, /* 1.13 */ + IAPPlayControlCode_NextChapter = 0x0C, /* 1.14 */ + IAPPlayControlCode_PrevChapter = 0x0D, /* 1.14 */ + IAPPlayControlCode_ResumeIPod = 0x0E, /* 1.14 */ +}; + +struct IAPPlayControlPayload { + uint8_t code; /* IAPPlayControlCode */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-current-selection.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-current-selection.h new file mode 100644 index 0000000000..a543ae7b86 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-current-selection.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* [1] P.546 Table C-34 PlayCurrentSelection packet */ + +struct IAPPlayCurrentSelectionPayload { + uint32_t track_index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status-change-notification.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status-change-notification.h new file mode 100644 index 0000000000..a7ce5095f5 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status-change-notification.h @@ -0,0 +1,137 @@ +#pragma once +#include + +struct IAPSetPlayStatusChangeNotification1BytePayload { + uint8_t enable; +} __attribute__((packed)); + +enum IAPStatusChangeNotificationBits { + IAPStatusChangeNotificationBits_Basic = 1 << 0, + IAPStatusChangeNotificationBits_Extended = 1 << 1, + IAPStatusChangeNotificationBits_TrackIndex = 1 << 2, + IAPStatusChangeNotificationBits_TrackTimeOffsetMSec = 1 << 3, + IAPStatusChangeNotificationBits_TrackTimeOffsetSec = 1 << 4, + IAPStatusChangeNotificationBits_ChapterIndex = 1 << 5, + IAPStatusChangeNotificationBits_ChapterTimeOffsetMSec = 1 << 6, + IAPStatusChangeNotificationBits_ChapterTimeOffsetSec = 1 << 7, + IAPStatusChangeNotificationBits_TrackUniqueID = 1 << 8, + IAPStatusChangeNotificationBits_TrackMediaType = 1 << 9, + IAPStatusChangeNotificationBits_TrackLyricsReady = 1 << 10, + IAPStatusChangeNotificationBits_TrackCapsChanged = 1 << 11, + IAPStatusChangeNotificationBits_PlaybackEngineContentsChanged = 1 << 12, + +}; + +struct IAPSetPlayStatusChangeNotification4BytesPayload { + uint32_t mask; /* IAPStatusChangeNotificationBits */ +} __attribute__((packed)); + +/* [1] P.426 5.1.36 Command 0x0027: PlayStatusChangeNotification */ + +enum IAPStatusChangeNotificationType { + IAPStatusChangeNotificationType_PlaybackStopped = 0x00, + IAPStatusChangeNotificationType_TrackIndex = 0x01, + IAPStatusChangeNotificationType_PlaybackFEWSeekStop = 0x02, + IAPStatusChangeNotificationType_PlaybackREWSeekStop = 0x03, + IAPStatusChangeNotificationType_TrackTimeOffsetMSec = 0x04, + IAPStatusChangeNotificationType_ChapterIndex = 0x05, + IAPStatusChangeNotificationType_PlaybackStatusExtended = 0x06, + IAPStatusChangeNotificationType_TrackTimeOffsetSec = 0x07, + IAPStatusChangeNotificationType_ChapterTimeOffsetMSec = 0x08, + IAPStatusChangeNotificationType_ChapterTimeOffsetSec = 0x09, + IAPStatusChangeNotificationType_TrackUniqueID = 0x0A, + IAPStatusChangeNotificationType_TrackPlaybackMode = 0x0B, + IAPStatusChangeNotificationType_TrackLyricsReady = 0x0C, + IAPStatusChangeNotificationType_TrackCapsChanged = 0x0D, + IAPStatusChangeNotificationType_PlaybackEngineContentsChanged = 0x0E, +}; + +struct IAPPlayStatusChangeNotificationPayload { + uint8_t type; + uint8_t data[]; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationPlaybackStoppedPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_PlaybackStopped */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackIndexPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackIndex */ + uint32_t index; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationPlaybackFEWSeekStopPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_PlaybackFEWSeekStop */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationPlaybackREWSeekStopPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_PlaybackREWSeekStop */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackTimeOffsetMSecPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackTimeOffsetMSec */ + uint32_t offset_ms; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationChapterIndexPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_ChapterIndex */ + uint32_t index; +} __attribute__((packed)); + +enum IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates { + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Stopped = 0x02, + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_FFWSeekStarted = 0x05, + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_REWSeekStarted = 0x06, + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_FFWREWSeekStopped = 0x07, + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Playing = 0x0A, + IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates_Paused = 0x0B, +}; + +struct IAPPlayStatusChangeNotificationPlaybackStatusExtendedPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_PlaybackStatusExtended */ + uint8_t state; /* IAPPlayStatusChangeNotificationPlaybackStatusExtendedStates */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackTimeOffsetSecPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackTimeOffsetSec */ + uint32_t offset_s; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationChapterTimeOffsetMSecPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_ChapterTimeOffsetMSec */ + uint32_t offset_ms; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationChapterTimeOffsetSecPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_ChapterTimeOffsetSec */ + uint32_t offset_s; +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackUniqueIDPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackUniqueID */ + uint8_t id[8]; +} __attribute__((packed)); + +enum IAPPlayStatusChangeNotificationTrackMediaTypePlayMode { + IAPPlayStatusChangeNotificationTrackMediaTypePlayMode_Audio = 0x00, + IAPPlayStatusChangeNotificationTrackMediaTypePlayMode_Video = 0x01, +}; + +struct IAPPlayStatusChangeNotificationTrackPlaybackModePayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackPlaybackMode */ + uint8_t play_mode; /* IAPPlayStatusChangeNotificationTrackMediaTypePlayMode */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackLyricsReadyPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackLyricsReady */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationTrackCapsChangedPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_TrackCapsChanged */ + uint32_t caps; /* IAPIPodStateTrackCapBits */ +} __attribute__((packed)); + +struct IAPPlayStatusChangeNotificationPlaybackEngineContentsChangedPayload { + uint8_t type; /* = IAPStatusChangeNotificationType_PlaybackEngineContentsChanged */ + uint32_t count; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status.h new file mode 100644 index 0000000000..66d4e00a79 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/play-status.h @@ -0,0 +1,15 @@ +#pragma once +#include + +enum IAPPlayStatus { + IAPPlayStatus_Stopped = 0x00, + IAPPlayStatus_Playing = 0x01, + IAPPlayStatus_Paused = 0x02, + IAPPlayStatus_Error = 0xFF, +}; + +struct IAPExtendedRetPlayStatusPayload { + uint32_t track_total_ms; + uint32_t track_pos_ms; + uint8_t state; /* IAPPlayStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/playlist-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/playlist-info.h new file mode 100644 index 0000000000..7693e5a002 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/playlist-info.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.456 5.1.66 Command 0x0048: GetPlaylistInfo */ + +enum IAPPlaylistInfoType { + PlaylistInfo = 0x00, +}; + +struct IAPGetPlaylistInfoPayload { + uint8_t info_type; /* IAPPlaylistInfoType */ + uint32_t index; +} __attribute__((packed)); + +struct IAPRetPlaylistInfoPayload { + uint8_t info_type; /* IAPPlaylistInfoType */ + uint8_t data; /* TODO: add definitions */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/repeat.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/repeat.h new file mode 100644 index 0000000000..619e5dacef --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/repeat.h @@ -0,0 +1,13 @@ +#pragma once +#include + +/* [1] P.434 5.1.44 Command 0x0030: ReturnRepeat */ + +struct IAPReturnRepeatPayload { + uint8_t mode; /* IAPIPodStateRepeaetSettingState */ +}; + +struct IAPSetRepeatPayload { + uint8_t mode; /* IAPIPodStateRepeatSettingState */ + uint8_t restore_on_exit; /* optional */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/set-display-image.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/set-display-image.h new file mode 100644 index 0000000000..4687fe8f35 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/set-display-image.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.435 5.1.46 Command 0x0032: SetDisplayImage */ + +struct IAPSetDisplayImageFirstPayload { + uint16_t index; /* = 0x0000 */ + uint8_t pixel_format; /* IAPArtworkPixelFormats */ + uint16_t pixel_width; + uint16_t pixel_height; + uint32_t stride; + uint8_t data[]; +} __attribute__((packed)); + +struct IAPSetDisplayImageSubsequenctPayload { + uint16_t index; /* > 0x0000 */ + uint8_t data[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/shuffle.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/shuffle.h new file mode 100644 index 0000000000..f94a0e6e1e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/shuffle.h @@ -0,0 +1,13 @@ +#pragma once +#include + +/* [1] P.431 5.1.41 Command 0x002D: ReturnShuffle */ + +struct IAPReturnShufflePayload { + uint8_t mode; /* IAPIPodStateShuffleSettingState */ +}; + +struct IAPSetShufflePayload { + uint8_t mode; /* IAPIPodStateShuffleSettingState */ + uint8_t restore_on_exit; /* optional */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/track-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/track-info.h new file mode 100644 index 0000000000..2eab33ed67 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/track-info.h @@ -0,0 +1,59 @@ +#pragma once +#include + +/* [1] P.446 5.1.57 Command 0x003E: GetUIDTrackInfo */ + +enum IAPTrakcInfoTypeBits { + /* mask[0] */ + IAPTrakcInfoTypeBits_Caps = 1 << 0, + IAPTrakcInfoTypeBits_TrackName = 1 << 1, + IAPTrakcInfoTypeBits_ArtistName = 1 << 2, + IAPTrakcInfoTypeBits_AlbumName = 1 << 3, + IAPTrakcInfoTypeBits_GenreName = 1 << 4, + IAPTrakcInfoTypeBits_ComposerName = 1 << 5, + IAPTrakcInfoTypeBits_TotalTrackTimeDuration = 1 << 6, + IAPTrakcInfoTypeBits_UniqueTrackID = 1 << 7, + /* mask[1] */ + IAPTrakcInfoTypeBits_ChapterCount = 1 << 0, + IAPTrakcInfoTypeBits_ChapterTimes = 1 << 1, + IAPTrakcInfoTypeBits_ChapterNames = 1 << 2, + IAPTrakcInfoTypeBits_Lyrics = 1 << 3, + IAPTrakcInfoTypeBits_Description = 1 << 4, + IAPTrakcInfoTypeBits_AlbumTrackIndex = 1 << 5, + IAPTrakcInfoTypeBits_DiscSetAlbumIndex = 1 << 6, + IAPTrakcInfoTypeBits_PlayCount = 1 << 7, + /* mask[2] */ + IAPTrakcInfoTypeBits_SkipCount = 1 << 0, + IAPTrakcInfoTypeBits_PodcastReleaseDate = 1 << 1, + IAPTrakcInfoTypeBits_LastPlayedDate = 1 << 2, + IAPTrakcInfoTypeBits_Year = 1 << 3, + IAPTrakcInfoTypeBits_StarRating = 1 << 4, + IAPTrakcInfoTypeBits_SeriesName = 1 << 5, + IAPTrakcInfoTypeBits_SeasonNumber = 1 << 6, + IAPTrakcInfoTypeBits_TrackVolumeAdjust = 1 << 7, + /* mask[3] */ + IAPTrakcInfoTypeBits_TrackEQPreset = 1 << 0, + IAPTrakcInfoTypeBits_TrackDataRate = 1 << 1, + IAPTrakcInfoTypeBits_BookmarkOffset = 1 << 2, + IAPTrakcInfoTypeBits_StartStopTimeOffset = 1 << 3, + +}; + +struct IAPGetUIDTrackInfoPayload { + uint8_t uid[8]; + uint32_t mask[]; /* IAPTrakcInfoTypeBits */ +} __attribute__((packed)); + +struct IAPGetDBTrackInfoPayload { + uint8_t database_index; + uint8_t track_count; + uint32_t mask[]; /* IAPTrakcInfoTypeBits */ +} __attribute__((packed)); + +struct IAPRetDBTrackInfoPayload { + uint8_t playing_index; + uint8_t track_count; + uint32_t mask[]; /* IAPTrakcInfoTypeBits */ +} __attribute__((packed)); + +/* TODO: define IAPRet{UID,DB,PB}TrackInfoPayload, but who really needs this? */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/uid-list.h b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/uid-list.h new file mode 100644 index 0000000000..4947e70f56 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/extended-interface/uid-list.h @@ -0,0 +1,15 @@ +#pragma once +#include + +/* [1] P.457 5.1.68 Command 0x004A: PrepareUIDList */ + +struct IAPPrepareUIDListPayload { + uint16_t current_sector; + uint16_t max_sectors; + uint8_t uids[][8]; +} __attribute__((packed)); + +struct IAPPlayPreparedUIDListPayload { + uint8_t reserved; + uint8_t uid[8]; +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general.h b/firmware/usbstack/iap/libiap/spec/lingoes/general.h new file mode 100644 index 0000000000..5bcae5276d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general.h @@ -0,0 +1,112 @@ +#pragma once +#include + +/* [1] P.119 Table 3-1 General lingo commands */ +enum IAPGeneralCommandID { + IAPGeneralCommandID_RequestIdentify = 0x00, /* 0.00, from dev, no payload */ + IAPGeneralCommandID_Identify = 0x01, /* 0.00, from acc, identify.h, deprecated */ + IAPGeneralCommandID_IPodAck = 0x02, /* 1.00, from dev, ipod-ack.h */ + IAPGeneralCommandID_RequestExtendedInterfaceMode = 0x03, /* 1.00, from acc, no payload */ + IAPGeneralCommandID_ReturnExtendedInterfaceMode = 0x04, /* 1.00, from dev, extended-interface-mode.h */ + IAPGeneralCommandID_EnterExtendedInterfaceMode = 0x05, /* 1.00, from acc, no payload, deprecated */ + IAPGeneralCommandID_ExitExtendedInterfaceMode = 0x06, /* 1.00, from acc, no payload */ + IAPGeneralCommandID_RequestIPodName = 0x07, /* 1.00, from acc, no payload */ + IAPGeneralCommandID_ReturnIPodName = 0x08, /* 1.00, from dev, ipod-name.h */ + IAPGeneralCommandID_RequestIPodSoftwareVersion = 0x09, /* 1.00, from acc, no payload */ + IAPGeneralCommandID_ReturnIPodSoftwareVersion = 0x0A, /* 1.00, from dev, ipod-software-version.h */ + IAPGeneralCommandID_RequestIPodSerialNum = 0x0B, /* 1.00, from acc, no payload */ + IAPGeneralCommandID_ReturnIPodSerialNum = 0x0C, /* 1.00, from dev, ipod-serial-num.h */ + IAPGeneralCommandID_RequestIPodModelNum = 0x0D, /* 1.00, from acc, deprecated */ + IAPGeneralCommandID_ReturnIPodModelNum = 0x0E, /* 1.00, from dev, deprecated */ + IAPGeneralCommandID_RequestLingoProtocolVersion = 0x0F, /* 1.00, from acc, lingo-protocol-version.h */ + IAPGeneralCommandID_ReturnLingoProtocolVersion = 0x10, /* 1.00, from dev, lingo-protocol-version.h */ + IAPGeneralCommandID_RequestTransportMaxPayloadSize = 0x11, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_ReturnTransportMaxPayloadSize = 0x12, /* 1.09, from dev, transport-max-payload-size.h */ + IAPGeneralCommandID_IdentifyDeviceLingoes = 0x13, /* 1.01, from acc, identify-device-lingoes.h */ + IAPGeneralCommandID_GetAccessoryAuthenticationInfo = 0x14, /* 1.01, from dev, no payload */ + IAPGeneralCommandID_RetAccessoryAuthenticationInfo = 0x15, /* 1.01, from acc, acc-auth-info.h */ + IAPGeneralCommandID_AckAccessoryAuthenticationInfo = 0x16, /* 1.01, from dev, acc-auth-info.h */ + IAPGeneralCommandID_GetAccessoryAuthenticationSignature = 0x17, /* 1.01, from dev, acc-auth-sig.h */ + IAPGeneralCommandID_RetAccessoryAuthenticationSignature = 0x18, /* 1.01, from acc, acc-auth-sig.h */ + IAPGeneralCommandID_AckAccessoryAuthenticationStatus = 0x19, /* 1.01, from dev, acc-auth-sig.h*/ + IAPGeneralCommandID_GetIPodAuthenticationInfo = 0x1A, /* 1.01, from acc, no payload */ + IAPGeneralCommandID_RetIPodAuthenticationInfo = 0x1B, /* 1.01, from dev, ipod-auth-info.h */ + IAPGeneralCommandID_AckIPodAuthenticationInfo = 0x1C, /* 1.01, from acc, ipod-auth-info.h */ + IAPGeneralCommandID_GetIPodAuthenticationSignature = 0x1D, /* 1.01, from acc, ipod-auth-sig.h */ + IAPGeneralCommandID_RetIPodAuthenticationSignature = 0x1E, /* 1.01, from dev, ipod-auth-sig.h */ + IAPGeneralCommandID_AckIPodAuthenticationStatus = 0x1F, /* 1.01, from acc, ipod-auth-sig.h */ + IAPGeneralCommandID_NotifyIPodStateChange = 0x23, /* 1.02, from dev, notify-ipod-state-change.h */ + IAPGeneralCommandID_GetIPodOptions = 0x24, /* 1.05, from acc, no payload */ + IAPGeneralCommandID_RetIPodOptions = 0x25, /* 1.05, from dev, ipod-options.h */ + IAPGeneralCommandID_GetAccessoryInfo = 0x27, /* 1.04, from dev, acc-info.h */ + IAPGeneralCommandID_RetAccessoryInfo = 0x28, /* 1.04, from acc, acc-info.h */ + IAPGeneralCommandID_GetIPodPreferences = 0x29, /* 1.05, from acc, ipod-preferences.h */ + IAPGeneralCommandID_RetIPodPreferences = 0x2A, /* 1.05, from dev, ipod-preferences.h */ + IAPGeneralCommandID_SetIPodPreferences = 0x2B, /* 1.05, from acc, ipod-preferences.h */ + IAPGeneralCommandID_GetUIMode = 0x35, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_RetUIMode = 0x36, /* 1.09, from dev, ui-mode.h */ + IAPGeneralCommandID_SetUIMode = 0x37, /* 1.09, from acc, ui-mode.h */ + IAPGeneralCommandID_StartIDPS = 0x38, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_SetFIDTokenValues = 0x39, /* 1.09, from acc, fid-token-values.h */ + IAPGeneralCommandID_AckFIDTokenValues = 0x3A, /* 1.09, from dev, fid-token-values.h */ + IAPGeneralCommandID_EndIDPS = 0x3B, /* 1.09, from acc, end-idps.h */ + IAPGeneralCommandID_IDPSStatus = 0x3C, /* 1.09, from dev, end-idps.h */ + IAPGeneralCommandID_OpenDataSessionForProtocol = 0x3F, /* 1.09, from dev, data-session.h */ + IAPGeneralCommandID_CloseDataSession = 0x40, /* 1.09, from dev, data-session.h */ + IAPGeneralCommandID_AccessoryAck = 0x41, /* 1.09, from acc, acc-ack.h */ + IAPGeneralCommandID_AccessoryDataTransfer = 0x42, /* 1.09, from acc, data-transfer.h */ + IAPGeneralCommandID_IPodDataTransfer = 0x43, /* 1.09, from dev, data-transfer.h */ + IAPGeneralCommandID_SetAccessoryStatusNotification = 0x46, /* 1.09, from dev, acc-status-notification.h */ + IAPGeneralCommandID_RetAccessoryStatusNotification = 0x47, /* 1.09, from acc, acc-status-notification.h */ + IAPGeneralCommandID_AccessoryStatusNotification = 0x48, /* 1.09, from acc, acc-status-notification.h */ + IAPGeneralCommandID_SetEventNotification = 0x49, /* 1.09, from acc, event-notification.h */ + IAPGeneralCommandID_IPodNotification = 0x4A, /* 1.09, from dev, ipod-notifiction.h */ + IAPGeneralCommandID_GetIPodOptionsForLingo = 0x4B, /* 1.09, from acc, ipod-options-for-lingo.h */ + IAPGeneralCommandID_RetIPodOptionsForLingo = 0x4C, /* 1.09, from dev, ipod-options-for-lingo.h */ + IAPGeneralCommandID_GetEventNotification = 0x4D, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_RetEventNotification = 0x4E, /* 1.09, from dev, event-notification.h */ + IAPGeneralCommandID_GetSupportedEventNotification = 0x4F, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_CancelCommand = 0x50, /* 1.09, from acc, cancel-command.h */ + IAPGeneralCommandID_RetSupportedEventNotification = 0x51, /* 1.09, from dev, event-notification.h */ + IAPGeneralCommandID_SetAvailableCurrent = 0x54, /* 1.09, from acc, set-available-current.h */ + IAPGeneralCommandID_SetInternalBatteryChargingState = 0x56, /* 1.09, from acc, set-internal-battery-charging-state.h */ + IAPGeneralCommandID_RequestApplicationLaunch = 0x64, /* 1.09, from acc, request-application-launch.h */ + IAPGeneralCommandID_GetNowPlayingApplicationBundleName = 0x65, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_RetNowPlayingApplicationBundleName = 0x66, /* 1.09, from dev, now-playing-app-bundle-name.h */ + IAPGeneralCommandID_GetLocalizationInfo = 0x67, /* 1.09, from acc, localization-info.h */ + IAPGeneralCommandID_RetLocalizationInfo = 0x68, /* 1.09, from dev, localization-info.h */ + IAPGeneralCommandID_RequestWiFiConnectionInfo = 0x69, /* 1.09, from acc, no payload */ + IAPGeneralCommandID_WiFiConnectionInfo = 0x6A, /* 1.09, from dev, wifi-connection-info.h */ +}; + +#include "general/acc-ack.h" +#include "general/acc-auth-info.h" +#include "general/acc-auth-sig.h" +#include "general/acc-info.h" +#include "general/acc-status-notification.h" +#include "general/cancel-command.h" +#include "general/data-session.h" +#include "general/data-transfer.h" +#include "general/end-idps.h" +#include "general/event-notification.h" +#include "general/extended-interface-mode.h" +#include "general/fid-token-values.h" +#include "general/identify-device-lingoes.h" +#include "general/identify.h" +#include "general/ipod-ack.h" +#include "general/ipod-auth-info.h" +#include "general/ipod-auth-sig.h" +#include "general/ipod-name.h" +#include "general/ipod-notification.h" +#include "general/ipod-options-for-lingo.h" +#include "general/ipod-options.h" +#include "general/ipod-preferences.h" +#include "general/ipod-software-version.h" +#include "general/lingo-protocol-version.h" +#include "general/localization-info.h" +#include "general/now-playing-app-bundle-name.h" +#include "general/request-application-launch.h" +#include "general/set-available-current.h" +#include "general/transport-max-payload-size.h" +#include "general/ui-mode.h" +#include "general/wifi-connection-info.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-ack.h new file mode 100644 index 0000000000..3d9a4d91ef --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-ack.h @@ -0,0 +1,7 @@ +#pragma once +#include + +struct IAPAccAckPayload { + uint8_t status; /* = IAPAckStatus_{Success,EBadParameter} */ + uint8_t id; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-info.h new file mode 100644 index 0000000000..2dd764e93e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-info.h @@ -0,0 +1,27 @@ +#pragma once +#include + +struct IAPRetAccAuthInfoPayload { + uint8_t protocol_major; + uint8_t protocol_minor; +} __attribute__((packed)); + +struct IAPRetAccAuthInfoPayload2p0 { + uint8_t protocol_major; /* = 0x02 */ + uint8_t protocol_minor; /* = 0x00 */ + uint8_t cert_current_section_index; + uint8_t cert_max_section_index; + uint8_t cert_data[]; +} __attribute__((packed)); + +enum IAPAckAccAuthInfoStatus { + IAPAckAccAuthInfoStatus_Supported = 0x00, + IAPAckAccAuthInfoStatus_CertTooLong = 0x04, + IAPAckAccAuthInfoStatus_Unsupported = 0x08, + IAPAckAccAuthInfoStatus_InvalidCert = 0x0A, + IAPAckAccAuthInfoStatus_InvalidCertPerm = 0x0B, +}; + +struct IAPAckAccAuthInfoPayload { + uint8_t status; /* IAPAckAccAuthInfoStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-sig.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-sig.h new file mode 100644 index 0000000000..9fd22d2598 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-auth-sig.h @@ -0,0 +1,22 @@ +#pragma once +#include + +struct IAPGetAccAuthSigPayload1p0 { + uint8_t challenge[16]; + uint8_t retry; +} __attribute__((packed)); + +struct IAPGetAccAuthSigPayload2p0 { + uint8_t challenge[20]; + uint8_t retry; +} __attribute__((packed)); + +/* +struct IAPRetAccAuthSigPayload { + uint8_t sig[]; +} __attribute__((packed)); +*/ + +struct IAPAckAccAuthSigPayload { + uint8_t status; /* IAPAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-info.h new file mode 100644 index 0000000000..6f91bee72e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-info.h @@ -0,0 +1,83 @@ +#pragma once +#include + +enum IAPAccInfoType { + IAPAccInfoType_AccInfoCaps = 0x00, /* G1 -> R2 */ + IAPAccInfoType_AccName = 0x01, /* G1 -> R1 */ + IAPAccInfoType_MinDeviceFirmwareVersion = 0x02, /* G2 -> R3 */ + IAPAccInfoType_MinLingoVersion = 0x03, /* G3 -> R4 */ + IAPAccInfoType_FirmwareVersion = 0x04, /* G1 -> R5 */ + IAPAccInfoType_HardwareVersion = 0x05, /* G1 -> R5 */ + IAPAccInfoType_Manufacture = 0x06, /* G1 -> R2 */ + IAPAccInfoType_ModelNumber = 0x07, /* G1 -> R2 */ + IAPAccInfoType_SerialNumber = 0x08, /* G1 -> R2 */ + IAPAccInfoType_MaxPayloadSize = 0x09, /* G1 -> R6 */ + IAPAccInfoType_SupportedStatusTypes = 0x0B, /* G1 -> R7 */ +}; + +/* G1 */ +struct IAPGetAccInfoPayload { + uint8_t type; /* IAPAccInfoType */ +} __attribute__((packed)); + +/* G2 */ +struct IAPGetAccInfoMinDeviceFirmwareVersionPayload { + uint8_t type; /* = IAPAccInfoType_MinDeviceFirmwareVersion */ + uint32_t model_id; +} __attribute__((packed)); + +/* G3 */ +struct IAPGetAccInfoMinLingoVersionPayload { + uint8_t type; /* = IAPAccInfoType_MinLingoVersion */ + uint8_t lingo_id; +}; + +/* R1 */ +struct IAPRetAccInfoPayload { + uint8_t type; /* IAPAccInfoType */ + char value[]; +}; + +/* R2 */ +struct IAPRetAccInfoCapsPayload { + uint8_t type; /* = IAPAccInfoType_AccInfoCaps */ + uint32_t caps; +} __attribute__((packed)); + +/* R3 */ +struct IAPRetAccInfoMinDeviceFirmwareVersionPayload { + uint8_t type; /* = IAPAccInfoType_MinDeviceFirmwareVersion */ +}; + +/* R4 */ +struct IAPRetAccInfoMinLingoVersionPayload { + uint8_t type; /* = IAPAccInfoType_MinLingoVersion */ + uint8_t lingo_id; + uint8_t major; + uint8_t minor; +}; + +/* R5 */ +struct IAPRetAccInfoFirmHardVersionPayload { + uint8_t type; /* = IAPAccInfoType_{Firmware,Hardware}Version */ + uint8_t major; + uint8_t minor; + uint8_t revision; +} __attribute__((packed)); + +/* R6 */ +struct IAPRetAccInfoMaxPayloadSizePayload { + uint8_t type; /* = IAPAccInfoType_MaxPayloadSize */ + uint16_t max_payload_size; +} __attribute__((packed)); + +enum IAPAccInfoStatusTypes { + IAPAccInfoStatusTypes_Bluetooth = 0b0010, + IAPAccInfoStatusTypes_FaultCondition = 0b0100, +}; + +/* R7 */ +struct IAPRetAccInfoSupportedStatusTypes { + uint8_t type; /* = IAPAccInfoType_SupportedStatusTypes */ + uint32_t status_types; /* IAPAccInfoStatusTypes */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-status-notification.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-status-notification.h new file mode 100644 index 0000000000..501d6d2ce6 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/acc-status-notification.h @@ -0,0 +1,18 @@ +#pragma once +#include + +struct IAPSetAccStatusNotificationPayload { + uint32_t mask; /* IAPAccInfoStatusTypes */ +} __attribute__((packed)); + +struct IAPRetAccStatusNotificationPayload { + uint32_t mask; /* IAPAccInfoStatusTypes */ +} __attribute__((packed)); + +struct IAPAccStatusNotificationPayload { + uint32_t type; /* IAPAccInfoStatusTypes */ + uint8_t params[]; +} __attribute__((packed)); + +/* [1] P.181 Table 3-108 AccessoryStatusNotification parameters */ +/* TODO: add definitions */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/cancel-command.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/cancel-command.h new file mode 100644 index 0000000000..af10443935 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/cancel-command.h @@ -0,0 +1,8 @@ +#pragma once +#include + +struct IAPCancelCommandPayload { + uint8_t lingo_id; + uint16_t command_id; + uint16_t transaction_id; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/data-session.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/data-session.h new file mode 100644 index 0000000000..1367987cd6 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/data-session.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPOpenDataSessionForProtocolPayload { + uint16_t session_id; + uint8_t protocol_index; +} __attribute__((packed)); + +struct IAPCloseDataSessionPayload { + uint16_t session_id; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/data-transfer.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/data-transfer.h new file mode 100644 index 0000000000..73cfa522a1 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/data-transfer.h @@ -0,0 +1,12 @@ +#pragma once +#include + +struct IAPAccDataTransferPayload { + uint16_t session_id; + uint8_t data[]; +} __attribute__((packed)); + +struct IAPIPodDataTransferPayload { + uint16_t session_id; + uint8_t data[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/end-idps.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/end-idps.h new file mode 100644 index 0000000000..c9c7fe41e1 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/end-idps.h @@ -0,0 +1,33 @@ +#pragma once +#include + +enum IAPEndIDPSStatus { + IAPEndIDPSStatus_Success = 0x00, + IAPEndIDPSStatus_Reset = 0x01, + IAPEndIDPSStatus_Abort = 0x02, + IAPEndIDPSStatus_AnotherTransport = 0x03, +}; + +struct IAPEndIDPSPayload { + uint8_t status; /* IAPEndIDPSStatus */ +}; + +enum IAPIDPSStatus { + /* IAPEndIDPSStatus_Success */ + IAPIDPSStatus_Success = 0x00, + IAPIDPSStatus_RequiredTokenRejected = 0x01, + IAPIDPSStatus_RequiredTokenMissing = 0x02, + IAPIDPSStatus_RequiredTokenRejectedMissing = 0x03, + /* IAPEndIDPSStatus_Reset */ + IAPIDPSStatus_NotTimedOut = 0x04, + IAPIDPSStatus_TimedOut = 0x05, + /* IAPEndIDPSStatus_Abort */ + IAPIDPSStatus_Aborted = 0x06, + /* IAPEndIDPSStatus_AnotherTransport */ + IAPIDPSStatus_Failed = 0x07, + +}; + +struct IAPIDPSStatusPayload { + uint8_t status; /* IAPIDPSStatus */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/event-notification.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/event-notification.h new file mode 100644 index 0000000000..d6ac7f1505 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/event-notification.h @@ -0,0 +1,31 @@ +#pragma once +#include + +/* [1] P.184 Table 3-112 Notification bitmask bits */ + +enum IAPEventNotificationEvents { + IAPSetEventNotificationEvents_FlowControl = 1 << 2, + IAPSetEventNotificationEvents_RadioTagging = 1 << 3, + IAPSetEventNotificationEvents_Camera = 1 << 4, + IAPSetEventNotificationEvents_ChargingInfo = 1 << 5, + IAPSetEventNotificationEvents_DatabaseChanged = 1 << 9, + IAPSetEventNotificationEvents_AppBundleName = 1 << 10, + IAPSetEventNotificationEvents_SessionSpaceAvail = 1 << 11, + IAPSetEventNotificationEvents_CommandComplete = 1 << 13, + IAPSetEventNotificationEvents_IPodOutMode = 1 << 15, + IAPSetEventNotificationEvents_BluetoothConnection = 1 << 17, + IAPSetEventNotificationEvents_AppDisplayName = 1 << 19, + IAPSetEventNotificationEvents_AssistiveTouch = 1 << 20, +}; + +struct IAPSetEventNotificationPayload { + uint64_t mask; /* IAPEventNotificationEvents */ +} __attribute__((packed)); + +struct IAPRetEventNotificationPayload { + uint64_t mask; /* IAPEventNotificationEvents */ +} __attribute__((packed)); + +struct IAPRetSupportedEventNotificationPayload { + uint64_t mask; /* IAPEventNotificationEvents */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/extended-interface-mode.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/extended-interface-mode.h new file mode 100644 index 0000000000..5a19804e26 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/extended-interface-mode.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* [1] P.127 3.3.4 Command 0x04: ReturnExtendedInterfaceMode */ + +struct IAPReturnExtendedInterfaceModePayload { + uint8_t is_ext_mode; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values.h new file mode 100644 index 0000000000..01bbf6ffbd --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values.h @@ -0,0 +1,58 @@ +#pragma once +#include + +/* [1] P.161 Table 3-67 FIDTokenValues tokens */ +/* = 0x{type}{subtype} */ +enum IAPFIDTokenTypes { + IAPFIDTokenTypes_Identify = 0x0000, + IAPFIDTokenTypes_AccCaps = 0x0001, + IAPFIDTokenTypes_AccInfo = 0x0002, + IAPFIDTokenTypes_IPodPreference = 0x0003, + IAPFIDTokenTypes_EAProtocol = 0x0004, + IAPFIDTokenTypes_BundleSeedIDPref = 0x0005, + IAPFIDTokenTypes_ScreenInfo = 0x0007, + IAPFIDTokenTypes_EAProtocolMetadata = 0x0008, + IAPFIDTokenTypes_AccDigitalAudioSampleRates = 0x000E, + IAPFIDTokenTypes_AccDigitalAudioVideoDelay = 0x000F, + IAPFIDTokenTypes_MicrophoneCaps = 0x0100, +}; + +/* [1] P.161 Table 3-66 FIDTokenValues field format */ +struct IAPFIDTokenValuesToken { + uint8_t length; + uint8_t type; + uint8_t subtype; + uint8_t data[]; +} __attribute__((packed)); + +/* [1] P.170 Table 3-86 Acknowledgment status codes */ +enum IAPFIDTokenValuesAckStatus { + IAPFIDTokenValuesIdentifyAckStatus_Accepted = 0x00, + IAPFIDTokenValuesIdentifyAckStatus_RequiredFailed = 0x01, + IAPFIDTokenValuesIdentifyAckStatus_OptionalFailed = 0x02, + IAPFIDTokenValuesIdentifyAckStatus_NotSupported = 0x03, + IAPFIDTokenValuesIdentifyAckStatus_LingoBusy = 0x04, + IAPFIDTokenValuesIdentifyAckStatus_MaxConnections = 0x05, +}; + +#include "fid-token-values/acc-caps.h" +#include "fid-token-values/acc-digital-audio-sample-rates.h" +#include "fid-token-values/acc-digital-audio-video-delay.h" +#include "fid-token-values/acc-info.h" +#include "fid-token-values/bundle-seed-id-pref.h" +#include "fid-token-values/ea-protocol-metadata.h" +#include "fid-token-values/ea-protocol.h" +#include "fid-token-values/identify.h" +#include "fid-token-values/ipod-preference.h" +#include "fid-token-values/microphone-caps.h" +#include "fid-token-values/screen-info.h" + +struct IAPSetFIDTokenValuesPayload { + uint8_t num_token_values; + uint8_t data[]; /* packed token values */ +}; + +struct IAPAckFIDTokenValuesPayload { + uint8_t num_token_value_acks; + uint8_t data[]; /* packed token value acks */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-caps.h new file mode 100644 index 0000000000..3a86700594 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-caps.h @@ -0,0 +1,31 @@ +#pragma once +#include + +/* [1] P.164 Table 3-71 Accessory capabilities bit values */ +enum IAPFIDTokenValuesAccCapsBits { + IAPFIDTokenValuesAccCapsBits_LineOut = 1 << 0, + IAPFIDTokenValuesAccCapsBits_LineIn = 1 << 1, + IAPFIDTokenValuesAccCapsBits_VideoOut = 1 << 2, + IAPFIDTokenValuesAccCapsBits_USBAudioOut = 1 << 4, + IAPFIDTokenValuesAccCapsBits_AppsCommunication = 1 << 9, + IAPFIDTokenValuesAccCapsBits_CheckVolume = 1 << 11, + IAPFIDTokenValuesAccCapsBits_VoiceOver = 1 << 17, + IAPFIDTokenValuesAccCapsBits_AsyncPlaybackChange = 1 << 18, + IAPFIDTokenValuesAccCapsBits_MixedResponse = 1 << 19, + IAPFIDTokenValuesAccCapsBits_AudioRoutingSwitch = 1 << 21, + IAPFIDTokenValuesAccCapsBits_AssistiveTouch = 1 << 23, +}; + +struct IAPFIDTokenValuesAccCapsToken { + uint8_t length; /* = 0x0A */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x01 */ + uint64_t caps_bits; /* IAPFIDTokenValuesAccCapsBits */ +} __attribute__((packed)); + +struct IAPFIDTokenValuesAccCapsAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x01 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-sample-rates.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-sample-rates.h new file mode 100644 index 0000000000..e9ee239aaa --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-sample-rates.h @@ -0,0 +1,16 @@ +#pragma once +#include + +struct IAPFIDTokenValuesAccDigitalAudioSampleRatesToken { + uint8_t length; + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x0E */ + uint32_t sample_rates[]; +} __attribute__((packed)); + +struct IAPFIDTokenValuesAccDigitalAudioSampleRatesAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x0E */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-video-delay.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-video-delay.h new file mode 100644 index 0000000000..b658d55c5f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-digital-audio-video-delay.h @@ -0,0 +1,16 @@ +#pragma once +#include + +struct IAPFIDTokenValuesAccDigitalAudioVideoDelayToken { + uint8_t length; /* = 0x06 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x0F */ + uint32_t delay; +} __attribute__((packed)); + +struct IAPFIDTokenValuesAccDigitalAudioVideoDelayAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x0F */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-info.h new file mode 100644 index 0000000000..94926ae7d9 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/acc-info.h @@ -0,0 +1,33 @@ +#pragma once +#include + +/* [1] P.165 Table 3-73 Accessory Info Type values */ +enum IAPFIDTokenValuesAccInfoTypes { + IAPFIDTokenValuesAccInfoTypes_AccName = 0x01, + IAPFIDTokenValuesAccInfoTypes_FirmwareVersion = 0x04, + IAPFIDTokenValuesAccInfoTypes_HardwareVersion = 0x05, + IAPFIDTokenValuesAccInfoTypes_Manufacture = 0x06, + IAPFIDTokenValuesAccInfoTypes_ModelNumber = 0x07, + IAPFIDTokenValuesAccInfoTypes_SerialNumber = 0x08, + IAPFIDTokenValuesAccInfoTypes_MaxPayloadSize = 0x09, + IAPFIDTokenValuesAccInfoTypes_AccStatus = 0x0B, + IAPFIDTokenValuesAccInfoTypes_RFCerts = 0x0C, +}; + +struct IAPFIDTokenValuesAccInfoToken { + uint8_t length; + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x02 */ + uint8_t info_type; /* IAPFIDTokenValuesAccInfoTypes */ + uint8_t info[]; +} __attribute__((packed)); + +/* TODO: add info definitions */ + +struct IAPFIDTokenValuesAccInfoAck { + uint8_t length; /* = 0x04 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x02 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ + uint8_t info_type; /* IAPFIDTokenValuesAccInfoTypes */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/bundle-seed-id-pref.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/bundle-seed-id-pref.h new file mode 100644 index 0000000000..144ebc3ac8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/bundle-seed-id-pref.h @@ -0,0 +1,16 @@ +#pragma once +#include + +struct IAPFIDTokenValuesBundleSeedIDPrefToken { + uint8_t length; /* = 0x0D */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x05 */ + char bundle_seed_id_string[11]; +} __attribute__((packed)); + +struct IAPFIDTokenValuesBundleSeedIDPrefAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x05 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol-metadata.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol-metadata.h new file mode 100644 index 0000000000..7ec88e97b7 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol-metadata.h @@ -0,0 +1,23 @@ +#pragma once +#include + +enum IAPFIDTokenValuesEAProtocolMetadataTypes { + IAPFIDTokenValuesEAProtocolMetadataTypes_DontFindApp = 0x00, + IAPFIDTokenValuesEAProtocolMetadataTypes_FindApp = 0x01, + IAPFIDTokenValuesEAProtocolMetadataTypes_DontFindButDisplayMessage = 0x03, +}; + +struct IAPFIDTokenValuesEAProtocolMetadataToken { + uint8_t length; /* = 0x04 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x08 */ + uint8_t protocol_index; + uint8_t metadata_type; /* IAPFIDTokenValuesEAProtocolMetadataTypes */ +} __attribute__((packed)); + +struct IAPFIDTokenValuesEAProtocolMetadataAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x08 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol.h new file mode 100644 index 0000000000..20558ea7d3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ea-protocol.h @@ -0,0 +1,20 @@ +#pragma once +#include + +/* [1] P.167 Table 3-76 EAProtocolToken format */ +struct IAPFIDTokenValuesEAProtocolToken { + uint8_t length; + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x04 */ + uint8_t protocol_index; + char protocol_string[]; +} __attribute__((packed)); + +/* [1] P.171 Table 3-90 Acknowledgment format for EAProtocolToken */ +struct IAPFIDTokenValuesEAProtocolAck { + uint8_t length; /* = 0x04 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x04 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ + uint8_t protocol_index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/identify.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/identify.h new file mode 100644 index 0000000000..8779774b28 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/identify.h @@ -0,0 +1,28 @@ +#pragma once +#include + +struct IAPFIDTokenValuesIdentifyTokenHead { + uint8_t length; + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x00 */ + uint8_t num_lingoes; +} __attribute__((packed)); + +struct IAPFIDTokenValuesIdentifyTokenTail { + uint32_t device_option; /* = 0b10 */ + uint32_t device_id; +} __attribute__((packed)); + +struct IAPFIDTokenValuesIdentifyToken { + struct IAPFIDTokenValuesIdentifyTokenHead head; + /*uint8_t lingoes[num_lingoes]; */ + struct IAPFIDTokenValuesIdentifyTokenHead tail; +} __attribute__((packed)); + +struct IAPFIDTokenValuesIdentifyAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x00 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ + uint8_t lingo_ids[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ipod-preference.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ipod-preference.h new file mode 100644 index 0000000000..722cc1db11 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/ipod-preference.h @@ -0,0 +1,21 @@ +#pragma once +#include + +/* [1] P.166 Table 3-75 iPodPreferenceToken format */ +struct IAPFIDTokenValuesIPodPreferenceToken { + uint8_t length; /* = 0x05 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x03 */ + uint8_t class_id; /* IAPIPodPereferenceClassID */ + uint8_t setting_id; + uint8_t restore_on_exit; /* = 0x01 */ +} __attribute__((packed)); + +/* [1] P.171 Table 3-89 Acknowledgment format for ipodPreferenceToken */ +struct IAPFIDTokenValuesIPodPreferenceAck { + uint8_t length; /* = 0x04 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x03 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ + uint8_t class_id; /* IAPIPodPereferenceClassID */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/microphone-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/microphone-caps.h new file mode 100644 index 0000000000..947f7852b6 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/microphone-caps.h @@ -0,0 +1,24 @@ +#pragma once +#include + +enum IAPFIDTokenValuesMicrophoneCaps { + IAPFIDTokenValuesMicrophoneCaps_StereoInput = 1 << 0, + IAPFIDTokenValuesMicrophoneCaps_StereoMonoInputSwitch = 1 << 1, + IAPFIDTokenValuesMicrophoneCaps_VariableRecordLevel = 1 << 2, + IAPFIDTokenValuesMicrophoneCaps_RecordLevelLimit = 1 << 3, + IAPFIDTokenValuesMicrophoneCaps_DuplexAudio = 1 << 4, +}; + +struct IAPFIDTokenValuesMicrophoneCapsToken { + uint8_t length; /* = 0x06 */ + uint8_t type; /* = 0x01 */ + uint8_t subtype; /* = 0x00 */ + uint32_t caps_bits; /* IAPFIDTokenValuesMicrophoneCaps */ +} __attribute__((packed)); + +struct IAPFIDTokenValuesMicrophoneCapsAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x01 */ + uint8_t subtype; /* = 0x00 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/screen-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/screen-info.h new file mode 100644 index 0000000000..a05c8b0faf --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/fid-token-values/screen-info.h @@ -0,0 +1,27 @@ +#pragma once +#include + +enum IAPFIDTokenValuesScreenInfoFeatureBits { + IAPFIDTokenValuesScreenInfoFeatureBits_ColorDisplay = 1 << 0, +}; + +struct IAPFIDTokenValuesScreenInfoToken { + uint8_t length; /* = 0x10 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x07 */ + uint16_t total_screen_width_inches; + uint16_t total_screen_height_inches; + uint16_t total_screen_width_pixels; + uint16_t total_screen_height_pixels; + uint16_t ipod_out_screen_width_pixels; + uint16_t ipod_out_screen_height_pixels; + uint8_t screen_feature_mask; /* IAPFIDTokenValuesScreenInfoFeatureBits */ + uint8_t screen_gamma_value; +} __attribute__((packed)); + +struct IAPFIDTokenValuesScreenInfoAck { + uint8_t length; /* = 0x03 */ + uint8_t type; /* = 0x00 */ + uint8_t subtype; /* = 0x07 */ + uint8_t status; /* IAPFIDTokenValuesAckStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/identify-device-lingoes.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/identify-device-lingoes.h new file mode 100644 index 0000000000..d2c36955e3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/identify-device-lingoes.h @@ -0,0 +1,30 @@ +#pragma once +#include + +/* [1] P.134 Table 3-24 Device Lingoes Spoken bits */ +enum IAPIdentifyDeviceLingoesLingoBits { + IAPIdentifyDeviceLingoesLingoBits_General = 1 << 0, + IAPIdentifyDeviceLingoesLingoBits_SimpleRemote = 1 << 2, + IAPIdentifyDeviceLingoesLingoBits_DisplayRemote = 1 << 3, + IAPIdentifyDeviceLingoesLingoBits_ExtendedInterface = 1 << 4, + IAPIdentifyDeviceLingoesLingoBits_AccessoryPower = 1 << 5, + IAPIdentifyDeviceLingoesLingoBits_USBHostMode = 1 << 6, + IAPIdentifyDeviceLingoesLingoBits_RFTuner = 1 << 7, + IAPIdentifyDeviceLingoesLingoBits_AccessoryEqualizer = 1 << 8, + IAPIdentifyDeviceLingoesLingoBits_Sports = 1 << 9, + IAPIdentifyDeviceLingoesLingoBits_DigitalAudio = 1 << 10, + IAPIdentifyDeviceLingoesLingoBits_Storage = 1 << 12, +}; + +/* [1] P.135 Table 3-25 IdentifyDeviceLingoes Options bits */ +enum IAPIdentifyDeviceLingoesOptions { + IAPIdentifyDeviceLingoesOptions_NoAuth = 0b00, + IAPIdentifyDeviceLingoesOptions_DeferAuth = 0b01, + IAPIdentifyDeviceLingoesOptions_ImmediateAuth = 0b10, +}; + +struct IAPIdentifyDeviceLingoesPayload { + uint32_t lingoes_bits; /* IAPIdentifyDeviceLingoesLingoBits */ + uint32_t options; /* IAPIdentifyDeviceLingoesOptions */ + uint32_t device_id; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/identify.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/identify.h new file mode 100644 index 0000000000..ef469fffa6 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/identify.h @@ -0,0 +1,15 @@ +#pragma once +#include + +/* [1] P.531 Table C-8 Identify packet */ +struct IAPIdentifyPayload { + uint8_t supported_lingo; +}; + +/* [1] P.531 Table C-9 Identify packet for high-power accessories */ +struct IAPIdentifyHighPowerPayload { + uint8_t supported_lingo; /* = IAPLingoID_AccessoryPower */ + uint8_t reserved; /* = 0x00 */ + uint8_t num_valid_bits; /* = 0x02 */ + uint8_t option_flags; /* TODO: add bitfield definition */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-ack.h new file mode 100644 index 0000000000..3dfe657a64 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-ack.h @@ -0,0 +1,57 @@ +#pragma once +#include + +/* [1] P.124 3.3.2 Command 0x02: iPodAck */ + +enum IAPAckStatus { + IAPAckStatus_Success = 0x00, + IAPAckStatus_EUnknownDatabaseOrSessionID = 0x01, + IAPAckStatus_ECommandFailed = 0x02, + IAPAckStatus_EOutOfResource = 0x03, + IAPAckStatus_EBadParameter = 0x04, + IAPAckStatus_EUnknownID = 0x05, + IAPAckStatus_CommandPending = 0x06, + IAPAckStatus_ENotAuthenticated = 0x07, + IAPAckStatus_EBadAuthVersion = 0x08, + IAPAckStatus_EAccPowerModeRequestFailed = 0x09, + IAPAckStatus_EInvalidCert = 0x0A, + IAPAckStatus_EInvalidCertPerm = 0x0B, + IAPAckStatus_EFileInUse = 0x0C, + IAPAckStatus_EInvalidFileHandle = 0x0D, + IAPAckStatus_EDirectoryNotEmpty = 0x0E, + IAPAckStatus_EOperationTimedOut = 0x0F, + IAPAckStatus_ECommandUnavailable = 0x10, + IAPAckStatus_EBadWire = 0x11, + IAPAckStatus_ESelectionNotGenius = 0x12, + IAPAckStatus_MultiDataRecvSuccess = 0x13, + IAPAckStatus_ELingoBusy = 0x14, + IAPAckStatus_EMaxNumAcc = 0x15, + IAPAckStatus_HIDIndexInUse = 0x16, + IAPAckStatus_EDropped = 0x17, + IAPAckStatus_EInvalidVideoSettings = 0x18, +}; + +struct IAPIPodAckPayload { + uint8_t status; /* IAPAckStatus */ + uint8_t id; +} __attribute__((packed)); + +struct IAPIPodAckCommandPendingPayload { + uint8_t status; /* = IAPAckStatus_CommandPending */ + uint8_t id; + uint32_t time_ms; +} __attribute__((packed)); + +/* [1] P.389 Table 4-292 Multisection iPodAck packet */ +struct IAPIPodAckMultiSectPayload { + uint8_t status; /* IAPAckStatus_IAPAckStatus_MultiDataRecvSuccess */ + uint8_t id; + uint16_t sect_cur; +}; + +struct IAPIPodAckEDroppedPayload { + uint8_t status; /* = IAPAckStatus_EDropped */ + uint8_t id; /* = IAPCommandIDGeneral_AccessoryDataTransfer */ + uint16_t session_id; + uint32_t num_dropped_bytes; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-info.h new file mode 100644 index 0000000000..de03274418 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-info.h @@ -0,0 +1,14 @@ +#pragma once +#include + +struct IAPRetIPodAuthInfoPayload2p0 { + uint8_t protocol_major; /* = 0x02 */ + uint8_t protocol_minor; /* = 0x00 */ + uint8_t cert_current_section_index; + uint8_t cert_max_section_index; + uint8_t cert_data[]; +} __attribute__((packed)); + +struct IAPAckIPodAuthInfoPayload { + uint8_t status; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-sig.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-sig.h new file mode 100644 index 0000000000..977b299790 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-auth-sig.h @@ -0,0 +1,17 @@ +#pragma once +#include + +struct IAPGetIPodAuthSigPayload2p0 { + uint8_t challenge[20]; + uint8_t retry; +} __attribute__((packed)); + +/* +struct IAPRetIPodAuthSigPayload { + uint8_t sig[]; +} __attribute__((packed)); +*/ + +struct IAPAckIPodAuthSigPayload { + uint8_t status; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-name.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-name.h new file mode 100644 index 0000000000..7cedcedeff --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-name.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* +struct IAPReturnIPodNamePayload { + char name[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-notification.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-notification.h new file mode 100644 index 0000000000..2674c4867e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-notification.h @@ -0,0 +1,120 @@ +#pragma once +#include + +enum IAPIPodNotificationType { + IAPIPodNotificationType_FlowControl = 2, + IAPIPodNotificationType_RadioTagging = 3, + IAPIPodNotificationType_Camera = 4, + IAPIPodNotificationType_ChargingInfo = 5, + IAPIPodNotificationType_DatabaseChanged = 9, + IAPIPodNotificationType_AppBundleName = 10, + IAPIPodNotificationType_SessionSpaceAvail = 11, + IAPIPodNotificationType_CommandComplete = 13, + IAPIPodNotificationType_IPodOutMode = 15, + IAPIPodNotificationType_BluetoothConnection = 17, + IAPIPodNotificationType_AppDisplayName = 19, + IAPIPodNotificationType_AssistiveTouch = 20, +}; + +struct IAPIPodNotificationPayload { + uint8_t type; /* IAPIPodNotificationType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPIPodNotificationFlowControlPayload { + uint8_t type; /* = IAPIPodNotificationType_FlowControl */ + uint32_t wait_time_ms; + uint16_t overflow_transaction_id; +} __attribute__((packed)); + +enum IAPIPodNotificationRadioTaggingStatus { + IAPIPodNotificationRadioTaggingStatus_Success = 0x00, + IAPIPodNotificationRadioTaggingStatus_Failed = 0x01, + IAPIPodNotificationRadioTaggingStatus_InfoAvail = 0x02, + IAPIPodNotificationRadioTaggingStatus_InfoNotAvail = 0x03, +}; + +struct IAPIPodNotificationRadioTaggingPayload { + uint8_t type; /* = IAPIPodNotificationType_RadioTagging */ + uint8_t status; /* IAPIPodNotificationRadioTaggingStatus */ +} __attribute__((packed)); + +enum IAPIPodNotificationCameraStatus { + IAPIPodNotificationCameraStatus_AppOff = 0x00, + IAPIPodNotificationCameraStatus_Preview = 0x03, + IAPIPodNotificationCameraStatus_Recording = 0x04, +}; + +struct IAPIPodNotificationCameraPayload { + uint8_t type; /* = IAPIPodNotificationType_Camera */ + uint8_t status; /* IAPIPodNotificationCameraStatus */ +} __attribute__((packed)); + +enum IAPIPodNotificationChargingInfoType { + IAPIPodNotificationChargingInfoType_AvailCurrent = 0x00, +}; + +struct IAPIPodNotificationChargingInfoPayload { + uint8_t type; /* = IAPIPodNotificationType_ChargingInfo */ + uint8_t info_type; /* IAPIPodNotificationChargingInfoType */ + uint16_t info_value; +} __attribute__((packed)); + +struct IAPIPodNotificationDatabaseChangedPayload { + uint8_t type; /* = IAPIPodNotificationType_DatabaseChanged */ +} __attribute__((packed)); + +struct IAPIPodNotificationAppBundleNamePayload { + uint8_t type; /* = IAPIPodNotificationType_AppBundleName */ + char name[]; +} __attribute__((packed)); + +struct IAPIPodNotificationSessionSpaceAvailPayload { + uint8_t type; /* = IAPIPodNotificationType_SessionSpaceAvail */ + uint16_t session_id; +} __attribute__((packed)); + +enum IAPIPodNotificationCommandCompleteStatus { + IAPIPodNotificationCommandCompleteStatus_Success = 0x00, + IAPIPodNotificationCommandCompleteStatus_Failed = 0x01, + IAPIPodNotificationCommandCompleteStatus_Cancelled = 0x02, +}; + +struct IAPIPodNotificationCommandCompletePayload { + uint8_t type; /* = IAPIPodNotificationType_CommandComplete */ + uint8_t lingo_id; + uint16_t command_id; + uint8_t status; /* IAPIPodNotificationCommandCompleteStatus */ +} __attribute__((packed)); + +struct IAPIPodNotificationIPodOutModePayload { + uint8_t type; /* = IAPIPodNotificationType_IPodOutMode */ + uint8_t is_active; +} __attribute__((packed)); + +enum IAPIPodNotificationBluetoothConnectionStatusBits { + IAPIPodNotificationBluetoothConnectionStatusBits_HFP = 1 << 0, + IAPIPodNotificationBluetoothConnectionStatusBits_PBAP = 1 << 1, + IAPIPodNotificationBluetoothConnectionStatusBits_AVRCP = 1 << 3, + IAPIPodNotificationBluetoothConnectionStatusBits_A2DP = 1 << 4, + IAPIPodNotificationBluetoothConnectionStatusBits_HID = 1 << 5, + IAPIPodNotificationBluetoothConnectionStatusBits_IAP = 1 << 7, + IAPIPodNotificationBluetoothConnectionStatusBits_PAN_NAP = 1 << 8, + IAPIPodNotificationBluetoothConnectionStatusBits_PAN_U = 1 << 12, +}; + +struct IAPIPodNotificationBluetoothConnectionPayload { + uint8_t type; /* = IAPIPodNotificationType_BluetoothConnection */ + uint8_t mac_addr[6]; + uint64_t status; /* IAPIPodNotificationBluetoothConnectionStatusBits */ +} __attribute__((packed)); + +struct IAPIPodNotificationAppDisplayNamePayload { + uint8_t type; /* = IAPIPodNotificationType_AppDisplayName */ + char name[]; +} __attribute__((packed)); + +struct IAPIPodNotificationAssistiveTouchPayload { + uint8_t type; /* = IAPIPodNotificationType_AssistiveTouch */ + uint8_t is_on; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options-for-lingo.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options-for-lingo.h new file mode 100644 index 0000000000..aeeca1a793 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options-for-lingo.h @@ -0,0 +1,114 @@ +#pragma once +#include + +struct IAPGetIPodOptionsForLingoPayload { + uint8_t lingo_id; +} __attribute__((packed)); + +/* [1] P.192 Table 3-132 RetiPodOptionsForLingo option bits */ + +enum IAPRetIPodOptionsForLingoGeneralBits { + IAPRetIPodOptionsForLingoGeneralBits_LineOutUsage = 1 << 0, + IAPRetIPodOptionsForLingoGeneralBits_VideOutput = 1 << 1, + IAPRetIPodOptionsForLingoGeneralBits_VideoNTSCFormat = 1 << 2, + IAPRetIPodOptionsForLingoGeneralBits_VideoPALFormat = 1 << 3, + IAPRetIPodOptionsForLingoGeneralBits_VideoCompositeConnection = 1 << 4, + IAPRetIPodOptionsForLingoGeneralBits_VideoSVideoConnection = 1 << 5, + IAPRetIPodOptionsForLingoGeneralBits_VideoComponentConnection = 1 << 6, + IAPRetIPodOptionsForLingoGeneralBits_VideoClosedCaptioning = 1 << 7, + IAPRetIPodOptionsForLingoGeneralBits_VideoAspectRatio4x3 = 1 << 8, + IAPRetIPodOptionsForLingoGeneralBits_VideoAspectRatio16x9 = 1 << 9, + IAPRetIPodOptionsForLingoGeneralBits_VideoSubtitle = 1 << 10, + IAPRetIPodOptionsForLingoGeneralBits_VideoAltAudioChannel = 1 << 11, + IAPRetIPodOptionsForLingoGeneralBits_AppCommnunication = 1 << 13, + IAPRetIPodOptionsForLingoGeneralBits_DeviceNotification = 1 << 14, + IAPRetIPodOptionsForLingoGeneralBits_PauseOnRemovalControl = 1 << 19, + IAPRetIPodOptionsForLingoGeneralBits_RestrictedIDPS = 1 << 23, + IAPRetIPodOptionsForLingoGeneralBits_AppLaunchRequest = 1 << 24, + IAPRetIPodOptionsForLingoGeneralBits_AudioRouteSwitch = 1 << 26, + IAPRetIPodOptionsForLingoGeneralBits_USBHostModeHardwareInvoke = 1 << 27, + IAPRetIPodOptionsForLingoGeneralBits_USBHostModeAudioOutput = 1 << 28, + IAPRetIPodOptionsForLingoGeneralBits_USBHostModeAudioInput = 1 << 29, + IAPRetIPodOptionsForLingoGeneralBits_USBHostModeNoMaxCurrent = 1 << 30, +}; + +enum IAPRetIPodOptionsForLingoSimpleRemoteBits { + IAPRetIPodOptionsForLingoSimpleRemoteBits_ContextSpecificControls = 1 << 0, + IAPRetIPodOptionsForLingoSimpleRemoteBits_AudioMediaControls = 1 << 1, + IAPRetIPodOptionsForLingoSimpleRemoteBits_VideoMediaControls = 1 << 2, + IAPRetIPodOptionsForLingoSimpleRemoteBits_ImageMediaControls = 1 << 3, + IAPRetIPodOptionsForLingoSimpleRemoteBits_SportsMediaControls = 1 << 4, + IAPRetIPodOptionsForLingoSimpleRemoteBits_CameraMediaControls = 1 << 8, + IAPRetIPodOptionsForLingoSimpleRemoteBits_USBHIDCommands = 1 << 9, + IAPRetIPodOptionsForLingoSimpleRemoteBits_VoiceOverControls = 1 << 10, + IAPRetIPodOptionsForLingoSimpleRemoteBits_VoiceOverPreferences = 1 << 11, + IAPRetIPodOptionsForLingoSimpleRemoteBits_AssistiveTouchCursor = 1 << 12, +}; + +enum IAPRetIPodOptionsForLingoDisplayRemoteBits { + IAPRetIPodOptionsForLingoDisplayRemoteBits_UIVolumeControl = 1 << 0, + IAPRetIPodOptionsForLingoDisplayRemoteBits_AbsoluteVolumeControl = 1 << 1, + IAPRetIPodOptionsForLingoDisplayRemoteBits_GeniusPlaylistCreation = 1 << 2, +}; + +enum IAPRetIPodOptionsForLingoExtendedInterfaceBits { + IAPRetIPodOptionsForLingoExtendedInterfaceBits_VideoBrowsing = 1 << 0, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_InfoCommands = 1 << 1, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_NestedPlaylists = 1 << 2, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_GeniusPlaylistCreationAndRefresh = 1 << 3, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_DisplayImages = 1 << 4, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_CategoryListAccess = 1 << 5, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_PlayControl = 1 << 6, + IAPRetIPodOptionsForLingoExtendedInterfaceBits_UIDBasedCommands = 1 << 7, +}; + +/* +enum IAPRetIPodOptionsForLingoAccessoryPowerBits { +}; +*/ + +enum IAPRetIPodOptionsForLingoUSBHostModeBits { + IAPRetIPodOptionsForLingoUSBHostModeBits_USBHostModeHardwareInvoke = 1 << 0, /* deprecated */ + IAPRetIPodOptionsForLingoUSBHostModeBits_USBHostModeFirmwareInvoke = 1 << 1, +}; + +enum IAPRetIPodOptionsForLingoRFTunerBits { + IAPRetIPodOptionsForLingoRFTunerBits_RDSRawMode = 1 << 0, + IAPRetIPodOptionsForLingoRFTunerBits_HDRadioTuning = 1 << 1, + IAPRetIPodOptionsForLingoRFTunerBits_AMRadioTuning = 1 << 2, +}; + +/* +enum IAPRetIPodOptionsForLingoAccessoryEqualizerBits { +}; +*/ + +enum IAPRetIPodOptionsForLingoSportsBits { + IAPRetIPodOptionsForLingoSportsBits_NikePlusEquipment = 1 << 1, +}; + +enum IAPRetIPodOptionsForLingoDigitalAudioBits { + IAPRetIPodOptionsForLingoDigitalAudioBits_SetVideoDelayEnabled = 1 << 0, + IAPRetIPodOptionsForLingoDigitalAudioBits_SampleRateCapsFIDToken = 1 << 2, + IAPRetIPodOptionsForLingoDigitalAudioBits_VideoDelayFIDToken = 1 << 3, +}; + +enum IAPRetIPodOptionsForLingoStorageBits { + IAPRetIPodOptionsForLingoStorageBits_ITunesTagging = 1 << 0, + IAPRetIPodOptionsForLingoStorageBits_NikePlusEquipment = 1 << 1, +}; + +enum IAPRetIPodOptionsForLingoIPodOutBits { + IAPRetIPodOptionsForLingoIPodOutBits_WheelUIModeAvail = 1 << 0, + IAPRetIPodOptionsForLingoIPodOutBits_PALVideoEnabled = 1 << 2, +}; + +enum IAPRetIPodOptionsForLingoLocationBits { + IAPRetIPodOptionsForLingoLocationBits_AcceptNMEAData = 1 << 0, + IAPRetIPodOptionsForLingoLocationBits_SendLocationAssistanceData = 1 << 1, +}; + +struct IAPRetIPodOptionsForLingoPayload { + uint8_t lingo_id; + uint64_t bits; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options.h new file mode 100644 index 0000000000..14377c04d9 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-options.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum IAPRetIPodOptionsState { + IAPRetIPodOptionsState_VideoOutputSupport = 1 << 0, + IAPRetIPodOptionsState_SetiPodPreferencesSupport = 1 << 1, +}; + +struct IAPRetIPodOptionsPayload { + uint64_t state; /* IAPRetIPodOptionsState */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-preferences.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-preferences.h new file mode 100644 index 0000000000..ec6d168440 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-preferences.h @@ -0,0 +1,95 @@ +#pragma once +#include + +/* [1] P.152 Table 3-57 Apple device preference class and setting IDs */ +enum IAPIPodPereferenceClassID { + IAPIPodPereferenceClassID_VideoOut = 0x00, + IAPIPodPereferenceClassID_Screen = 0x01, + IAPIPodPereferenceClassID_VideoFormat = 0x02, + IAPIPodPereferenceClassID_LineOutUsage = 0x03, + IAPIPodPereferenceClassID_VideoOutConnection = 0x08, + IAPIPodPereferenceClassID_ClosedCaptioning = 0x09, + IAPIPodPereferenceClassID_VideoAspectRatio = 0x0A, + IAPIPodPereferenceClassID_Subtitle = 0x0C, + IAPIPodPereferenceClassID_VideoAltAudioChannel = 0x0D, + IAPIPodPereferenceClassID_PauseOnPowerRemoval = 0x0F, + IAPIPodPereferenceClassID_VoiceOver = 0x14, + IAPIPodPereferenceClassID_AssistiveTouch = 0x16, +}; + +enum IAPIPodPreferenceVideoOutSettingID { + IAPIPodPreferenceVideoOutSettingID_Off = 0x00, + IAPIPodPreferenceVideoOutSettingID_On = 0x01, +}; + +enum IAPIPodPreferenceScreenSettingID { + IAPIPodPreferenceScreenSettingID_FillScreen = 0x00, + IAPIPodPreferenceScreenSettingID_FitToEdge = 0x01, +}; + +enum IAPIPodPreferenceVideoFormatSettingID { + IAPIPodPreferenceVideoFormatSettingID_NTSC = 0x00, + IAPIPodPreferenceVideoFormatSettingID_PAL = 0x01, +}; + +enum IAPIPodPreferenceLineOutUsageSettingID { + IAPIPodPreferenceLineOutUsageSettingID_NotUsed = 0x00, + IAPIPodPreferenceLineOutUsageSettingID_Used = 0x01, +}; + +enum IAPIPodPreferenceVideoOutConnectionSettingID { + IAPIPodPreferenceVideoOutConnectionSettingID_Composite = 0x01, + IAPIPodPreferenceVideoOutConnectionSettingID_SVideo = 0x02, + IAPIPodPreferenceVideoOutConnectionSettingID_Component = 0x03, +}; + +enum IAPIPodPreferenceClosedCaptioningSettingID { + IAPIPodPreferenceClosedCaptioningSettingID_Off = 0x00, + IAPIPodPreferenceClosedCaptioningSettingID_On = 0x01, +}; + +enum IAPIPodPreferenceVideoAspectRatioSettingID { + IAPIPodPreferenceVideoAspectRatioSettingID_4x3 = 0x00, + IAPIPodPreferenceVideoAspectRatioSettingID_16x9 = 0x01, +}; + +enum IAPIPodPreferenceSubtitleSettingID { + IAPIPodPreferenceSubtitleSettingID_Off = 0x00, + IAPIPodPreferenceSubtitleSettingID_On = 0x01, + +}; + +enum IAPIPodPreferenceVideoAltAudioChannelSettingID { + IAPIPodPreferenceVideoAltAudioChannelSettingID_Off = 0x00, + IAPIPodPreferenceVideoAltAudioChannelSettingID_On = 0x01, +}; + +enum IAPIPodPreferencePauseOnPowerRemovalSettingID { + IAPIPodPreferencePauseOnPowerRemovalSettingID_Off = 0x00, + IAPIPodPreferencePauseOnPowerRemovalSettingID_On = 0x01, +}; + +enum IAPIPodPreferenceVoiceOverSettingID { + IAPIPodPreferenceVoiceOverSettingID_Off = 0x00, + IAPIPodPreferenceVoiceOverSettingID_On = 0x01, +}; + +enum IAPIPodPreferenceAssistiveTouchSettingID { + IAPIPodPreferenceAssistiveTouchSettingID_Off = 0x00, + IAPIPodPreferenceAssistiveTouchSettingID_On = 0x01, +}; + +struct IAPGetIPodPreferencesPayload { + uint8_t class_id; /* IAPIPodPereferenceClassID */ +} __attribute__((packed)); + +struct IAPRetIPodPreferencesPayload { + uint8_t class_id; /* IAPIPodPereferenceClassID */ + uint8_t setting_id; +} __attribute__((packed)); + +struct IAPSetIPodPreferencesPayload { + uint8_t class_id; /* IAPIPodPereferenceClassID */ + uint8_t setting_id; + uint8_t restore_on_exit; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-serial-num.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-serial-num.h new file mode 100644 index 0000000000..3285007403 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-serial-num.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPReturnIPodSerialNumPayload { + char serial[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-software-version.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-software-version.h new file mode 100644 index 0000000000..bb036dbbc9 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ipod-software-version.h @@ -0,0 +1,8 @@ +#pragma once +#include + +struct IAPReturnIPodSoftwareVersionPayload { + uint8_t major; + uint8_t minor; + uint8_t revision; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/lingo-protocol-version.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/lingo-protocol-version.h new file mode 100644 index 0000000000..44569c7c12 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/lingo-protocol-version.h @@ -0,0 +1,12 @@ +#pragma once +#include + +struct IAPRequestLingoProtocolVersionPayload { + uint8_t lingo; +} __attribute__((packed)); + +struct IAPReturnLingoProtocolVersionPayload { + uint8_t lingo; + uint8_t major; + uint8_t minor; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/localization-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/localization-info.h new file mode 100644 index 0000000000..f9a4235834 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/localization-info.h @@ -0,0 +1,16 @@ +#pragma once +#include + +enum IAPLocalicationInfoType { + IAPLocalicationInfoType_Language = 0x00, + IAPLocalicationInfoType_Region = 0x01, +}; + +struct IAPGetLocalizationInfoPayload { + uint8_t type; /* IAPLocalicationInfoType */ +} __attribute__((packed)); + +struct IAPRetLocalizationInfoPayload { + uint8_t type; /* IAPLocalicationInfoType */ + char value[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/notify-ipod-state-change.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/notify-ipod-state-change.h new file mode 100644 index 0000000000..22b187832a --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/notify-ipod-state-change.h @@ -0,0 +1,13 @@ +#pragma once +#include + +enum IAPNotifyIPodStateChangeState { + IAPNotifyIPodStateChangeState_LostContextHibernate = 0x01, + IAPNotifyIPodStateChangeState_SaveContextHibernate = 0x02, + IAPNotifyIPodStateChangeState_Sleep = 0x03, + IAPNotifyIPodStateChangeState_PowerOn = 0x04, +}; + +struct IAPNotifyIPodStateChangePayload { + uint8_t state; /* IAPNotifyIPodStateChangeState */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/now-playing-app-bundle-name.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/now-playing-app-bundle-name.h new file mode 100644 index 0000000000..b6401d555d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/now-playing-app-bundle-name.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* +struct IAPRetNowPlayingApplicationBundleNamePayload { + char name[]; +} __attribute__((packed)); +*/ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/request-application-launch.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/request-application-launch.h new file mode 100644 index 0000000000..8a6ecc3cd7 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/request-application-launch.h @@ -0,0 +1,9 @@ +#pragma once +#include + +struct IAPRequestApplicationLaunchPayload { + uint8_t reserved1; + uint8_t flag; + uint8_t reserved2; + char app_id[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/set-available-current.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/set-available-current.h new file mode 100644 index 0000000000..ed9cf86f30 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/set-available-current.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPSetAvailableCurrentPayload { + uint16_t current_limit_ma; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/set-internal-battery-charging-state.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/set-internal-battery-charging-state.h new file mode 100644 index 0000000000..b5804cc4cb --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/set-internal-battery-charging-state.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum IAPInternalBatteryChargingState { + IAPInternalBatteryChargingState_DontCharge = 0x00, + IAPInternalBatteryChargingState_MayCharge = 0x01, +}; + +struct IAPInternalBatteryChargingStatePayload { + uint8_t state; /* IAPInternalBatteryChargingState */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/transport-max-payload-size.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/transport-max-payload-size.h new file mode 100644 index 0000000000..bf83edd35c --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/transport-max-payload-size.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPReturnTransportMaxPayloadSizePayload { + uint16_t max_payload_size; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/ui-mode.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/ui-mode.h new file mode 100644 index 0000000000..ed0410458a --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/ui-mode.h @@ -0,0 +1,13 @@ +#pragma once +#include + +enum IAPUIMode { + IAPUIMode_Standard = 0x00, + IAPUIMode_Extended = 0x01, + IAPUIMode_IPodOutFullscreen = 0x02, + IAPUIMode_IPodOutActionSafe = 0x03, +}; + +struct IAPSetUIModePayload { + uint8_t ui_mode; /* IAPUIMode */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/general/wifi-connection-info.h b/firmware/usbstack/iap/libiap/spec/lingoes/general/wifi-connection-info.h new file mode 100644 index 0000000000..701bbe5fa0 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/general/wifi-connection-info.h @@ -0,0 +1,24 @@ +#pragma once +#include + +enum IAPWiFiConnectionInfoStatus { + IAPWiFiConnectionInfoStatus_Success = 0x00, + IAPWiFiConnectionInfoStatus_Unavailable = 0x01, + IAPWiFiConnectionInfoStatus_Declined = 0x02, + IAPWiFiConnectionInfoStatus_Failed = 0x03, +}; + +enum IAPWiFiConnectionInfoSecurityType { + IAPWiFiConnectionInfoSecurityType_Unsecured = 0x00, + IAPWiFiConnectionInfoSecurityType_WEP = 0x01, + IAPWiFiConnectionInfoSecurityType_WPA = 0x02, + IAPWiFiConnectionInfoSecurityType_WPA2 = 0x03, + IAPWiFiConnectionInfoSecurityType_MixedWPAWPA2 = 0x04, +}; + +struct IAPWiFiConnectionInfoPayload { + uint8_t status; /* IAPWiFiConnectionInfoStatus */ + uint8_t security_type; /* IAPWiFiConnectionInfoSecurityType */ + char ssid[32]; + char passphrase[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out.h b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out.h new file mode 100644 index 0000000000..cbf6d0b329 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out.h @@ -0,0 +1,14 @@ +#pragma once +#include + +/* [1] P.366 Table 4-258 iPod Out lingo command summary */ +enum IAPIPodOutCommandID { + IAPIPodOutCommandID_IPodAck = 0x00, /* from dev, general/ipod-ack.h */ + IAPIPodOutCommandID_GetIPodOutOptions = 0x01, /* from acc, ipod-out-options.h */ + IAPIPodOutCommandID_RetIPodOutOptions = 0x02, /* from dev, ipod-out-options.h */ + IAPIPodOutCommandID_SetIPodOutOptions = 0x03, /* from acc, ipod-out-options.h */ + IAPIPodOutCommandID_AccessoryStateChangeEvent = 0x04, /* from acc, */ +}; + +#include "ipod-out/accessory-state-change-event.h" +#include "ipod-out/ipod-out-options.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/accessory-state-change-event.h b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/accessory-state-change-event.h new file mode 100644 index 0000000000..4863e89554 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/accessory-state-change-event.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.368 4.12.7 Command 0x04: AccessoryStateChangeEvent */ + +enum IAPAccessoryStateChangeEvents { + IAPAccessoryStateChangeEvents_DisplaySwitchAwayFromIPodOut = 0x00, + IAPAccessoryStateChangeEvents_DisplaySwitchBackToIPodOut = 0x01, + IAPAccessoryStateChangeEvents_AudioSwitchAwayFromIPodOut = 0x02, + IAPAccessoryStateChangeEvents_AudioSwitchBackToIPodOut = 0x03, + IAPAccessoryStateChangeEvents_EnterDaytimeMode = 0x04, + IAPAccessoryStateChangeEvents_EnterNighttimeMode = 0x05, +}; + +struct AccessoryStateChangeEventPayload { + uint8_t state_change; /* IAPAccessoryStateChangeEvents */ +} __attribute__((packed)); + diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/ipod-out-options.h b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/ipod-out-options.h new file mode 100644 index 0000000000..0af8b99ce2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/ipod-out/ipod-out-options.h @@ -0,0 +1,29 @@ +#pragma once +#include + +/* [1] P.366 4.12.4 Command 0x01: GetiPodOutOptions */ + +struct IAPGetIPodOutOptionsPayload { + uint8_t report_current; +} __attribute__((packed)); + +enum IAPIPodOutOptionBits { + IAPIPodOutOptionBits_DisplayAudio = 1 << 0, + IAPIPodOutOptionBits_DisplayPhoneCall = 1 << 1, + IAPIPodOutOptionBits_DisplaySMS = 1 << 2, + IAPIPodOutOptionBits_DisplayVoicemail = 1 << 4, + IAPIPodOutOptionBits_DisplayPushNotifications = 1 << 5, + IAPIPodOutOptionBits_DisplayClockAlarmNotifications = 1 << 6, + IAPIPodOutOptionBits_DisplayTestPattern = 1 << 8, + IAPIPodOutOptionBits_ShowMinimalIPodOut = 1 << 9, + IAPIPodOutOptionBits_ShowFullIPodOud = 1 << 10, +}; + +struct IAPRetIPodOutOptionsPayload { + uint8_t report_current; + uint32_t options; /* IAPIPodOutOptionBits */ +} __attribute__((packed)); + +struct IAPSetIPodOutOptionsPayload { + uint32_t options; /* IAPIPodOutOptionBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/location.h b/firmware/usbstack/iap/libiap/spec/lingoes/location.h new file mode 100644 index 0000000000..8e2a4aabae --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/location.h @@ -0,0 +1,19 @@ +#pragma once +#include + +/* [1] P.371 Table 4-266 Location lingo commands */ +enum IAPLocationCommandID { + IAPLocationCommandID_AccessoryAck = 0x00, /* from acc */ + IAPLocationCommandID_GetAccessoryCaps = 0x01, /* from dev */ + IAPLocationCommandID_AccessoryCaps = 0x02, /* from acc */ + IAPLocationCommandID_GetAccessoryControl = 0x03, /* from dev */ + IAPLocationCommandID_RetAccessoryControl = 0x04, /* from acc */ + IAPLocationCommandID_SetAccessoryControl = 0x05, /* from dev */ + IAPLocationCommandID_GetAccessoryData = 0x06, /* from dev */ + IAPLocationCommandID_RetAccessoryData = 0x07, /* from acc */ + IAPLocationCommandID_SetAccessoryData = 0x08, /* from dev */ + IAPLocationCommandID_AsyncAccessoryData = 0x09, /* from acc */ + IAPLocationCommandID_IPodAck = 0x80, /* from dev */ +}; + +/* TODO: add payload definitions */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/microphone.h b/firmware/usbstack/iap/libiap/spec/lingoes/microphone.h new file mode 100644 index 0000000000..3c90b3800c --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/microphone.h @@ -0,0 +1,22 @@ +#pragma once +#include + +/* [1] P.534 Table C-12 Microphone lingo command summary */ +enum IAPMicrophoneCommandID { + IAPMicrophoneCommandID_BeginRecord = 0x00, /* deprecated */ + IAPMicrophoneCommandID_EndRecord = 0x01, /* deprecated */ + IAPMicrophoneCommandID_BeginPlayback = 0x02, /* deprecated */ + IAPMicrophoneCommandID_EndPlayback = 0x03, /* deprecated */ + IAPMicrophoneCommandID_AccessoryAck = 0x04, /* from acc, general/acc-ack.h */ + IAPMicrophoneCommandID_GetAccessoryAck = 0x05, /* from dev, no payload */ + IAPMicrophoneCommandID_IPodModeChange = 0x06, /* from dev, ipod-mode-change.h */ + IAPMicrophoneCommandID_GetAccessoryCaps = 0x07, /* from dev, no payload */ + IAPMicrophoneCommandID_RetAccessoryCaps = 0x08, /* from acc, acc-caps.h */ + IAPMicrophoneCommandID_GetAccessoryCtrl = 0x09, /* from dev, acc-ctrl.h */ + IAPMicrophoneCommandID_RetAccessoryCtrl = 0x0A, /* from acc, acc-ctrl.h */ + IAPMicrophoneCommandID_SetAccessoryCtrl = 0x0B, /* from dev, acc-ctrl.h */ +}; + +#include "microphone/acc-caps.h" +#include "microphone/acc-ctrl.h" +#include "microphone/ipod-mode-change.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-caps.h new file mode 100644 index 0000000000..3a98aadb32 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-caps.h @@ -0,0 +1,14 @@ +#pragma once +#include + +enum IAPAccCapsBits { + IAPAccCapsBits_StereoInput = 1 << 0, + IAPAccCapsBits_StereoMonoInputSwitch = 1 << 1, + IAPAccCapsBits_VariableRecordLevel = 1 << 2, + IAPAccCapsBits_RecordLevelLimit = 1 << 3, + IAPAccCapsBits_DuplexAudio = 1 << 4, +}; + +struct IAPRetAccCapsPayload { + uint8_t bits; /* IAPAccCapsBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-ctrl.h b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-ctrl.h new file mode 100644 index 0000000000..fe3506aad9 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/acc-ctrl.h @@ -0,0 +1,22 @@ +#pragma once +#include + +enum IAPAccCtrlType { + IAPAccCtrlType_LineInput = 0x01, + IAPAccCtrlType_RecordingLevelControl = 0x02, + IAPAccCtrlType_RecordingLevelLimiterControl = 0x03, +}; + +struct IAPGetAccCtrlPayload { + uint8_t type; /* IAPAccCtrlType */ +} __attribute__((packed)); + +struct IAPRetAccCtrlPayload { + uint8_t type; /* IAPAccCtrlType */ + uint8_t value; +} __attribute__((packed)); + +struct IAPSetAccCtrlPayload { + uint8_t type; /* IAPAccCtrlType */ + uint8_t value; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/microphone/ipod-mode-change.h b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/ipod-mode-change.h new file mode 100644 index 0000000000..55a49be67a --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/microphone/ipod-mode-change.h @@ -0,0 +1,14 @@ +#pragma once +#include + +/* [1] P.537 Table C-18 Mode values */ +enum IAPIPodModeChangeModes { + IAPIPodModeChangeModes_BeginAudioRecord = 0x00, + IAPIPodModeChangeModes_EndAudioRecord = 0x01, + IAPIPodModeChangeModes_BeginAudioPlayback = 0x02, + IAPIPodModeChangeModes_EndAudioPlayback = 0x03, +}; + +struct IAPIPodModeChangePayload { + uint8_t mode; /* IAPIPodModeChangeModes */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner.h new file mode 100644 index 0000000000..11a540b6f6 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner.h @@ -0,0 +1,69 @@ +#pragma once +#include + +/* [1] P.288 Table 4-111 RF Tuner lingo command summary */ +enum IAPRFTunerCommandID { + IAPRFTunerCommandID_AccessoryAck = 0x00, /* 1.00, from acc, acc-ack.h */ + IAPRFTunerCommandID_GetTunerCaps = 0x01, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerCaps = 0x02, /* 1.00, from acc, tuner-caps.h */ + IAPRFTunerCommandID_GetTunerCtrl = 0x03, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerCtrl = 0x04, /* 1.00, from acc, tuner-ctrl.h */ + IAPRFTunerCommandID_SetTunerCtrl = 0x05, /* 1.00, from dev, tuner-ctlr.h */ + IAPRFTunerCommandID_GetTunerBand = 0x06, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerBand = 0x07, /* 1.00, from acc, tuner-band.h */ + IAPRFTunerCommandID_SetTunerBand = 0x08, /* 1.00, from dev, tuner-band.h*/ + IAPRFTunerCommandID_GetTunerFreq = 0x09, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerFreq = 0x0A, /* 1.00, from acc, tuner-freq.h */ + IAPRFTunerCommandID_SetTunerFreq = 0x0B, /* 1.00, from dev, tuner-freq.h */ + IAPRFTunerCommandID_GetTunerMode = 0x0C, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerMode = 0x0D, /* 1.00, from acc, tuner-mode.h */ + IAPRFTunerCommandID_SetTunerMode = 0x0E, /* 1.00, from dev, tuner-mode.h */ + IAPRFTunerCommandID_GetTunerSeekRssi = 0x0F, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerSeekRssi = 0x10, /* 1.00, from acc, tuner-seek-rssi.h */ + IAPRFTunerCommandID_SetTunerSeekRssi = 0x11, /* 1.00, from dev, tuner-seek-rssi.h */ + IAPRFTunerCommandID_TunerSeekStart = 0x12, /* 1.00, from dev, tuner-seek.h */ + IAPRFTunerCommandID_TunerSeekDone = 0x13, /* 1.00, from acc, tuner-seek.h */ + IAPRFTunerCommandID_GetTunerStatus = 0x14, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetTunerStatus = 0x15, /* 1.00, from acc, tuner-status.h */ + IAPRFTunerCommandID_GetStatusNotifyMask = 0x16, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetStatusNotifyMask = 0x17, /* 1.00, from acc, status-notification.h */ + IAPRFTunerCommandID_SetStatusNotifyMask = 0x18, /* 1.00, from dev, status-notification.h */ + IAPRFTunerCommandID_StatusChangeNotify = 0x19, /* 1.00, from acc, status-notification.h */ + IAPRFTunerCommandID_GetRdsReadyStatus = 0x1A, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetRdsReadyStatus = 0x1B, /* 1.00, from acc, rds-ready-status.h */ + IAPRFTunerCommandID_GetRdsData = 0x1C, /* 1.00, from dev, rds-data.h */ + IAPRFTunerCommandID_RetRdsData = 0x1D, /* 1.00, from acc, rds-data.h */ + IAPRFTunerCommandID_GetRdsNotifyMask = 0x1E, /* 1.00, from dev, no payload */ + IAPRFTunerCommandID_RetRdsNotifyMask = 0x1F, /* 1.00, from acc, rds-notify-mask.h */ + IAPRFTunerCommandID_SetRdsNotifyMask = 0x20, /* 1.00, from dev, rds-notify-mask.h */ + IAPRFTunerCommandID_RdsReadyNotify = 0x21, /* 1.00, from acc, rds-data.h */ + IAPRFTunerCommandID_GetHDProgramServiceCount = 0x25, /* 1.01, from dev, no paylaod */ + IAPRFTunerCommandID_RetHDProgramServiceCount = 0x26, /* 1.01, from acc, hd-program.h */ + IAPRFTunerCommandID_GetHDProgramService = 0x27, /* 1.01, from dev, no payload */ + IAPRFTunerCommandID_RetHDProgramService = 0x28, /* 1.01, from acc, hd-program.h */ + IAPRFTunerCommandID_SetHDProgramService = 0x29, /* 1.01, from dev, hd-program.h */ + IAPRFTunerCommandID_GetHDDataReadyStatus = 0x2A, /* 1.01, from dev, no payload */ + IAPRFTunerCommandID_RetHDDataReadyStatus = 0x2B, /* 1.01, from acc, hd-data.h */ + IAPRFTunerCommandID_GetHDData = 0x2C, /* 1.01, from dev, hd-data.h */ + IAPRFTunerCommandID_RetHDData = 0x2D, /* 1.01, from acc, hd-data.h */ + IAPRFTunerCommandID_GetHDDataNotifyMask = 0x2E, /* 1.01, from dev, no payload */ + IAPRFTunerCommandID_RetHDDataNotifyMask = 0x2F, /* 1.01, from acc, hd-data.h */ + IAPRFTunerCommandID_SetHDDataNotifyMask = 0x30, /* 1.01, from dev, hd-data.h */ + IAPRFTunerCommandID_HDDataReadyNotify = 0x31, /* 1.01, from acc, hd-data.h */ +}; + +#include "rf-tuner/acc-ack.h" +#include "rf-tuner/hd-data.h" +#include "rf-tuner/hd-program.h" +#include "rf-tuner/rds-data.h" +#include "rf-tuner/rds-notify-mask.h" +#include "rf-tuner/rds-ready-status.h" +#include "rf-tuner/status-notification.h" +#include "rf-tuner/tuner-band.h" +#include "rf-tuner/tuner-caps.h" +#include "rf-tuner/tuner-ctrl.h" +#include "rf-tuner/tuner-freq.h" +#include "rf-tuner/tuner-mode.h" +#include "rf-tuner/tuner-seek-rssi.h" +#include "rf-tuner/tuner-seek.h" +#include "rf-tuner/tuner-status.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/acc-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/acc-ack.h new file mode 100644 index 0000000000..e16d3eab7d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/acc-ack.h @@ -0,0 +1,13 @@ +#pragma once +#include + +struct IAPRFTunerAccAckPayload { + uint8_t status; /* = IAPAckStatus */ + uint8_t id; +} __attribute__((packed)); + +struct IAPRFTunerAccAckPendingPayload { + uint8_t status; /* = IAPAckStatus_CommandPending */ + uint8_t id; + uint32_t pending_time_ms; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-data.h new file mode 100644 index 0000000000..0e95dc46b2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-data.h @@ -0,0 +1,42 @@ +#pragma once +#include + +/* [1] P.319 4.7.45 Command 0x2B: RetHDDataReadyStatus */ + +enum IAPHDDataType { + IAPHDDataType_PSD = 0x00, + IAPHDDataType_SISStationIDNumber = 0x02, + IAPHDDataType_SISStationNameShort = 0x03, + IAPHDDataType_SISStationNameLong = 0x04, + IAPHDDataType_SISALFN = 0x05, + IAPHDDataType_SISStationLocation = 0x06, + IAPHDDataType_SISStationMessage = 0x07, + IAPHDDataType_SISSlogan = 0x08, + IAPHDDataType_SISParameterMessage = 0x09, +}; + +struct IAPRetHDDataReadyStatusPayload { + uint32_t status; /* 1 << IAPHDDataType | ... */ +} __attribute__((packed)); + +struct IAPGetHDDataPayload { + uint8_t type; /* IAPHDDataType */ +} __attribute__((packed)); + +struct IAPRetHDDataPayload { + uint8_t type; /* IAPHDDataType */ + uint8_t data[]; /* TODO: add difinitions */ +} __attribute__((packed)); + +struct IAPRetHDDataNotifyMaskPayload { + uint32_t status; /* 1 << IAPHDDataType | ... */ +} __attribute__((packed)); + +struct IAPSetHDDataNotifyMaskPayload { + uint32_t status; /* 1 << IAPHDDataType | ... */ +} __attribute__((packed)); + +struct IAPHDDataReadyNotifyPayload { + uint8_t type; /* IAPHDDataType */ + uint8_t data[]; /* TODO: add difinitions */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-program.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-program.h new file mode 100644 index 0000000000..a02d8ba5d3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/hd-program.h @@ -0,0 +1,17 @@ +#pragma once +#include + +/* [1] P.317 4.7.40 Command 0x26: RetHDProgramServiceCount */ + +struct IAPRetHDProgramServiceCountPayload { + uint8_t count; + uint8_t analog_program_exists; +} __attribute__((packed)); + +struct IAPRetHDProgramServicePayload { + uint8_t index; +} __attribute__((packed)); + +struct IAPSetHDProgramServicePayload { + uint8_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-data.h new file mode 100644 index 0000000000..eebb783ea7 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-data.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.311 4.7.33 Command 0x1C: GetRdsData */ + +struct IAPGetRdsDataPayload { + uint8_t type; /* IAPRdsData{Parsed,Raw}Type */ +}; + +struct IAPRetRdsDataPayload { + uint8_t type; /* IAPRdsData{Parsed,Raw}Type */ + uint8_t data[]; /* TODO: add definitions */ +}; + +struct IAPReadyNotifyPayload { + uint8_t type; /* IAPRdsData{Parsed,Raw}Type */ + uint8_t data[]; /* TODO: add definitions */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-notify-mask.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-notify-mask.h new file mode 100644 index 0000000000..d3d67434e4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-notify-mask.h @@ -0,0 +1,12 @@ +#pragma once +#include + +/* [1] P.314 4.7.36 Command 0x1F: RetRdsNotifyMask */ + +struct IAPRetRdsNotifyMaskPayload { + uint32_t mask; /* 1 << IAPRdsData{Parsed,Raw}Type | ... */ +} __attribute__((packed)); + +struct IAPSetRdsNotifyMaskPayload { + uint32_t mask; /* 1 << IAPRdsData{Parsed,Raw}Type | ... */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-ready-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-ready-status.h new file mode 100644 index 0000000000..713ebec3bf --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/rds-ready-status.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.310 4.7.32 Command 0x1B: RetRdsReadyStatus */ + +enum IAPRdsDataParsedType { + IAPRdsDataParsedType_RadioText = 0x04, + IAPRdsDataParsedType_ProgramServiceName = 0x1E, +}; + +enum IAPRdsDataRawType { + IAPRdsDataRawType_RdsGroupData = 0x05, +}; + +struct IAPRetRdsReadyStatusPayload { + uint32_t status; /* 1 << IAPRdsData{Parsed,Raw}Type | ... */ +} __attribute__((packed)); + diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/status-notification.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/status-notification.h new file mode 100644 index 0000000000..4d304fb27f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/status-notification.h @@ -0,0 +1,16 @@ +#pragma once +#include + +/* [1] P.307 4.7.28 Command 0x17: RetStatusNotifyMask */ + +struct IAPRetStatusNotifyMaskPayload { + uint8_t mask; /* IAPTunerStatusBits */ +} __attribute__((packed)); + +struct IAPSetStatusNotifyMaskPayload { + uint8_t mask; /* IAPTunerStatusBits */ +} __attribute__((packed)); + +struct IAPStatusChangeNotifyPayload { + uint8_t mask; /* IAPTunerStatusBits */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-band.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-band.h new file mode 100644 index 0000000000..5ec03a950d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-band.h @@ -0,0 +1,19 @@ +#pragma once +#include + +/* [1] P.297 4.7.12 Command 0x07: RetTunerBand */ + +enum IAPTunerBandStateBits { + AMBandWorldwide = 0x00, + FMBandEuropeUS = 0x01, + FMBandJapan = 0x02, + FNBandWide = 0x03, +}; + +struct IAPRetTunerBandPayload { + uint8_t state; /* IAPTunerBandStateBits */ +} __attribute__((packed)); + +struct IAPSetTunerBandPayload { + uint8_t state; /* IAPTunerBandStateBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-caps.h new file mode 100644 index 0000000000..0b233f89fd --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-caps.h @@ -0,0 +1,42 @@ +#pragma once +#include + +/* [1] P.292 4.7.7 Command 0x02: RetTunerCaps */ + +enum IAPFMTunerResolutionID { + _200kHz = 0b00, + _100kHz = 0b01, + _50kHz = 0b10, +}; + +enum IAPAMTunerResolutionID { + _10kHz = 0b0, + _9kHz = 0b1, +}; + +enum IAPTunerCapsBits { + IAPTunerCapsBits_AMWorldwide = 1 << 0, + IAPTunerCapsBits_FMEuropeUS = 1 << 1, + IAPTunerCapsBits_FMJapan = 1 << 2, + IAPTunerCapsBits_FMWide = 1 << 3, + IAPTunerCapsBits_HDRadio = 1 << 4, + IAPTunerCapsBits_PowerOnOff = 1 << 8, + IAPTunerCapsBits_StatusChangeNotification = 1 << 9, + IAPTunerCapsBits_MinFMResolution = 3 << 16, /* IAPFMTunerResolutionID */ + IAPTunerCapsBits_TunerSeekUpDown = 1 << 18, + IAPTunerCapsBits_TunerSeekRSSIThreshold = 1 << 19, + IAPTunerCapsBits_ForcemonophonicMode = 1 << 20, + IAPTunerCapsBits_StereoBlend = 1 << 21, + IAPTunerCapsBits_FMTunerDeemphasisSelect = 1 << 22, + IAPTunerCapsBits_AMTuner9kHzResoluton = 1 << 23, + IAPTunerCapsBits_RDSData = 1 << 24, + IAPTunerCapsBits_TunerChannelRSSIIndication = 1 << 25, + IAPTunerCapsBits_StereoSourceIndicator = 1 << 26, + IAPTunerCapsBits_RDSRawMode = 1 << 27, +}; + +struct IAPRetTunerCapsPayload { + uint32_t caps; /* IAPTunerCapsBits */ + uint8_t reserved1; /* = 0x01 */ + uint8_t reserved2; /* = 0x00 */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-ctrl.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-ctrl.h new file mode 100644 index 0000000000..c09fd2465d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-ctrl.h @@ -0,0 +1,18 @@ +#pragma once +#include + +/* [1] P.294 4.7.9 Command 0x04: RetTunerCtrl */ + +enum IAPTunerCtrlStateBits { + PowerDraw = 1 << 0, + StateChangeNotification = 1 << 1, + RDSRawMode = 1 << 3, +}; + +struct IAPRetTunerCtrlPayload { + uint8_t state; /* IAPTunerCtrlStateBits */ +} __attribute__((packed)); + +struct IAPSetTunerCtrlPayload { + uint8_t state; /* IAPTunerCtrlStateBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-freq.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-freq.h new file mode 100644 index 0000000000..ed70a8f0aa --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-freq.h @@ -0,0 +1,13 @@ +#pragma once +#include + +/* [1] P.298 4.7.15 Command 0x0A: RetTunerFreq */ + +struct IAPRetTunerFreqPayload { + uint32_t freq_khz; + uint8_t rssi_level; +} __attribute__((packed)); + +struct IAPSetTunerFreqPayload { + uint32_t freq_khz; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-mode.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-mode.h new file mode 100644 index 0000000000..9ed455875f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-mode.h @@ -0,0 +1,22 @@ +#pragma once +#include + +/* [1] P.300 4.7.18 Command 0x0D: RetTunerMode */ + +enum IAPTunerModeStatusBits { + FMTunerResolution = 3 << 0, /* IAPFMTunerResolutionID */ + TunerSeekingUp = 1 << 2, + TunerSeekingWithMinRSSIThreshold = 1 << 3, + ForceMonophonic = 1 << 4, + StereoBlend = 1 << 5, + FMTunerDeemphasis50usec = 1 << 6, + AMTunerResolution = 1 << 7, /* IAPAMTunerResolutionID */ +}; + +struct IAPRetTunerModePayload { + uint8_t status; /* IAPTunerModeStatusBits */ +} __attribute__((packed)); + +struct IAPSetTunerModePayload { + uint8_t status; /* IAPTunerModeStatusBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek-rssi.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek-rssi.h new file mode 100644 index 0000000000..e3cf8fca8e --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek-rssi.h @@ -0,0 +1,12 @@ +#pragma once +#include + +/* [1] P.302 4.7.21 Command 0x10: RetTunerSeekRssi */ + +struct IAPRetTunerSeekRssiPayload { + uint8_t rssi_threshold; +} __attribute__((packed)); + +struct IAPSetTunerSeekRssiPayload { + uint8_t rssi_threshold; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek.h new file mode 100644 index 0000000000..ff56682887 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-seek.h @@ -0,0 +1,33 @@ +#pragma once +#include + +/* [1] P.302 4.7.23 Command 0x12: TunerSeekStart */ + +enum IAPTunerSeekOperationID { + IAPTunerSeekOperationID_NoSeek = 0x00, + IAPTunerSeekOperationID_SeekUpFromBeginning = 0x01, + IAPTunerSeekOperationID_SeekDownFromEnd = 0x02, + IAPTunerSeekOperationID_SeekUpFromCurrent = 0x03, + IAPTunerSeekOperationID_SeekDownFromCurrent = 0x04, + IAPTunerSeekOperationID_SeekUpFromBeginningWithRssiThreshold = 0x05, + IAPTunerSeekOperationID_SeekDownFromEndWithRssiThreshold = 0x06, + IAPTunerSeekOperationID_SeekUpFromCurrentWithRssiThreshold = 0x07, + IAPTunerSeekOperationID_SeekDownFromCurrentWithRssiThreshold = 0x08, + IAPTunerSeekOperationID_SeekUpFromBeginningForHDSignal = 0x09, + IAPTunerSeekOperationID_SeekDownFromEndForHDSignal = 0x0A, + IAPTunerSeekOperationID_SeekUpFromCurrentForHDSignal = 0x0B, + IAPTunerSeekOperationID_SeekDownFromCurrentForHDSignal = 0x0C, + IAPTunerSeekOperationID_SeekUpFromBeginningForHDSignalWithRssiThreshold = 0x0D, + IAPTunerSeekOperationID_SeekDownFromEndForHDSignalWithRssiThreshold = 0x0E, + IAPTunerSeekOperationID_SeekUpFromCurrentForHDSignalWithRssiThreshold = 0x0F, + IAPTunerSeekOperationID_SeekDownFromCurrentForHDSignalWithRssiThreshold = 0x10, +}; + +struct IAPTunerSeekStartPayload { + uint8_t operation_id; /* IAPTunerSeekOperationID */ +} __attribute__((packed)); + +struct IAPTunerSeekDonePayload { + uint32_t freq_khz; + uint8_t signal_strength; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-status.h new file mode 100644 index 0000000000..70ba8b01e4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/rf-tuner/tuner-status.h @@ -0,0 +1,17 @@ +#pragma once +#include + +/* [1] P.306 4.7.26 Command 0x15: RetTunerStatus */ + +enum IAPTunerStatusBits { + IAPTunerStatusBits_DataReceived = 1 << 0, + IAPTunerStatusBits_RSSILevelChanged = 1 << 1, + IAPTunerStatusBits_StereoSourceIndicator = 1 << 2, + IAPTunerStatusBits_HDSignalPresent = 1 << 3, + IAPTunerStatusBits_HDDigitalAudioPresent = 1 << 4, + IAPTunerStatusBits_HDDataReceived = 1 << 5, +}; + +struct IAPRetTunerStatusPayload { + uint8_t status; /* IAPTunerStatusBits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote.h new file mode 100644 index 0000000000..e96a25bde3 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote.h @@ -0,0 +1,39 @@ +#pragma once +#include + +/* [1] P.213 Table 4-3 Simple remote lingo command summary */ +enum IAPSimpleRemoteCommandID { + IAPSimpleRemoteCommandID_ContextButtonStatus = 0x00, /* from acc, button-status.h */ + IAPSimpleRemoteCommandID_IPodAck = 0x01, /* from dev, general/ipod-ack.h */ + IAPSimpleRemoteCommandID_ImageButtonStatus = 0x02, /* from acc, button-status.h, deprecated */ + IAPSimpleRemoteCommandID_VideoButtonStatus = 0x03, /* from acc, button-status.h */ + IAPSimpleRemoteCommandID_AudioButtonStatus = 0x04, /* from acc, button-status.h */ + IAPSimpleRemoteCommandID_IPodOutButtonStatus = 0x0B, /* from acc, ipod-out-button-status.h */ + IAPSimpleRemoteCommandID_RotationInputStatus = 0x0C, /* from acc, rotation-input-status.h */ + IAPSimpleRemoteCommandID_RadioButtonStatus = 0x0D, /* from acc, radio-button-status.h */ + IAPSimpleRemoteCommandID_CameraButtonStatus = 0x0E, /* from acc, camera-button-status.h */ + IAPSimpleRemoteCommandID_RegisterDescriptor = 0x0F, /* from acc, hid.h */ + IAPSimpleRemoteCommandID_IPodHIDReport = 0x10, /* from acc, hid.h */ + IAPSimpleRemoteCommandID_AccessoryHIDReport = 0x11, /* from dev, hid.h */ + IAPSimpleRemoteCommandID_UnregisterDescriptor = 0x12, /* from acc, hid.h */ + IAPSimpleRemoteCommandID_VoiceOverEvent = 0x13, /* from acc, voice-over-event.h */ + IAPSimpleRemoteCommandID_GetVoiceOverParameter = 0x14, /* from acc, voice-over-parameter.h */ + IAPSimpleRemoteCommandID_RetVoiceOverParameter = 0x15, /* from dev, voice-over-parameter.h */ + IAPSimpleRemoteCommandID_SetVoiceOverParameter = 0x16, /* from acc, voice-over-parameter.h */ + IAPSimpleRemoteCommandID_GetCurrentVoiceOverItemProperty = 0x17, /* from acc, current-voice-over-item-property.h */ + IAPSimpleRemoteCommandID_RetCurrentVoiceOverItemProperty = 0x18, /* from dev, current-voice-over-item-property.h */ + IAPSimpleRemoteCommandID_SetVoiceOverContext = 0x19, /* from acc, set-voice-over-context.h */ + IAPSimpleRemoteCommandID_VoiceOverParameterChanged = 0x2A, /* from dev, voice-over-parameter.h */ + IAPSimpleRemoteCommandID_AccessoryAck = 0x81, /* from acc, general/acc-ack.h */ +}; + +#include "simple-remote/button-status.h" +#include "simple-remote/camera-button-status.h" +#include "simple-remote/current-voice-over-item-property.h" +#include "simple-remote/hid.h" +#include "simple-remote/ipod-out-button-status.h" +#include "simple-remote/radio-button-status.h" +#include "simple-remote/rotation-input-status.h" +#include "simple-remote/set-voice-over-context.h" +#include "simple-remote/voice-over-event.h" +#include "simple-remote/voice-over-parameter.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/button-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/button-status.h new file mode 100644 index 0000000000..779c2f540b --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/button-status.h @@ -0,0 +1,98 @@ +#pragma once +#include + +enum IAPContextButtonBits { + /* bits1 */ + IAPContextButtonStatusButtons_PlayPause = 1 << 0, + IAPContextButtonStatusButtons_VolumeUp = 1 << 1, + IAPContextButtonStatusButtons_VolumeDown = 1 << 2, + IAPContextButtonStatusButtons_NextTrack = 1 << 3, + IAPContextButtonStatusButtons_PrevTrack = 1 << 4, + IAPContextButtonStatusButtons_NextAlbum = 1 << 5, + IAPContextButtonStatusButtons_PrevAlbum = 1 << 6, + IAPContextButtonStatusButtons_Stop = 1 << 7, + /* bits2 */ + IAPContextButtonStatusButtons_PlayResume = 1 << 0, + IAPContextButtonStatusButtons_Pause = 1 << 1, + IAPContextButtonStatusButtons_MuteToggle = 1 << 2, + IAPContextButtonStatusButtons_NextChapter = 1 << 3, + IAPContextButtonStatusButtons_PrevChapter = 1 << 4, + IAPContextButtonStatusButtons_NextPlaylist = 1 << 5, + IAPContextButtonStatusButtons_PrevPlaylist = 1 << 6, + IAPContextButtonStatusButtons_ShuffleSettingAdvance = 1 << 7, + /* bits3 */ + IAPContextButtonStatusButtons_RepeatSettingAdvance = 1 << 0, + IAPContextButtonStatusButtons_PowerOn = 1 << 1, + IAPContextButtonStatusButtons_PowerOff = 1 << 2, + IAPContextButtonStatusButtons_Backlight30Seconds = 1 << 3, + IAPContextButtonStatusButtons_BeginFastForward = 1 << 4, + IAPContextButtonStatusButtons_BeginRewind = 1 << 5, + IAPContextButtonStatusButtons_Menu = 1 << 6, + IAPContextButtonStatusButtons_Select = 1 << 7, + /* bits4 */ + IAPContextButtonStatusButtons_UpArrow = 1 << 0, + IAPContextButtonStatusButtons_DownArrow = 1 << 1, + IAPContextButtonStatusButtons_BacklightOff = 1 << 2, +}; + +enum IAPImageButtonBits { + /* bits1 */ + IAPImageButtonBits_PlayPause = 1 << 0, + IAPImageButtonBits_NextImage = 1 << 1, + IAPImageButtonBits_PrevImage = 1 << 2, + IAPImageButtonBits_Stop = 1 << 3, + IAPImageButtonBits_PlayResume = 1 << 4, + IAPImageButtonBits_Pause = 1 << 5, + IAPImageButtonBits_ShuffleAdvance = 1 << 6, + IAPImageButtonBits_RepeatAdvance = 1 << 7, +}; + +enum IAPVideoButtonBits { + /* bits1 */ + IAPVideoButtonBits_PlayPause = 1 << 0, + IAPVideoButtonBits_NextVideo = 1 << 1, + IAPVideoButtonBits_PrevVideo = 1 << 2, + IAPVideoButtonBits_Stop = 1 << 3, + IAPVideoButtonBits_PlayResume = 1 << 4, + IAPVideoButtonBits_Pause = 1 << 5, + IAPVideoButtonBits_BeginFF = 1 << 6, + IAPVideoButtonBits_BeginREW = 1 << 7, + /* bits1 */ + IAPVideoButtonBitsNextChapter = 1 << 0, + IAPVideoButtonBitsPrevChapter = 1 << 1, + IAPVideoButtonBitsNextFrame = 1 << 2, + IAPVideoButtonBitsPrevFrame = 1 << 3, +}; + +enum IAPAudioButtonBits { + /* bits1 */ + IAPAudioButtonBits_PlayPause = 1 << 0, + IAPAudioButtonBits_VolumeUp = 1 << 1, + IAPAudioButtonBits_VolumeDown = 1 << 2, + IAPAudioButtonBits_NextTrack = 1 << 3, + IAPAudioButtonBits_PrevTrack = 1 << 4, + IAPAudioButtonBits_NextAlbum = 1 << 5, + IAPAudioButtonBits_PrevAlbum = 1 << 6, + IAPAudioButtonBits_Stop = 1 << 7, + /* bits2 */ + IAPAudioButtonBits_PlayResume = 1 << 0, + IAPAudioButtonBits_Pause = 1 << 1, + IAPAudioButtonBits_MuteToggle = 1 << 2, + IAPAudioButtonBits_NextChapter = 1 << 3, + IAPAudioButtonBits_PrevChapter = 1 << 4, + IAPAudioButtonBits_NextPlaylist = 1 << 5, + IAPAudioButtonBits_PrevPlaylist = 1 << 6, + IAPAudioButtonBits_ShuffleSettingAdvance = 1 << 7, + /* bits3 */ + IAPAudioButtonBits_RepeatSettingAdvance = 1 << 0, + IAPAudioButtonBits_BeginFF = 1 << 1, + IAPAudioButtonBits_BeginREW = 1 << 2, + IAPAudioButtonBits_Record = 1 << 3, +}; + +struct IAPButtonStatusPayload { + uint8_t bits1; /* IAP{Context,Image,Video,Audio}ButtonBits(bits1) */ + uint8_t bits2; /* IAP{Context,Image,Video,Audio}ButtonBits(bits2), optional */ + uint8_t bits3; /* IAP{Context,Image,Video,Audio}ButtonBits(bits3), optional */ + uint8_t bits4; /* IAP{Context,Image,Video,Audio}ButtonBits(bits4), optional */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/camera-button-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/camera-button-status.h new file mode 100644 index 0000000000..7e5fd17b8b --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/camera-button-status.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum IAPCameraButtonStatus { + IAPCameraButtonStatus_Up = 0x00, + IAPCameraButtonStatus_Down = 0x01, +}; + +struct IAPCameraButtonStatusPayload { + uint8_t status; /* IAPCameraButtonStatus */ +}; diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/current-voice-over-item-property.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/current-voice-over-item-property.h new file mode 100644 index 0000000000..fbbf85fe79 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/current-voice-over-item-property.h @@ -0,0 +1,72 @@ +#pragma once +#include + +enum IAPCurrentVoiceOverItemPropertyType { + IAPCurrentVoiceOverItemPropertyType_Label, + IAPCurrentVoiceOverItemPropertyType_Value, + IAPCurrentVoiceOverItemPropertyType_Hint, + IAPCurrentVoiceOverItemPropertyType_Frame, + IAPCurrentVoiceOverItemPropertyType_Traits, +}; + +struct IAPGetCurrentVoiceOverItemPropertyPayload { + uint8_t type; /* IAPCurrentVoiceOverItemPropertyType */ +} __attribute__((packed)); + +struct IAPRetCurrentVoiceOverItemPropertyPayload { + uint8_t type; /* IAPCurrentVoiceOverItemPropertyType */ + uint8_t value[]; +} __attribute__((packed)); + +struct IAPRetCurrentVoiceOverItemPropertyLabelPayload { + uint8_t type; /* = IAPCurrentVoiceOverItemPropertyType_Label */ + uint16_t current_section_index; + uint16_t last_section_index; + char text[]; +} __attribute__((packed)); + +struct IAPRetCurrentVoiceOverItemPropertyVolumePayload { + uint8_t type; /* = IAPCurrentVoiceOverItemPropertyType_Volume */ + uint16_t current_section_index; + uint16_t last_section_index; + char text[]; +} __attribute__((packed)); + +struct IAPRetCurrentVoiceOverItemPropertyHintPayload { + uint8_t type; /* = IAPCurrentVoiceOverItemPropertyType_Hint */ + uint16_t current_section_index; + uint16_t last_section_index; + char text[]; +} __attribute__((packed)); + +struct IAPRetCurrentVoiceOverItemPropertyFramePayload { + uint8_t type; /* = IAPCurrentVoiceOverItemPropertyType_Frame */ + uint16_t top_x; + uint16_t top_y; + uint16_t bottom_x; + uint16_t bottom_y; +} __attribute__((packed)); + +enum IAPRetCurrentVoiceOverItemPropertyTraits { + IAPRetCurrentVoiceOverItemPropertyTraits_Button = 0x0000, + IAPRetCurrentVoiceOverItemPropertyTraits_Link = 0x0001, + IAPRetCurrentVoiceOverItemPropertyTraits_SearchField = 0x0002, + IAPRetCurrentVoiceOverItemPropertyTraits_Image = 0x0003, + IAPRetCurrentVoiceOverItemPropertyTraits_Selected = 0x0004, + IAPRetCurrentVoiceOverItemPropertyTraits_Sound = 0x0005, + IAPRetCurrentVoiceOverItemPropertyTraits_KeyboardKey = 0x0006, + IAPRetCurrentVoiceOverItemPropertyTraits_StaticText = 0x0007, + IAPRetCurrentVoiceOverItemPropertyTraits_SummaryElement = 0x0008, + IAPRetCurrentVoiceOverItemPropertyTraits_NotEnabled = 0x0009, + IAPRetCurrentVoiceOverItemPropertyTraits_UpdatesFrequently = 0x000A, + IAPRetCurrentVoiceOverItemPropertyTraits_StartsMediaSession = 0x000B, + IAPRetCurrentVoiceOverItemPropertyTraits_Adjustable = 0x000C, + IAPRetCurrentVoiceOverItemPropertyTraits_BackButton = 0x000D, + IAPRetCurrentVoiceOverItemPropertyTraits_Map = 0x000E, + IAPRetCurrentVoiceOverItemPropertyTraits_DeleteKey = 0x000F, +}; + +struct IAPRetCurrentVoiceOverItemPropertyTraitsPayload { + uint8_t type; /* = IAPCurrentVoiceOverItemPropertyType_Traits */ + uint16_t traits[]; /* IAPRetCurrentVoiceOverItemPropertyTraits */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/hid.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/hid.h new file mode 100644 index 0000000000..70b42be1fd --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/hid.h @@ -0,0 +1,75 @@ +#pragma once +#include + +enum IAPRegisterDescriptorCountryCodes { + IAPRegisterDescriptorCountryCodes_Undefined = 0x00, + IAPRegisterDescriptorCountryCodes_Arabic = 0x01, + IAPRegisterDescriptorCountryCodes_Belgian = 0x02, + IAPRegisterDescriptorCountryCodes_CanadianBilingual = 0x03, + IAPRegisterDescriptorCountryCodes_CanadianFrench = 0x04, + IAPRegisterDescriptorCountryCodes_CzechRepublic = 0x05, + IAPRegisterDescriptorCountryCodes_Danish = 0x06, + IAPRegisterDescriptorCountryCodes_Finnish = 0x07, + IAPRegisterDescriptorCountryCodes_French = 0x08, + IAPRegisterDescriptorCountryCodes_German = 0x09, + IAPRegisterDescriptorCountryCodes_Greek = 0x0A, + IAPRegisterDescriptorCountryCodes_Hebrew = 0x0B, + IAPRegisterDescriptorCountryCodes_Hungarian = 0x0C, + IAPRegisterDescriptorCountryCodes_International = 0x0D, + IAPRegisterDescriptorCountryCodes_Italian = 0x0E, + IAPRegisterDescriptorCountryCodes_Japan = 0x0F, + IAPRegisterDescriptorCountryCodes_Korean = 0x10, + IAPRegisterDescriptorCountryCodes_LatinAmerican = 0x11, + IAPRegisterDescriptorCountryCodes_Netherlands = 0x12, + IAPRegisterDescriptorCountryCodes_Norwegian = 0x13, + IAPRegisterDescriptorCountryCodes_Persian = 0x14, + IAPRegisterDescriptorCountryCodes_Poland = 0x15, + IAPRegisterDescriptorCountryCodes_Portuguese = 0x16, + IAPRegisterDescriptorCountryCodes_Russian = 0x17, + IAPRegisterDescriptorCountryCodes_Slovakia = 0x18, + IAPRegisterDescriptorCountryCodes_Spanish = 0x19, + IAPRegisterDescriptorCountryCodes_Swedish = 0x1A, + IAPRegisterDescriptorCountryCodes_SwissFrench = 0x1B, + IAPRegisterDescriptorCountryCodes_SwissGerman = 0x1C, + IAPRegisterDescriptorCountryCodes_Switzerland = 0x1D, + IAPRegisterDescriptorCountryCodes_Taiwan = 0x1E, + IAPRegisterDescriptorCountryCodes_TurkishQ = 0x1F, + IAPRegisterDescriptorCountryCodes_UK = 0x20, + IAPRegisterDescriptorCountryCodes_US = 0x21, + IAPRegisterDescriptorCountryCodes_Croatian = 0x22, + IAPRegisterDescriptorCountryCodes_TurkishF = 0x23, + IAPRegisterDescriptorCountryCodes_Thai = 0xFA, + IAPRegisterDescriptorCountryCodes_Flemish = 0xFB, + IAPRegisterDescriptorCountryCodes_Romanian = 0xFC, + IAPRegisterDescriptorCountryCodes_Bulgarian = 0xFD, + IAPRegisterDescriptorCountryCodes_Chinese = 0xFE, +}; + +struct IAPRegisterDescriptorPayload { + uint8_t index; + uint16_t vid; + uint16_t pid; + uint8_t country_code; /* IAPRegisterDescriptorCountryCodes */ + uint8_t descriptor[]; +} __attribute__((packed)); + +enum IAPHIDReportReportType { + IAPHIDReportReportType_Input = 0x00, + IAPHIDReportReportType_Output = 0x01, +}; + +struct IAPIPodHIDReportPayload { + uint8_t index; + uint8_t report_type; /* = IAPHIDReportReportType_Input */ + uint8_t report[]; +} __attribute__((packed)); + +struct IAPAccessoryHIDReportPayload { + uint8_t index; + uint8_t report_type; /* = IAPHIDReportReportType_Output */ + uint8_t report[]; +} __attribute__((packed)); + +struct IAPUnregisterDescriptorPayload { + uint8_t index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/ipod-out-button-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/ipod-out-button-status.h new file mode 100644 index 0000000000..a81839a9c4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/ipod-out-button-status.h @@ -0,0 +1,26 @@ +#pragma once +#include + +enum IAPIPodOutButtonStatusSource { + IAPIPodOutButtonStatusSource_CarCenterConsole = 0x00, + IAPIPodOutButtonStatusSource_SteeringWheel = 0x01, + IAPIPodOutButtonStatusSource_CarDashboard = 0x02, +}; + +enum IAPIPodOutButtonBits { + /* bits1 */ + IAPIPodOutButtonBits_Select = 1 << 0, + IAPIPodOutButtonBits_Left = 1 << 1, + IAPIPodOutButtonBits_Rigth = 1 << 2, + IAPIPodOutButtonBits_Up = 1 << 3, + IAPIPodOutButtonBits_Down = 1 << 4, + IAPIPodOutButtonBits_Menu = 1 << 5, +}; + +struct IAPIPodOutButtonStatusPayload { + uint8_t source; /* IAPIPodOutButtonStatusSource */ + uint8_t bits1; /* IAPIPodOutButtonBits(bits1) */ + uint8_t bits2; /* IAPIPodOutButtonBits(bits2), optional */ + uint8_t bits3; /* IAPIPodOutButtonBits(bits3), optional */ + uint8_t bits4; /* IAPIPodOutButtonBits(bits4), optional */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/radio-button-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/radio-button-status.h new file mode 100644 index 0000000000..71b4a7abc5 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/radio-button-status.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum IAPRadioButtonStatus { + IAPRadioButtonStatus_Released = 0x00, + IAPRadioButtonStatus_Pushed = 0x02, +}; + +struct IAPRadioButtonStatusPayload { + uint8_t status; /* IAPRadioButtonStatus */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/rotation-input-status.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/rotation-input-status.h new file mode 100644 index 0000000000..05ca73726d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/rotation-input-status.h @@ -0,0 +1,33 @@ +#pragma once +#include + +enum IAPRotationInputStatusControllerType { + IAPRotationInputStatusControllerType_FreeWheel = 0x00, +}; + +enum IAPRotationInputStatusRotationDirection { + IAPRotationInputStatusRotationDirection_Left = 0x00, /* CCW */ + IAPRotationInputStatusRotationDirection_Right = 0x01, /* CW */ +}; + +enum IAPRotationInputStatusRotationAction { + IAPRotationInputStatusRotationAction_Completed = 0x00, + IAPRotationInputStatusRotationAction_InProgress = 0x01, + IAPRotationInputStatusRotationAction_Repeat = 0x02, +}; + +enum IAPRotationInputStatusRotationType { + IAPRotationInputStatusRotationType_Detent = 0x00, + IAPRotationInputStatusRotationType_Degree = 0x01, +}; + +struct IAPRotationInputStatusPayload { + uint32_t user_action_duration_ms; + uint8_t source; /* IAPIPodOutButtonStatusSource */ + uint8_t controller_type; /* IAPRotationInputStatusControllerType */ + uint8_t rotation_direction; /* IAPRotationInputStatusRotationDirection */ + uint8_t rotation_action; /* IAPRotationInputStatusRotationAction */ + uint8_t rotation_type; /* IAPRotationInputStatusRotationType */ + uint16_t detents_or_degrees_moved; + uint16_t max_detents_or_degrees_moved; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/set-voice-over-context.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/set-voice-over-context.h new file mode 100644 index 0000000000..05945067f0 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/set-voice-over-context.h @@ -0,0 +1,17 @@ +#pragma once +#include + +enum IAPSetVoiceOverContextType { + IAPSetVoiceOverContextType_None = 0x00, + IAPSetVoiceOverContextType_Header = 0x01, + IAPSetVoiceOverContextType_Link = 0x02, + IAPSetVoiceOverContextType_Form = 0x03, + IAPSetVoiceOverContextType_Cursor = 0x04, + IAPSetVoiceOverContextType_VerticalNavigation = 0x05, + IAPSetVoiceOverContextType_ValueAdjustment = 0x06, + IAPSetVoiceOverContextType_ZoomAdjustment = 0x07, +}; + +struct IAPSetVoiceOverContextPayload { + uint8_t type; /* IAPSetVoiceOverContextType */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-event.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-event.h new file mode 100644 index 0000000000..f09290cee4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-event.h @@ -0,0 +1,85 @@ +#pragma once +#include + +enum IAPVoiceOverEventType { + IAPVoiceOverEventType_MovePointer = 0x00, + IAPVoiceOverEventType_MoveToFirst = 0x01, + IAPVoiceOverEventType_MoveToLast = 0x02, + IAPVoiceOverEventType_MoveToNext = 0x03, + IAPVoiceOverEventType_MoveToPrev = 0x04, + IAPVoiceOverEventType_ScrollLeftPage = 0x05, + IAPVoiceOverEventType_ScrollRightPage = 0x06, + IAPVoiceOverEventType_ScrollUpPage = 0x07, + IAPVoiceOverEventType_ScrollDownPage = 0x08, + IAPVoiceOverEventType_ScrollToPoint = 0x09, + IAPVoiceOverEventType_SendTextToInput = 0x0A, + IAPVoiceOverEventType_Cut = 0x0B, + IAPVoiceOverEventType_Copy = 0x0C, + IAPVoiceOverEventType_Paste = 0x0D, + IAPVoiceOverEventType_Home = 0x0E, + IAPVoiceOverEventType_Touch = 0x0F, + IAPVoiceOverEventType_DisplayScale = 0x10, + IAPVoiceOverEventType_CenterDisplay = 0x11, + IAPVoiceOverEventType_PauseSpeak = 0x12, + IAPVoiceOverEventType_ResumeSpeak = 0x13, + IAPVoiceOverEventType_ReadTextFromCurrent = 0x14, + IAPVoiceOverEventType_ReadTextFromTop = 0x15, + IAPVoiceOverEventType_SendTextToSpeech = 0x16, + IAPVoiceOverEventType_Escape = 0x17, +}; + +struct IAPVoiceOverEventPayload { + uint8_t type; /* IAPVoiceOverEventType */ + uint8_t data[]; +} __attribute__((packed)); + +struct IAPVoiceOverEventMovePointerPayload { + uint8_t type; /* = IAPVoiceOverEventType_MovePointer */ + uint16_t x; + uint16_t y; +} __attribute__((packed)); + +struct IAPVoiceOverEventScrollToPointPayload { + uint8_t type; /* = IAPVoiceOverEventType_ScrollToPoint */ + uint16_t x; + uint16_t y; +} __attribute__((packed)); + +struct IAPVoiceOverEventSendTextToInputPayload { + uint8_t type; /* = IAPVoiceOverEventType_SendTextToInput */ + uint16_t current_section_index; + uint16_t last_section_index; + char text[]; +} __attribute__((packed)); + +enum IAPVoiceOverEventTouchEventType { + IAPVoiceOverEventTouchEventType_Begin = 0x00, + IAPVoiceOverEventTouchEventType_Moved = 0x01, + IAPVoiceOverEventTouchEventType_Stationary = 0x02, + IAPVoiceOverEventTouchEventType_Ended = 0x03, + IAPVoiceOverEventTouchEventType_Canceled = 0x04, +}; + +struct IAPVoiceOverEventTouchPayload { + uint8_t type; /* = IAPVoiceOverEventType_Touch */ + uint8_t reserved[4]; + uint8_t touch_event_type; /* IAPVoiceOverEventTouchEventType */ +} __attribute__((packed)); + +struct IAPVoiceOverEventDisplayScalePayload { + uint8_t type; /* = IAPVoiceOverEventType_DisplayScale */ + uint16_t scale_factor; +} __attribute__((packed)); + +struct IAPVoiceOverEventCenterDisplayPayload { + uint8_t type; /* = IAPVoiceOverEventType_CenterDisplay */ + uint16_t x; + uint16_t y; +} __attribute__((packed)); + +struct IAPVoiceOverEventSendTextToSpeechPayload { + uint8_t type; /* = IAPVoiceOverEventType_SendTextToSpeech */ + uint16_t current_section_index; + uint16_t last_section_index; + char text[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-parameter.h b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-parameter.h new file mode 100644 index 0000000000..0d409294b8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/simple-remote/voice-over-parameter.h @@ -0,0 +1,28 @@ +#pragma once +#include + +enum IAPGetVoiceOverParameterType { + IAPGetVoiceOverParameterType_VoiceOverVolume = 0x00, + IAPGetVoiceOverParameterType_SpeakingRate = 0x01, + IAPGetVoiceOverParameterType_VoiceOverEnabled = 0x02, +}; + +struct IAPGetVoiceOverParameterPayload { + uint8_t type; /* IAPGetVoiceOverParameterType */ +} __attribute__((packed)); + +struct IAPRetVoiceOverParameterPayload { + uint8_t type; /* IAPGetVoiceOverParameterType */ + uint8_t value; +} __attribute__((packed)); + +struct IAPSetVoiceOverParameterPayload { + uint8_t type; /* IAPGetVoiceOverParameterType */ + uint8_t value; +} __attribute__((packed)); + + +struct IAPVoiceOverParameterChangedPayload { + uint8_t type; /* IAPGetVoiceOverParameterType */ + uint8_t value; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sound-check-state.h b/firmware/usbstack/iap/libiap/spec/lingoes/sound-check-state.h new file mode 100644 index 0000000000..a33e179f3a --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sound-check-state.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPRetSoundCheckStatePayload { + uint8_t sound_check_status; +} __attribute__((packed)); + +struct IAPSetSoundCheckStatePayload { + uint8_t sound_check_status; + uint8_t restore_on_exit; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports.h new file mode 100644 index 0000000000..7969516dc8 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports.h @@ -0,0 +1,25 @@ +#pragma once +#include + +/* [1] P.336 Table 4-212 Sports lingo command summary */ +enum IAPSportsCommandID { + IAPSportsCommandID_AccessoryAck = 0x00, /* from acc, general/acc-ack.h */ + IAPSportsCommandID_GetAccessoryVersion = 0x01, /* from dev, no payload */ + IAPSportsCommandID_RetAccessoryVersion = 0x02, /* from acc, accessory-version.h */ + IAPSportsCommandID_GetAccessoryCaps = 0x03, /* from dev, no payload */ + IAPSportsCommandID_RetAccessoryCaps = 0x04, /* from acc, accessory-caps.h */ + IAPSportsCommandID_IPodAck = 0x80, /* from dev, general/ipod-ack.h */ + IAPSportsCommandID_GetIPodCaps = 0x83, /* from acc, no payload */ + IAPSportsCommandID_RetIPodCaps = 0x84, /* from dev, ipod-caps.h */ + IAPSportsCommandID_GetUserIndex = 0x85, /* from acc, no payload */ + IAPSportsCommandID_RetUserIndex = 0x86, /* from dev, user-index.h */ + IAPSportsCommandID_GetUserData = 0x88, /* from acc, user-data.h */ + IAPSportsCommandID_RetUserData = 0x89, /* from dev, user-data.h */ + IAPSportsCommandID_SetUserData = 0x8A, /* from acc, user-data.h */ +}; + +#include "sports/accessory-caps.h" +#include "sports/accessory-version.h" +#include "sports/ipod-caps.h" +#include "sports/user-data.h" +#include "sports/user-index.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-caps.h new file mode 100644 index 0000000000..ddbdb779b4 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-caps.h @@ -0,0 +1,11 @@ +#pragma once +#include + +enum IAPSportsAccessoryCapBits { + IAPAccessoryCapBits_LaterCommands = 1 << 9, +}; + +struct IAPRetAccessoryCapsPayload { + uint16_t mask; /* IAPSportsAccessoryCapBits */ + uint8_t reserved; /* = 0x00 */ +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-version.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-version.h new file mode 100644 index 0000000000..6a6be98427 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports/accessory-version.h @@ -0,0 +1,7 @@ +#pragma once +#include + +struct IAPRetAccessoryVersionPayload { + uint8_t major; + uint8_t minor; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports/ipod-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports/ipod-caps.h new file mode 100644 index 0000000000..a203552a0f --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports/ipod-caps.h @@ -0,0 +1,12 @@ +#pragma once +#include + +enum IAPSportsIPodCapBits { + Cardio = 1 << 0, + UserData = 1 << 1, +}; + +struct IAPRetIPodCapsPayload { + uint16_t caps; /* IAPSportsIPodCapBits */ + uint8_t user_count; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-data.h new file mode 100644 index 0000000000..5044e64efa --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-data.h @@ -0,0 +1,73 @@ +#pragma once +#include + +/* [1] P.341 4.9.13 Command 0x88: GetUserData */ + +enum IAPSportsUserDataType { + IAPSportsUserDataType_UnitSystem = 0x00, + IAPSportsUserDataType_Name = 0x01, + IAPSportsUserDataType_Gender = 0x02, + IAPSportsUserDataType_Weight = 0x03, + IAPSportsUserDataType_Age = 0x04, + IAPSportsUserDataType_WorkoutRecording = 0x05, +}; + +struct IAPGetUserDataPayload { + uint8_t type; /* IAPSportsUserDataType */ +} __attribute__((packed)); + +struct IAPRetUserDataPayload { + uint8_t type; /* IAPSportsUserDataType */ + uint8_t data[]; +} __attribute__((packed)); + +enum IAPSportsUnitSystem { + IAPSportsUnitSystem_None = 0x00, + IAPSportsUnitSystem_Imperial = 0x01, + IAPSportsUnitSystem_Metric = 0x02, +}; + +struct IAPRetUserDataUnitSystemPayload { + uint8_t type; /* = IAPSportsUserDataType_UnitSystem */ + uint8_t preferred_unit_system; /* IAPSportsUnitSystem */ +} __attribute__((packed)); + +struct IAPRetUserDataNamePayload { + uint8_t type; /* = IAPSportsUserDataType_Name */ + char name[]; +} __attribute__((packed)); + +enum IAPSportsGender { + IAPSportsGender_None = 0x00, + IAPSportsGender_Female = 0x01, + IAPSportsGender_Male = 0x02, +}; + +struct IAPRetUserDataGenderPayload { + uint8_t type; /* = IAPSportsUserDataType_Gender */ + uint8_t gender; /* IAPSportsGender */ +} __attribute__((packed)); + +struct IAPRetUserDataWeightPayload { + uint8_t type; /* = IAPSportsUserDataType_Weight */ + uint16_t weight; +} __attribute__((packed)); + +struct IAPRetUserDataAgePayload { + uint8_t type; /* = IAPSportsUserDataType_Age */ + uint8_t age; +} __attribute__((packed)); + +enum IAPSportsWorkoutRecordingPreferences { + IAPSportsWorkoutRecordingPreferences_None = 0x00, + IAPSportsWorkoutRecordingPreferences_Never = 0x01, + IAPSportsWorkoutRecordingPreferences_Ask = 0x02, + IAPSportsWorkoutRecordingPreferences_Always = 0x03, +}; + +struct IAPRetUserDataWorkoutRecordingPayload { + uint8_t type; /* = IAPSportsUserDataType_WorkoutRecording */ + uint8_t preference; /* IAPSportsWorkoutRecordingPreferences */ +} __attribute__((packed)); + +/* IAPSetUserDataPayload = IAPRetUserDataPayload */ diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-index.h b/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-index.h new file mode 100644 index 0000000000..2fce3aa328 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/sports/user-index.h @@ -0,0 +1,6 @@ +#pragma once +#include + +struct IAPRetUserIndexPayload { + uint8_t user_index; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage.h new file mode 100644 index 0000000000..cfce9291b1 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage.h @@ -0,0 +1,27 @@ +#pragma once +#include + +/* [1] P.357 Table 4-242 Storage lingo commands */ + +enum IAPStorageCommandID { + IAPStorageCommandID_IPodAck = 0x00, /* from dev, ipod-ack.h */ + IAPStorageCommandID_GetIPodCaps = 0x01, /* from acc, no payload */ + IAPStorageCommandID_RetIPodCaps = 0x02, /* from dev, ipod-caps.h */ + IAPStorageCommandID_RetIPodFileHandle = 0x04, /* from dev, ipod-file-handle.h */ + IAPStorageCommandID_WriteIPodFileData = 0x07, /* from acc, write-ipod-file-data.h */ + IAPStorageCommandID_CloseIPodFile = 0x08, /* from acc, ipod-file-handle.h */ + IAPStorageCommandID_GetIPodFreeSpace = 0x10, /* from acc, no payload */ + IAPStorageCommandID_RetIPodFreeSpace = 0x11, /* from dev, ipod-free-space.h */ + IAPStorageCommandID_OpenIPodFeatureFile = 0x12, /* from acc, ipod-file-handle.h */ + IAPStorageCommandID_AccessoryAck = 0x80, /* from acc, acc-ack.h */ + IAPStorageCommandID_GetAccessoryCaps = 0x81, /* from dev, no payload */ + IAPStorageCommandID_RetAccessoryCaps = 0x82, /* from acc, */ +}; + +#include "storage/acc-ack.h" +#include "storage/accessory-caps.h" +#include "storage/ipod-ack.h" +#include "storage/ipod-caps.h" +#include "storage/ipod-file-handle.h" +#include "storage/ipod-free-space.h" +#include "storage/write-ipod-file-data.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/acc-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/acc-ack.h new file mode 100644 index 0000000000..1980237af2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/acc-ack.h @@ -0,0 +1,8 @@ +#pragma once +#include + +struct IAPStorageAccessoryAckPayload { + uint8_t status; /* IAPAckStatus */ + uint8_t id; + uint8_t handle; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/accessory-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/accessory-caps.h new file mode 100644 index 0000000000..7a7c26deed --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/accessory-caps.h @@ -0,0 +1,11 @@ +#pragma once +#include + +/* [1] P.365 4.11.14 Command 0x82: RetAccessoryCaps */ + +struct IAPStorageRetAccessoryCapsPayload { + uint8_t reserved1; /* = 0x00 */ + uint8_t reserved2; /* = 0xFF */ + uint8_t major; + uint8_t minor; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-ack.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-ack.h new file mode 100644 index 0000000000..05b9c012e2 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-ack.h @@ -0,0 +1,10 @@ +#pragma once +#include + +/* [1] P.359 4.11.3 Command 0x00: iPodAck */ + +struct IAPStorageIPodAckPayload { + uint8_t status; /* IAPAckStatus */ + uint8_t id; + uint8_t handle; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-caps.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-caps.h new file mode 100644 index 0000000000..2a4d04be46 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-caps.h @@ -0,0 +1,13 @@ +#pragma once +#include + +/* [1] P.359 4.11.5 Command 0x02: RetiPodCaps */ + +struct IAPIPodRetIPodCapsPayload { + uint64_t total_space; + uint32_t max_file_size; + uint16_t max_write_size; + uint8_t reserved[6]; + uint8_t major_version; + uint8_t minor_version; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-file-handle.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-file-handle.h new file mode 100644 index 0000000000..edaeefc7cd --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-file-handle.h @@ -0,0 +1,30 @@ +#pragma once +#include + +/* [1] P.360 4.11.6 Command 0x04: RetiPodFileHandle */ + +struct IAPRetIPodFileHandlePayload { + uint8_t handle; +} __attribute__((packed)); + +struct IAPCloseIPodFilePayload { + uint8_t handle; +} __attribute__((packed)); + +enum IAPOpenIPodFeatureFileFeatureType { + IAPOpenIPodFeatureFileFeatureType_TadioTagging = 0x01, + IAPOpenIPodFeatureFileFeatureType_CardioEquipmentWorkout = 0x02, +}; + +enum IAPOpenIPodFeatureFileOptionsMask { + IAPOpenIPodFeatureFileOptionsMask_AppendFileData = 1 << 0, + IAPOpenIPodFeatureFileOptionsMask_AppendIPodInfo = 1 << 1, + IAPOpenIPodFeatureFileOptionsMask_InsertSignature = 1 << 3, +}; + +struct IAPOpenIPodFeatureFilePayload { + uint8_t feature_type; /* IAPOpenIPodFeatureFileFeatureType */ + uint32_t options_mask; /* IAPOpenIPodFeatureFileOptionsMask */ + uint8_t file_data[]; +} __attribute__((packed)); + diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-free-space.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-free-space.h new file mode 100644 index 0000000000..8f81ec8059 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/ipod-free-space.h @@ -0,0 +1,8 @@ +#pragma once +#include + +/* [1] P.362 4.11.10 Command 0x11: RetiPodFreeSpace */ + +struct IAPRetIPodFreeSpacePayload { + uint64_t free_space; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/storage/write-ipod-file-data.h b/firmware/usbstack/iap/libiap/spec/lingoes/storage/write-ipod-file-data.h new file mode 100644 index 0000000000..1c6fe7ce42 --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/storage/write-ipod-file-data.h @@ -0,0 +1,10 @@ +#pragma once +#include + +/* [1] P.360 4.11.7 Command 0x07: WriteiPodFileData */ + +struct IAPWriteIPodFileDataPayload { + uint32_t offset; + uint8_t handle; + uint8_t data[]; +} __attribute__((packed)); diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode.h b/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode.h new file mode 100644 index 0000000000..cf4594bdec --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode.h @@ -0,0 +1,14 @@ +#pragma once +#include + +/* [1] P.283 Table 4-101 USB Host mode commands */ +enum IAPUSBHostModeCommandID { + IAPUSBHostModeCommandID_AccessoryAck = 0x00, /* from acc, general/acc-ack.h */ + IAPUSBHostModeCommandID_NotifyUSBMode = 0x04, /* from dev, usb-mode.h */ + IAPUSBHostModeCommandID_IPodAck = 0x80, /* from dev, general/ipod-ack.h */ + IAPUSBHostModeCommandID_GetIPodUSBMode = 0x81, /* from acc, no payload */ + IAPUSBHostModeCommandID_RetIPodUSBMode = 0x82, /* from dev, usb-mode.h */ + IAPUSBHostModeCommandID_SetIPodUSBMode = 0x83, /* from acc, usb-mode.h */ +}; + +#include "usb-host-mode/usb-mode.h" diff --git a/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode/usb-mode.h b/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode/usb-mode.h new file mode 100644 index 0000000000..fdc0f6e58d --- /dev/null +++ b/firmware/usbstack/iap/libiap/spec/lingoes/usb-host-mode/usb-mode.h @@ -0,0 +1,22 @@ +#pragma once +#include + +/* [1] P.285 4.6.3 Command 0x04: NotifyUSBMode */ + +enum IAPUSBHostModeStatusCodes { + Disabled = 0x00, + DeviceMode = 0x01, + HostMode = 0x02, +}; + +struct IAPNotifyUSBModePayload { + uint8_t mode; /* IAPUSBHostModeStatusCodes */ +}; + +struct IAPRetIPodUSBModePayload { + uint8_t mode; /* IAPUSBHostModeStatusCodes */ +}; + +struct IAPSetIPodUSBModePayload { + uint8_t mode; /* IAPUSBHostModeStatusCodes */ +}; diff --git a/firmware/usbstack/iap/libiap/time.h b/firmware/usbstack/iap/libiap/time.h new file mode 100644 index 0000000000..304bb4f04d --- /dev/null +++ b/firmware/usbstack/iap/libiap/time.h @@ -0,0 +1,11 @@ +#pragma once +#include + +struct IAPPlatformTime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t seconds; +}; diff --git a/firmware/usbstack/iap/libiap/unaligned.h b/firmware/usbstack/iap/libiap/unaligned.h new file mode 100644 index 0000000000..864cac0a34 --- /dev/null +++ b/firmware/usbstack/iap/libiap/unaligned.h @@ -0,0 +1,7 @@ +#pragma once +#include + +typedef __attribute__((aligned(1))) uint8_t uu8; +typedef __attribute__((aligned(1))) uint16_t uu16; +typedef __attribute__((aligned(1))) uint32_t uu32; +typedef __attribute__((aligned(1))) uint64_t uu64; diff --git a/firmware/usbstack/iap/macros.h b/firmware/usbstack/iap/macros.h new file mode 100644 index 0000000000..f741398560 --- /dev/null +++ b/firmware/usbstack/iap/macros.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include + +#include "debug.h" + +#if DEBUG_ENABLE_INFO == 1 +#define LOG(fmt, ...) logf("%lu %s:%d " fmt, iap_debug_timestamp(), __func__, __LINE__ __VA_OPT__(, __VA_ARGS__)); +#else +#define LOG(...) +#endif + +#if DEBUG_ENABLE_ERROR == 1 +#define ERROR(fmt, ...) logf("%lu %s:%d " fmt, iap_debug_timestamp(), __func__, __LINE__ __VA_OPT__(, __VA_ARGS__)); +#else +#define ERROR(...) +#endif + +#define check_act(cond, act, ...) \ + if(!(cond)) { \ + ERROR("assertion failed" __VA_OPT__(":" __VA_ARGS__)); \ + act; \ + } + +#define AS_PACKET_SIZE 192 +#define AS_EP_IN usb_iap_ep_allocs[0].ep +#define HID_EP_IN usb_iap_ep_allocs[1].ep diff --git a/firmware/usbstack/iap/notification.c b/firmware/usbstack/iap/notification.c new file mode 100644 index 0000000000..95444f077c --- /dev/null +++ b/firmware/usbstack/iap/notification.c @@ -0,0 +1,111 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "playback.h" + +#include "iap-usb.h" +#include "libiap/iap.h" +#include "platform.h" + +#include "macros.h" + +extern bool iap_initialized; + +void iap_on_track_time_position(uint32_t pos_ms) { + if(!iap_initialized) { + return; + } + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_track_time_position(ctx, pos_ms); +} + +void iap_on_track_playback_index(uint32_t index, bool track_ready) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + + if(track_ready) { + /* called from from audio_finish_load_track() */ + goto notify; + } + + /* called from audio_playlist_track_change() */ + struct Platform* plt = ctx->platform; + if(plt->aa_slot < 0) { + goto notify; + } + if(playback_current_aa_hid(plt->aa_slot) >= 0) { + /* artwork ready, maybe preloaded track */ + goto notify; + } else { + /* artwork not ready, maybe after a playlist jump. + * in this case, we will be called again from audio_finish_load_track(), + * with track_ready == true. */ + return; + } +notify: + iap_notify_track_playback_index(ctx, index); +} + +void iap_on_tracks_count(uint32_t count) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_tracks_count(ctx, count); +} + +void iap_on_play_status(int status) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_play_status(ctx, _iap_convert_play_status(status)); +} + +void iap_on_volume(int volume) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_volume(ctx, _iap_convert_volume(volume), iap_false); +} + +void iap_on_shuffle_state(bool state) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_shuffle_state(ctx, _iap_convert_shuffle_state(state)); +} + +void iap_on_repeat_state(int state) { + if(!iap_initialized) { + return; + } + + struct IAPContext* ctx = _iap_acquire_ctx(false); + iap_notify_repeat_state(ctx, _iap_convert_repeat_state(state)); +} diff --git a/firmware/usbstack/iap/platform-macros.h b/firmware/usbstack/iap/platform-macros.h new file mode 100644 index 0000000000..6b1eb8a5ab --- /dev/null +++ b/firmware/usbstack/iap/platform-macros.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once +#include + +#include "debug.h" + +#if DEBUG_ENABLE_INFO == 1 +#define IAP_LOGF(fmt, ...) logf("%lu " fmt, iap_debug_timestamp() __VA_OPT__(, __VA_ARGS__)) +#endif + +#if DEBUG_ENABLE_ERROR == 1 +#define IAP_ERRORF(fmt, ...) logf("%lu " fmt, iap_debug_timestamp() __VA_OPT__(, __VA_ARGS__)) +#endif + +#define IAP_ARTWORK_WIDTH 128 +#define IAP_ARTWORK_HEIGHT 128 +#define IAP_COLOR_ARTWORK iap_true diff --git a/firmware/usbstack/iap/platform.c b/firmware/usbstack/iap/platform.c new file mode 100644 index 0000000000..1e5ce22a9e --- /dev/null +++ b/firmware/usbstack/iap/platform.c @@ -0,0 +1,482 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "audio.h" +#include "buffering.h" +#include "core_alloc.h" +#include "metadata.h" +#include "misc.h" +#include "pcm_mixer.h" +#include "pcm_sink.h" +#include "playback.h" +#include "playlist.h" +#include "powermgmt.h" +#include "settings.h" +#include "sound.h" +#include "usb_drv.h" + +#include "../usb_iap.h" +#include "debug.h" +#include "libiap/iap.h" +#include "macros.h" +#include "platform.h" + +void* iap_platform_malloc(struct IAPContext* iap_ctx, size_t size, int flags) { + struct Platform* plt = iap_ctx->platform; + for(size_t i = 0; i < ARRAYLEN(plt->malloc_results); i += 1) { + if(plt->malloc_results[i].ptr != NULL) { + continue; + } + struct IAPAllocResult result; + if(flags & IAPPlatformMallocFlags_Uncached) { + check_act(iap_alloc_usb_send_buffer(size, &result), return NULL); + } else { + check_act(iap_alloc_buffer(size, &result), return NULL); + } + plt->malloc_results[i] = result; + return result.ptr; + } + ERROR("no free malloc slot"); + return NULL; +} + +void iap_platform_free(struct IAPContext* iap_ctx, void* ptr) { + struct Platform* plt = iap_ctx->platform; + for(size_t i = 0; i < ARRAYLEN(plt->malloc_results); i += 1) { + if(plt->malloc_results[i].ptr == ptr) { + core_free(plt->malloc_results[i].handle); + plt->malloc_results[i].ptr = NULL; + return; + } + } + ERROR("no matching malloc slot"); +} + +int iap_platform_send_hid_report(struct IAPContext* iap_ctx, const void* ptr, size_t size) { + (void)iap_ctx; +#if DEBUG_DUMP_TX == 1 + logf("==== dev ==== %p %u > %d", ptr, size, HID_EP_IN); + iap_platform_dump_hex(ptr, MIN(size, 48)); +#endif + const int ret = usb_drv_send_nonblocking(HID_EP_IN, (void*)ptr, size); + return ret == 0 ? (int)size : ret; +} + +IAPBool iap_platform_get_ipod_serial_num(struct IAPContext* iap_ctx, struct IAPSpan* serial) { + (void)iap_ctx; + static const char* serial_num = "000000000000"; + return iap_span_append(serial, serial_num, strlen(serial_num) + 1); +} + +IAPBool iap_platform_get_play_status(struct IAPContext* iap_ctx, struct IAPPlatformPlayStatus* status) { + struct Platform* plt = iap_ctx->platform; + + status->state = _iap_convert_play_status(audio_status()); + if(status->state == IAPIPodStatePlayStatus_PlaybackStopped) { + return iap_true; + } + + struct mp3entry* id3 = audio_current_track(); + check_act(id3 != NULL, return iap_false); + status->track_total_ms = id3->length; + status->track_pos_ms = id3->elapsed; + status->track_index = playlist_get_display_index() - 1; + status->track_count = playlist_amount(); + status->track_caps = IAPIPodStateTrackCapBits_HasReleaseDate; + if(plt->aa_slot >= 0 && playback_current_aa_hid(plt->aa_slot) >= 0) { + status->track_caps |= IAPIPodStateTrackCapBits_HasAlbumArts; + } + + return iap_true; +} + +void iap_platform_control(struct IAPContext* iap_ctx, enum IAPPlatformControl control, struct IAPPlatformPendingControl pending) { + struct Platform* plt = iap_ctx->platform; + + long button = BUTTON_NONE; + switch(control) { + case IAPPlatformControl_TogglePlayPause: + button = BUTTON_MULTIMEDIA_PLAYPAUSE; + break; + case IAPPlatformControl_Play: + if(audio_status() != AUDIO_STATUS_PLAY) { + button = BUTTON_MULTIMEDIA_PLAYPAUSE; + } + break; + case IAPPlatformControl_Pause: + if(audio_status() == AUDIO_STATUS_PLAY) { + button = BUTTON_MULTIMEDIA_PLAYPAUSE; + } + break; + case IAPPlatformControl_Stop: + button = BUTTON_MULTIMEDIA_STOP; + break; + case IAPPlatformControl_Next: + button = BUTTON_MULTIMEDIA_NEXT; + break; + case IAPPlatformControl_Prev: + button = BUTTON_MULTIMEDIA_PREV; + break; + case IAPPlatformControl_VolumeUp: + adjust_volume(1); + break; + case IAPPlatformControl_VolumeDown: + adjust_volume(-1); + break; + case IAPPlatformControl_ToggleMute: + /* rockbox has no mute/unmute */ + break; + } + logf("control=%d button=0x%04lX", control, button); + + IAPBool ret = iap_true; + if(button == BUTTON_NONE) { + goto exit; + } + check_act(button_queue_try_post(button, 0), ret = false); + if(ret && button == BUTTON_MULTIMEDIA_PLAYPAUSE && audio_status() == 0) { + /* we are transitioning to stopped to playing, which may take time. + * to maintain synchronization with accessories, do not send an ack until playback actually begins. */ + plt->control_pending = true; + plt->pending_control = pending; + return; + } +exit: + check_act(iap_control_response(iap_ctx, pending, ret), ); +} + +static uint8_t normalize_8(int val, int min, int max) { + return 0xFF * (val - min) / (max - min); +} + +IAPBool iap_platform_get_volume(struct IAPContext* iap_ctx, struct IAPPlatformVolumeStatus* status) { + (void)iap_ctx; + + status->volume = _iap_convert_volume(global_status.volume); + status->muted = iap_false; + return iap_true; +} + +IAPBool iap_platform_get_power_status(struct IAPContext* iap_ctx, struct IAPPlatformPowerStatus* status) { + (void)iap_ctx; + status->battery_level = _iap_convert_battery_level(battery_level()); + status->state = _iap_convert_charge_status(charge_state); + return iap_true; +} + +IAPBool iap_platform_get_shuffle_setting(struct IAPContext* iap_ctx, uint8_t* status) { + (void)iap_ctx; + *status = _iap_convert_shuffle_state(global_settings.playlist_shuffle); + return iap_true; +} + +IAPBool iap_platform_set_shuffle_setting(struct IAPContext* iap_ctx, uint8_t status) { + (void)iap_ctx; + + if(status == IAPIPodStateShuffleSettingState_Tracks && !global_settings.playlist_shuffle) { + global_settings.playlist_shuffle = true; + settings_save(); + if(audio_status() & AUDIO_STATUS_PLAY) { + playlist_randomise(NULL, current_tick, true); + } + } else if(status == IAPIPodStateShuffleSettingState_Off && global_settings.playlist_shuffle) { + global_settings.playlist_shuffle = false; + settings_save(); + if(audio_status() & AUDIO_STATUS_PLAY) { + playlist_sort(NULL, true); + } + } + return iap_true; +} + +IAPBool iap_platform_get_repeat_setting(struct IAPContext* iap_ctx, uint8_t* status) { + (void)iap_ctx; + *status = _iap_convert_repeat_state(global_settings.repeat_mode); + return iap_true; +} + +IAPBool iap_platform_set_repeat_setting(struct IAPContext* iap_ctx, uint8_t status) { + (void)iap_ctx; + + /* there are more repeat options in Rockbox rather than iAP. + * e.g. RB Repeat One and AB are both mapped to iAP Repeat One. + * so we should not accept iAP Repeat One as RB Repeat One, if we have + * RB Repeat AB set. */ + if(_iap_convert_repeat_state(global_settings.repeat_mode) == status) { + /* already the same (or equievalent) mode set */ + return iap_true; + } + + static const uint8_t table[] = { + [IAPIPodStateRepeatSettingState_Off] = REPEAT_OFF, + [IAPIPodStateRepeatSettingState_One] = REPEAT_ONE, + [IAPIPodStateRepeatSettingState_All] = REPEAT_ALL, + }; + check_act(status < ARRAY_SIZE(table), return iap_false); + global_settings.repeat_mode = table[status]; + settings_save(); + if(audio_status() & AUDIO_STATUS_PLAY) { + audio_flush_and_reload_tracks(); + } + return iap_true; +} + +IAPBool iap_platform_get_date_time(struct IAPContext* iap_ctx, struct IAPDateTime* time) { + (void)iap_ctx; + _iap_convert_datetime(get_time(), time); + return iap_true; +} + +IAPBool iap_platform_get_backlight_level(struct IAPContext* iap_ctx, uint8_t* level) { + (void)iap_ctx; + +#ifdef HAVE_BACKLIGHT_BRIGHTNESS + *level = normalize_8(global_settings.brightness, MIN_BRIGHTNESS_SETTING, MAX_BRIGHTNESS_SETTING); +#else + *level = 0xFF; +#endif + return iap_true; +} + +IAPBool iap_platform_get_hold_switch_state(struct IAPContext* iap_ctx, IAPBool* state) { + (void)iap_ctx; + +#ifdef HAS_BUTTON_HOLD + *state = button_hold(); +#else + *state = iap_false; +#endif + return iap_true; +} + +/* taken from iap-core.c */ +static void get_trackinfo(const unsigned int track, struct mp3entry* id3) { + int tracknum = track; + tracknum += playlist_get_first_index(NULL); + if(tracknum >= playlist_amount()) { + tracknum -= playlist_amount(); + } + + if(playlist_next(0) != tracknum) { + struct playlist_track_info info; + playlist_get_track_info(NULL, tracknum, &info); + get_metadata(id3, -1, info.filename); + } else { + memcpy(id3, audio_current_track(), sizeof(*id3)); + } +} + +IAPBool iap_platform_get_indexed_track_info(struct IAPContext* iap_ctx, uint32_t index, struct IAPPlatformTrackInfo* info) { + struct Platform* plt = iap_ctx->platform; + + struct playlist_track_info track; + struct mp3entry id3; + check_act(playlist_get_track_info(NULL, index, &track) == 0, return iap_false); + get_trackinfo(index, &id3); + + if(info->total_ms != NULL) { + *info->total_ms = id3.length; + } + if(info->caps != NULL) { + *info->caps = IAPIPodStateTrackCapBits_HasReleaseDate; + /* FIXME: respect index */ + if(plt->aa_slot >= 0 && playback_current_aa_hid(plt->aa_slot) >= 0) { + *info->caps |= IAPIPodStateTrackCapBits_HasAlbumArts; + } + } + if(info->release_date != NULL) { + info->release_date->year = id3.year; + info->release_date->month = 0; + info->release_date->day = 0; + info->release_date->hour = 0; + info->release_date->minute = 0; + info->release_date->seconds = 0; + } + if(info->artist != NULL) { + check_act(iap_span_append(info->artist, id3.artist, strlen(id3.artist) + 1), return iap_false); + } + if(info->composer != NULL) { + check_act(iap_span_append(info->composer, id3.composer, strlen(id3.composer) + 1), return iap_false); + } + if(info->album != NULL) { + check_act(iap_span_append(info->album, id3.album, strlen(id3.album) + 1), return iap_false); + } + if(info->title != NULL) { + check_act(iap_span_append(info->title, id3.title, strlen(id3.title) + 1), return iap_false); + } + return iap_true; +} + +IAPBool iap_platform_set_playing_track(struct IAPContext* iap_ctx, uint32_t index) { + (void)iap_ctx; + audio_skip((int)index - playlist_next(0)); + return iap_true; +} + +IAPBool iap_platform_open_artwork(struct IAPContext* iap_ctx, uint32_t index, struct IAPPlatformArtwork* artwork) { + struct Platform* plt = iap_ctx->platform; + /* only aa for currently playing track is available */ + check_act((int)index == playlist_get_display_index() - 1, return iap_false); + const int hid = playback_current_aa_hid(plt->aa_slot); + check_act(hid >= 0, return iap_false, "%d %d", plt->aa_slot, hid); + struct bitmap* bmp; + check_act(bufgetdata(hid, 0, (void*)&bmp) > 0, return iap_false); + artwork->color = iap_true; + artwork->width = bmp->width; + artwork->height = bmp->height; + artwork->opaque = hid; + return iap_true; +} + +IAPBool iap_platform_get_artwork_ptr(struct IAPContext* iap_ctx, struct IAPPlatformArtwork* artwork, struct IAPSpan* span) { + struct Platform* plt = iap_ctx->platform; + + /* check the albumart has not reloaded */ + /* FIXME: not a correct check due to possibility of hid confliction */ + const int hid = playback_current_aa_hid(plt->aa_slot); + check_act(hid == (int)artwork->opaque, return iap_false); + + struct bitmap* bmp; + /* more checks */ + check_act(bufgetdata(hid, 0, (void*)&bmp) > 0, return iap_false); + check_act(bmp->width == artwork->width && bmp->height == artwork->height, return iap_false); + + span->ptr = bmp->data; + span->size = bmp->width * bmp->height * 2; + return iap_true; +} + +IAPBool iap_platform_close_artwork(struct IAPContext* iap_ctx, struct IAPPlatformArtwork* artwork) { + (void)iap_ctx; + (void)artwork; + return iap_true; +} + +void iap_platform_dump_hex(const void* ptr, size_t size) { + if(ptr == NULL) { + logf("(null)"); + return; + } + +#if DEBUG_HEXDUMP_NOLIMIT != 1 + size = MIN(size, 32); +#endif + + static const char chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + char line[4 * (8 + 1) + 1]; + for(size_t l = 0; l * 16 < size; l += 1) { + char* c = line; + for(size_t b = 0; b < 4; b += 1) { + for(size_t i = 0; i < 4; i += 1) { + size_t index = l * 16 + b * 4 + i; + if(index >= size) { + break; + } + *c++ = chars[((uint8_t*)ptr)[index] >> 4]; + *c++ = chars[((uint8_t*)ptr)[index] & 0xF]; + } + *c++ = ' '; + } + *c++ = '\0'; + logf("%04X: %s", l * 16, line); + } +} + +IAPBool iap_platform_on_acc_samprs_received(struct IAPContext* iap_ctx, struct IAPSpan* samprs) { + (void)iap_ctx; + + bool has_44k = false; + bool has_48k = false; + while(samprs->size > 0) { + uint32_t sample_rate; + check_act(iap_span_read_32(samprs, &sample_rate), return iap_false); + has_44k |= sample_rate == SAMPR_44; + has_48k |= sample_rate == SAMPR_48; + } + check_act(has_44k && has_48k, return iap_false, "accessory lacks mandatory freq support: 44k=%d 48k=%d", has_44k, has_48k); + check_act(mixer_switch_sink(PCM_SINK_IAP), return false); + return iap_true; +} + +uint8_t _iap_convert_play_status(int rb_audio_status) { + if(rb_audio_status & AUDIO_STATUS_PAUSE) { + return IAPIPodStatePlayStatus_PlaybackPaused; + } else if(rb_audio_status & AUDIO_STATUS_PLAY) { + return IAPIPodStatePlayStatus_Playing; + } else { + return IAPIPodStatePlayStatus_PlaybackStopped; + } +} + +uint8_t _iap_convert_volume(int rb_volume) { + return normalize_8(rb_volume, sound_min(SOUND_VOLUME), sound_max(SOUND_VOLUME)); +} + +uint8_t _iap_convert_shuffle_state(bool rb_state) { + return rb_state ? IAPIPodStateShuffleSettingState_Tracks : IAPIPodStateShuffleSettingState_Off; +} + +uint8_t _iap_convert_repeat_state(int rb_state) { + static const uint8_t table[] = { + [REPEAT_OFF] = IAPIPodStateRepeatSettingState_Off, + [REPEAT_ALL] = IAPIPodStateRepeatSettingState_All, + [REPEAT_ONE] = IAPIPodStateRepeatSettingState_One, + [REPEAT_SHUFFLE] = IAPIPodStateRepeatSettingState_All, +#ifdef AB_REPEAT_ENABLE + [REPEAT_AB] = IAPIPodStateRepeatSettingState_One, +#endif + }; + + if(rb_state == REPEAT_OFF && global_settings.next_folder) { + /* when repeat is off and next folder is enabled, report repeat all. + * without this hack, the accessory will likely set repeat to all, + * resulting in trapping us in the current directory. */ + return IAPIPodStateRepeatSettingState_All; + } else { + check_act(rb_state < ARRAY_SIZE(table), return iap_false); + return table[rb_state]; + } +} + +uint8_t _iap_convert_battery_level(int rb_battery_level) { + return 0xFF * rb_battery_level / 100; +} + +uint8_t _iap_convert_charge_status(enum charge_state_type rb_charge_state) { + switch(rb_charge_state) { + case CHARGING: + return IAPIPodStatePowerState_ExternalCharging; + case TOPOFF: + case TRICKLE: + return IAPIPodStatePowerState_ExternalCharged; + break; + default: + return IAPIPodStatePowerState_Internal; + } +} + +void _iap_convert_datetime(struct tm* rb_time, struct IAPDateTime* time) { + time->year = rb_time->tm_year + 1900; + time->month = rb_time->tm_mon + 1; + time->day = rb_time->tm_mday; + time->hour = rb_time->tm_hour; + time->minute = rb_time->tm_min; + time->seconds = rb_time->tm_sec; +} diff --git a/firmware/usbstack/iap/platform.h b/firmware/usbstack/iap/platform.h new file mode 100644 index 0000000000..2707722b04 --- /dev/null +++ b/firmware/usbstack/iap/platform.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once +#include + +#include "libiap/datetime.h" +#include "libiap/platform.h" +#include "powermgmt.h" +#include "time.h" + +#include "buffer.h" + +/* usb_iap.c */ +struct IAPContext* _iap_acquire_ctx(bool lock); +void _iap_release_ctx(void); + +struct Platform { + struct IAPAllocResult malloc_results[4]; /* allow up to 4 mallocs */ + struct IAPPlatformPendingControl pending_control; + bool control_pending; + + int aa_slot; +}; + +/* helper functions */ +uint8_t _iap_convert_play_status(int rb_audio_status); +uint8_t _iap_convert_volume(int rb_volume); +uint8_t _iap_convert_shuffle_state(bool rb_state); +uint8_t _iap_convert_repeat_state(int rb_state); +uint8_t _iap_convert_battery_level(int rb_battery_level); +uint8_t _iap_convert_charge_status(enum charge_state_type rb_charge_state); +void _iap_convert_datetime(struct tm* rb_time, struct IAPDateTime* time); diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 9b90fbd5ad..ba4bd82145 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -53,6 +53,10 @@ #include "usb_audio_def.h" // DEBUG #endif +#ifdef USB_ENABLE_IAP +#include "usb_iap.h" +#endif + /* TODO: Move target-specific stuff somewhere else (serial number reading) */ #if defined(IPOD_ARCH) && defined(CPU_PP) @@ -72,7 +76,11 @@ #define USB_MAX_CURRENT 500 #endif +#ifdef USB_ENABLE_IAP +#define NUM_CONFIGS 2 +#else #define NUM_CONFIGS 1 +#endif /*-------------------------------------------------------------------------*/ /* USB protocol descriptors: */ @@ -107,7 +115,7 @@ static struct usb_config_descriptor __attribute__((aligned(2))) .bDescriptorType = USB_DT_CONFIG, .wTotalLength = 0, /* will be filled in later */ .bNumInterfaces = 1, - .bConfigurationValue = 1, + .bConfigurationValue = 0, /* will be filled in later */ .iConfiguration = 0, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = (USB_MAX_CURRENT + 1) / 2, /* In 2mA units */ @@ -303,6 +311,30 @@ static struct usb_class_driver drivers[USB_NUM_DRIVERS] = .get_interface = usb_audio_get_interface, }, #endif +#ifdef USB_ENABLE_IAP + [USB_DRIVER_IAP] = { + .enabled = false, + .needs_exclusive_storage = false, + .config = 2, + .first_interface = 0, + .last_interface = 0, + .ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs), + .ep_allocs = usb_iap_ep_allocs, + .set_first_interface = usb_iap_set_first_interface, + .get_config_descriptor = usb_iap_get_config_descriptor, + .init_connection = usb_iap_init_connection, + .init = usb_iap_init, + .disconnect = usb_iap_disconnect, + .transfer_complete = usb_iap_transfer_complete, + .fast_transfer_complete = usb_iap_fast_transfer_complete, + .control_request = usb_iap_control_request, +#ifdef HAVE_HOTSWAP + .notify_hotswap = NULL, +#endif + .set_interface = usb_iap_set_interface, + .get_interface = usb_iap_get_interface, + }, +#endif }; #ifdef USB_LEGACY_CONTROL_API diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c new file mode 100644 index 0000000000..3126ec6bc3 --- /dev/null +++ b/firmware/usbstack/usb_iap.c @@ -0,0 +1,679 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "audio.h" +#include "pcm_mixer.h" +#include "pcm_sink.h" +#include "playback.h" +#include "powermgmt.h" +#include "timefuncs.h" +#include "usb_drv.h" + +#include "iap/audio.h" +#include "iap/libiap/iap.h" +#include "iap/libiap/platform.h" +#include "iap/macros.h" +#include "iap/platform-macros.h" +#include "iap/platform.h" +#include "usb_audio_def.h" +#include "usb_class_driver.h" +#include "usb_hid_def.h" +#include "usb_iap.h" + +struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2] = { + /* uac input */ + {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false}, + /* hid input */ + {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false}, +}; + +/* interface 0 (audio control) */ +static struct usb_interface_descriptor ipod_audio_control_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = -1, /* dynamic */ + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, +}; + +static struct usb_ac_header ipod_audio_control_uac_header = { + .bLength = USB_AC_SIZEOF_HEADER(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_HEADER, + .bcdADC = 0x0100, /* 1.00 */ + .wTotalLength = -1, /* dynamic */ + .bInCollection = 1, + .baInterfaceNr = {-1}, /* dynamic */ +}; + +static struct usb_ac_input_terminal ipod_audio_control_uac_input_terminal = { + .bLength = sizeof(struct usb_ac_input_terminal), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_INPUT_TERMINAL, + .bTerminalId = 1, + .wTerminalType = USB_AC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 2, /* ipod_audio_control_uac_output_terminal */ + .bNrChannels = 2, + .wChannelConfig = USB_AC_CHANNELS_LEFT_RIGHT_FRONT, +}; + +static struct usb_ac_output_terminal ipod_audio_control_uac_output_terminal = { + .bLength = sizeof(struct usb_ac_output_terminal), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_OUTPUT_TERMINAL, + .bTerminalId = 2, + .wTerminalType = USB_AC_TERMINAL_STREAMING, + .bAssocTerminal = 1, /* ipod_audio_control_uac_input_terminal */ + .bSourceId = 1, +}; + +/* interface 1 (audio stream) */ +/* interface 1 alt 0 */ +static struct usb_interface_descriptor ipod_audio_stream_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = -1, /* dynamic */ + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, +}; + +/* interface 1 alt 1 */ +static struct usb_interface_descriptor ipod_audio_stream_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = -1, /* dynamic */ + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, +}; + +static struct usb_as_interface ipod_audio_stream_1_uac_header = { + .bLength = sizeof(struct usb_as_interface), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AS_GENERAL, + .bTerminalLink = 2, /* ipod_audio_control_uac_output_terminal */ + .bDelay = 1, + .wFormatTag = USB_AS_FORMAT_TYPE_I_PCM, +}; + +/* TODO: remove unsupported freqs */ +static struct usb_as_format_type_i_discrete ipod_audio_stream_1_uac_discrete = { + .bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(9), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AS_FORMAT_TYPE, + .bFormatType = USB_AS_FORMAT_TYPE_I, + .bNrChannels = 2, + .bSubframeSize = 2, /* bBitResolution / 8 */ + .bBitResolution = 16, + .bSamFreqType = 9, + .tSamFreq = { + {0x40, 0x1F, 0x00}, /* 8000 */ + {0x11, 0x2B, 0x00}, /* 11025 */ + {0xE0, 0x2E, 0x00}, /* 12000 */ + {0x80, 0x3E, 0x00}, /* 16000 */ + {0x22, 0x56, 0x00}, /* 22050 */ + {0xC0, 0x5D, 0x00}, /* 24000 */ + {0x00, 0x7D, 0x00}, /* 32000 */ + {0x44, 0xAC, 0x00}, /* 44100 */ + {0x80, 0xBB, 0x00}, /* 48000 */ + }, +}; + +/* interface 1 endpoint 0 */ +static struct usb_as_iso_audio_endpoint ipod_audio_stream_1_endpoint = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = -1, /* dynamic */ + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = AS_PACKET_SIZE, + .bInterval = -1, /* dynamic */ + .bRefresh = 0, + .bSynchAddress = 0, +}; + +static struct usb_as_iso_ctrldata_endpoint ipod_audio_stream_1_endpoint_uac = { + .bLength = sizeof(struct usb_as_iso_ctrldata_endpoint), + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_AS_EP_GENERAL, + .bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL, + .bLockDelayUnits = 0, + .wLockDelay = 0, +}; + +/* interface 2 (hid) */ +static struct usb_interface_descriptor ipod_hid_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = -1, /* dynamic */ + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, +}; + +#define INPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \ + 0x85, id, /* Report ID */ \ + 0x95, count, /* Report Count */ \ + 0x82, 0x02, 0x01 /* Input 0x0102 (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) */ + +#define OUTPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \ + 0x85, id, /* Report ID */ \ + 0x95, count, /* Report Count */ \ + 0x92, 0x02, 0x01 /* Output 0x0102 (...) */ + +#define INPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \ + 0x85, id, /* Report ID */ \ + 0x96, count1, count2, /* Report Count */ \ + 0x82, 0x02, 0x01 /* Input 0x0102 (...) */ + +#define OUTPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \ + 0x85, id, /* Report ID */ \ + 0x96, count1, count2, /* Report Count */ \ + 0x92, 0x02, 0x01 /* Output 0x0102 (...) */ + +// clang-format off +static const uint8_t ipod_hid_report_fs[] = { + 0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */ + 0x09, 0x01, /* Usage 0x01 */ + 0xA1, 0x01, /* Collection 0x01 (Application) */ + 0x75, 0x08, /* Report Size 0x08 */ + 0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */ + 0x15, 0x00, /* Logical Minumum 0x0000 (0) */ + + INPUT_REPORT(0x01, 0x0C), + INPUT_REPORT(0x02, 0x0E), + INPUT_REPORT(0x03, 0x14), + INPUT_REPORT(0x04, 0x3F), + + OUTPUT_REPORT(0x05, 0x08), + OUTPUT_REPORT(0x06, 0x0A), + OUTPUT_REPORT(0x07, 0x0E), + OUTPUT_REPORT(0x08, 0x14), + OUTPUT_REPORT(0x09, 0x3F), + + 0xC0, /* End Collection */ +}; + +static const uint8_t ipod_hid_report_hs[] = { + 0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */ + 0x09, 0x01, /* Usage 0x01 */ + 0xA1, 0x01, /* Collection 0x01 (Application) */ + 0x75, 0x08, /* Report Size 0x08 */ + 0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */ + 0x15, 0x00, /* Logical Minumum 0x0000 (0) */ + + INPUT_REPORT(0x01, 0x05), + INPUT_REPORT(0x02, 0x09), + INPUT_REPORT(0x03, 0x0D), + INPUT_REPORT(0x04, 0x11), + INPUT_REPORT(0x05, 0x19), + INPUT_REPORT(0x06, 0x31), + INPUT_REPORT(0x07, 0x5F), + INPUT_REPORT(0x08, 0xC1), + INPUT_REPORT2(0x09, 0x01, 0x01), + INPUT_REPORT2(0x0A, 0x81, 0x01), + INPUT_REPORT2(0x0B, 0x01, 0x02), + INPUT_REPORT2(0x0C, 0xFF, 0x02), + + OUTPUT_REPORT(0x0D, 0x05), + OUTPUT_REPORT(0x0E, 0x09), + OUTPUT_REPORT(0x1F, 0x0D), + OUTPUT_REPORT(0x10, 0x11), + OUTPUT_REPORT(0x11, 0x19), + OUTPUT_REPORT(0x12, 0x31), + OUTPUT_REPORT(0x13, 0x5F), + OUTPUT_REPORT(0x14, 0xC1), + OUTPUT_REPORT(0x15, 0xFF), + + 0xC0, /* End Collection */ +}; +// clang-format on + +static struct usb_hid_descriptor ipod_hid_hid_desc = { + .bLength = sizeof(struct usb_hid_descriptor), + .bDescriptorType = USB_DT_HID, + .wBcdHID = 0x0111, /* 1.11 */ + .bCountryCode = 0, + .bNumDescriptors = 1, + .bDescriptorType0 = USB_DT_REPORT, + .wDescriptorLength0 = -1, /* dynamic */ +}; + +/* interface 2 endpoint 0 */ +static struct usb_endpoint_descriptor ipod_hid_endpoint = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = -1, /* dynamic */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = -1, /* dynamic */ + .bInterval = 1, +}; + +static struct { + int interface; +} ctrl; + +static struct { + uint32_t sample_rate; + int interface; + int8_t alt; +} stream; + +static struct { + int interface; +} hid; + +static struct mutex iap_ctx_mutex; +static struct Platform platform; +static struct timeout tick_tmo; +static bool iap_ctx_mutex_initialized = false; + +struct IAPContext* _iap_acquire_ctx(bool lock) { + static struct IAPContext ctx; + if(lock) { + mutex_lock(&iap_ctx_mutex); + } + return &ctx; +} + +void _iap_release_ctx() { + mutex_unlock(&iap_ctx_mutex); +} + +bool iap_initialized; + +/* those notifications need pollling */ +static enum charge_state_type last_charge_state; +static uint8_t last_battery_level; +static int8_t last_minute; +static int8_t last_hold_switch_state; + +enum Notify { + Notify_Tick, +}; + +static int tick_callback(struct timeout* tmo) { + (void)tmo; + usb_signal_class_notify(USB_DRIVER_IAP, Notify_Tick); + return HZ / 10; +} + +int usb_iap_set_first_interface(int interface) { + ctrl.interface = interface + 0; + stream.interface = interface + 1; + hid.interface = interface + 2; + return interface + 3; +} + +#define PACK_DESC(desc) pack_data(&dest, &desc, ((struct usb_descriptor_header*)&desc)->bLength) + +int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) { + (void)max_packet_size; + + unsigned char* orig_dest = dest; + + ipod_audio_control_desc.bInterfaceNumber = ctrl.interface; + PACK_DESC(ipod_audio_control_desc); + ipod_audio_control_uac_header.baInterfaceNr[0] = stream.interface; + ipod_audio_control_uac_header.wTotalLength = + sizeof(ipod_audio_control_uac_header) + + sizeof(ipod_audio_control_uac_input_terminal) + + sizeof(ipod_audio_control_uac_output_terminal); + PACK_DESC(ipod_audio_control_uac_header); + PACK_DESC(ipod_audio_control_uac_input_terminal); + PACK_DESC(ipod_audio_control_uac_output_terminal); + + ipod_audio_stream_0_desc.bInterfaceNumber = stream.interface; + ipod_audio_stream_1_desc.bInterfaceNumber = stream.interface; + ipod_audio_stream_1_endpoint.bEndpointAddress = AS_EP_IN; + ipod_audio_stream_1_endpoint.bInterval = usb_drv_port_speed() ? 4 : 1; + PACK_DESC(ipod_audio_stream_0_desc); + PACK_DESC(ipod_audio_stream_1_desc); + PACK_DESC(ipod_audio_stream_1_uac_header); + PACK_DESC(ipod_audio_stream_1_uac_discrete); + PACK_DESC(ipod_audio_stream_1_endpoint); + PACK_DESC(ipod_audio_stream_1_endpoint_uac); + + ipod_hid_desc.bInterfaceNumber = hid.interface; + ipod_hid_endpoint.bEndpointAddress = HID_EP_IN; + if(usb_drv_port_speed()) { + ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_hs); + ipod_hid_endpoint.wMaxPacketSize = 64; + } else { + ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_fs); + ipod_hid_endpoint.wMaxPacketSize = 64; + } + PACK_DESC(ipod_hid_desc); + PACK_DESC(ipod_hid_hid_desc); + PACK_DESC(ipod_hid_endpoint); + + return dest - orig_dest; +} + +void usb_iap_init_connection(void) { + stream.sample_rate = 48000; + last_charge_state = -1; + last_minute = -1; + last_hold_switch_state = -1; + + iap_debug_reset_timestamp(); + + /* TODO: disable iap on error */ + + /* init audio sink */ + check_act(iap_audio_init(), return); + + /* init libiap */ + if(!iap_ctx_mutex_initialized) { + iap_ctx_mutex_initialized = true; + mutex_init(&iap_ctx_mutex); + } + + struct IAPContext* ctx = _iap_acquire_ctx(true); + + const struct IAPOpts opts = { + .usb_highspeed = usb_drv_port_speed(), + .ignore_hid_report_id = iap_true, + .artwork_single_report = iap_false, + .enable_packet_dump = iap_false, + }; + check_act(iap_init_ctx(ctx, opts, &platform), goto cleanup_audio); + _iap_release_ctx(); + + /* prepare artwork */ + struct dim dim = {IAP_ARTWORK_WIDTH, IAP_ARTWORK_HEIGHT}; + platform.aa_slot = playback_claim_aa_slot(&dim); + if(platform.aa_slot < 0) { + ERROR("failed to claim albumart slot"); + } + platform.control_pending = false; + + /* register timer */ + timeout_register(&tick_tmo, tick_callback, HZ / 10, 0); + + iap_initialized = true; + LOG("initialized"); + return; + +cleanup_audio: + _iap_release_ctx(); + iap_audio_deinit(); +} + +int usb_iap_set_interface(int intf, int alt) { + LOG("set interface interface=%d alt=%d", intf, alt); + check_act(intf == stream.interface, return -1); + if(alt == 0) { + check_act(iap_audio_disable(), return -1); + } else if(alt == 1) { + check_act(iap_audio_enable(), return -1); + } else { + ERROR("invalid alt %d", alt); + return -1; + } + return 0; +} + +int usb_iap_get_interface(int intf) { + LOG("get interface interface=%d", intf); + check_act(intf == stream.interface, return -1); + return stream.alt; +} + +void usb_iap_init(void) { + LOG("init"); +} + +void usb_iap_disconnect(void) { + iap_initialized = false; + audio_pause(); + mixer_switch_sink(PCM_SINK_BUILTIN); + timeout_cancel(&tick_tmo); + if(platform.aa_slot >= 0) { + playback_release_aa_slot(platform.aa_slot); + } + struct IAPContext* ctx = _iap_acquire_ctx(true); + check_act(iap_deinit_ctx(ctx), ); + _iap_release_ctx(); + check_act(iap_audio_deinit(), ); + LOG("disconnected"); +} + +void usb_iap_transfer_complete(int ep, int dir, int status, int length) { + (void)length; + + if((ep | dir) == HID_EP_IN) { + check_act(status == 0, return); +#if DEBUG_DUMP_TX + LOG("ep=%d dir=%d state=%d length=%d", ep, dir, status, length); +#endif + struct IAPContext* ctx = _iap_acquire_ctx(true); + check_act(iap_notify_send_complete(ctx), ); + _iap_release_ctx(); + } +} + +bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length) { + (void)status; + (void)length; + return (ep | dir) == AS_EP_IN; +} + +static unsigned char ctrl_buf[256] USB_DEVBSS_ATTR; + +static void respond_zero(struct usb_ctrlrequest* req) { + if(req->wLength > sizeof(ctrl_buf)) { + ERROR("required data too long %u > %u", req->wLength, sizeof(ctrl_buf)); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + } else { + memset(ctrl_buf, 0, req->wLength); + usb_drv_control_response(USB_CONTROL_ACK, ctrl_buf, req->wLength); + } +} + +/* returns true when ctrl_buf has received data */ +static bool receive_data(struct usb_ctrlrequest* req, void* reqdata) { + if(reqdata == NULL) { + /* setup */ + if(req->wLength > sizeof(ctrl_buf)) { + ERROR("parameter too long"); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + } else { + usb_drv_control_response(USB_CONTROL_RECEIVE, ctrl_buf, req->wLength); + } + return false; + } else { + /* data */ + return true; + } +} + +static bool control_request_if_std(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { + (void)reqdata; + + unsigned char* const orig_dest = dest; + switch(req->bRequest) { + case USB_REQ_GET_DESCRIPTOR: { + const uint8_t desc_type = req->wValue >> 8; + const uint8_t desc_index = req->wValue & 0xff; + LOG("descriptor request type=%x index=%x", desc_type, desc_index); + (void)desc_index; + switch(desc_type) { + case USB_DT_HID: + PACK_DATA(&dest, ipod_hid_hid_desc); + break; + case USB_DT_REPORT: + if(usb_drv_port_speed()) { + PACK_DATA(&dest, ipod_hid_report_hs); + } else { + PACK_DATA(&dest, ipod_hid_report_fs); + } + break; + } + if(dest != orig_dest) { + usb_drv_control_response(USB_CONTROL_ACK, orig_dest, MIN(dest - orig_dest, req->wLength)); + return true; + } + } break; + } + return false; +} + +static bool control_request_if_class(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { + (void)dest; + + const uint8_t recip_interface = req->wIndex & 0xff; + if(recip_interface == hid.interface) { + switch(req->bRequest) { + case USB_HID_GET_REPORT: + respond_zero(req); + return true; + case USB_HID_SET_REPORT: { + if(!receive_data(req, reqdata)) { + return true; + } +#if DEBUG_DUMP_RX == 1 + logf("==== acc: %u bytes ====", req->wLength); + iap_platform_dump_hex(reqdata, req->wLength); +#endif + + struct IAPContext* ctx = _iap_acquire_ctx(true); + const bool ret = iap_feed_hid_report(ctx, reqdata, req->wLength); + _iap_release_ctx(); + + check_act(ret, return false); + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + return true; + } + case USB_HID_SET_IDLE: + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + return true; + } + } + return false; +} + +static bool control_request_if_endpoint(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { + (void)dest; + + LOG("ctrl to endpoint %x (stream=%x, hid=%x)", req->wIndex, AS_EP_IN, HID_EP_IN); + if(req->wIndex == AS_EP_IN) { + const uint8_t recip_entity = req->wIndex >> 8; + const uint8_t control_selector = req->wValue >> 8; + (void)recip_entity; + switch(req->bRequest) { + case USB_AC_SET_CUR: + if(!receive_data(req, reqdata)) { + return true; + } + LOG("audio ctrl to stream endpoint entity=0x%02X request=0x%02X length=%u", recip_entity, req->bRequest, req->wLength); + switch(control_selector) { + case USB_AS_EP_CS_SAMPLING_FREQ_CTL: + check_act(req->wLength == 3, goto stall); + stream.sample_rate = ctrl_buf[0] | (ctrl_buf[1] << 8) | (ctrl_buf[2] << 16); + LOG("audio stream sampling rate %lu", stream.sample_rate); + check_act(iap_audio_set_sampr(stream.sample_rate), goto stall); + break; + } + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + return true; + case USB_AC_GET_CUR: + switch(control_selector) { + case USB_AS_EP_CS_SAMPLING_FREQ_CTL: + check_act(req->wLength == 3, goto stall); + ctrl_buf[2] = (stream.sample_rate >> 16) & 0xff; + ctrl_buf[1] = (stream.sample_rate >> 8) & 0xff; + ctrl_buf[0] = (stream.sample_rate & 0xff); + usb_drv_control_response(USB_CONTROL_ACK, ctrl_buf, req->wLength); + return true; + } + /* fallthrough */ + case USB_AC_GET_MIN: + case USB_AC_GET_MAX: + case USB_AC_GET_RES: + respond_zero(req); + return true; + stall: + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + return true; + } + } + return false; +} + +bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { + const uint8_t req_recipient = req->bRequestType & USB_RECIP_MASK; + const uint8_t req_type = req->bRequestType & USB_TYPE_MASK; +#if 0 + LOG("bRequestType=%x, bRequest=%x, wValue=%x, wIndex=%x, wLength=%x", req->bRequestType, req->bRequest, req->wValue, req->wIndex, req->wLength); + LOG("recip=%x type=%x", req_recipient, req_type); +#endif + if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_STANDARD) { + return control_request_if_std(req, reqdata, dest); + } else if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_CLASS) { + return control_request_if_class(req, reqdata, dest); + } else if(req_recipient == USB_RECIP_ENDPOINT && req_type == USB_TYPE_CLASS) { + return control_request_if_endpoint(req, reqdata, dest); + } + return false; +} + +void usb_iap_notify_event(intptr_t data) { + switch(data) { + case Notify_Tick: { + struct IAPContext* ctx = _iap_acquire_ctx(true); + struct Platform* plt = ctx->platform; + if(plt->control_pending) { + /* waiting for playback begins */ + _iap_release_ctx(); + return; + } + + if(last_charge_state < 0 || last_charge_state != charge_state || last_battery_level != battery_level()) { + last_charge_state = charge_state; + last_battery_level = battery_level(); + iap_notify_power_state(ctx, _iap_convert_charge_status(last_charge_state), _iap_convert_battery_level(last_battery_level)); + } + + struct tm* tm = get_time(); + if(last_minute == -1 || last_minute != tm->tm_min) { + last_minute = tm->tm_min; + struct IAPDateTime time; + _iap_convert_datetime(get_time(), &time); + iap_notify_time_setting(ctx, &time); + } + +#ifdef HAS_BUTTON_HOLD + int8_t hold = button_hold() ? 1 : 0; + if(hold != last_hold_switch_state) { + last_hold_switch_state = hold; + iap_notify_hold_switch_state(ctx, last_hold_switch_state); + } +#endif + + check_act(iap_periodic_tick(ctx), ); + _iap_release_ctx(); + } break; + } +} diff --git a/firmware/usbstack/usb_iap.h b/firmware/usbstack/usb_iap.h new file mode 100644 index 0000000000..8825d56514 --- /dev/null +++ b/firmware/usbstack/usb_iap.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Sho Tanimoto + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#pragma once +#include "usb_core.h" +#include "usb_class_driver.h" + +extern struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2]; + +int usb_iap_request_endpoints(struct usb_class_driver*); +int usb_iap_set_first_interface(int interface); +int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size); +void usb_iap_init_connection(void); +bool usb_iap_set_alt_interface(int interface, int alt); +int usb_iap_get_alt_interface(int interface); +void usb_iap_init(void); +void usb_iap_disconnect(void); +void usb_iap_transfer_complete(int ep, int dir, int state, int length); +bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length); +bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); +int usb_iap_set_interface(int intf, int alt); +int usb_iap_get_interface(int intf); +void usb_iap_notify_event(intptr_t data); From 757e683506a45c20e824c581e6961b3ac7137faf Mon Sep 17 00:00:00 2001 From: mojyack Date: Sun, 23 Nov 2025 16:39:43 +0900 Subject: [PATCH 56/88] usb: core: handle apple vendor usb request Change-Id: Iab5135774353630e7bce4939f40ca35940e214f3 --- firmware/usbstack/usb_core.c | 5 +++++ firmware/usbstack/usb_iap.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index ba4bd82145..bd8e3eaa58 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -1039,6 +1039,11 @@ static void request_handler_device(struct usb_ctrlrequest* req, void* reqdata) response_data[1] = 0; usb_drv_control_response(USB_CONTROL_ACK, response_data, 2); break; + #ifdef USB_ENABLE_IAP + case USB_REQ_APPLE_SET_AVAIL_CURRENT: + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + break; + #endif default: logf("bad req:desc %d:%d", req->bRequest, req->wValue); usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); diff --git a/firmware/usbstack/usb_iap.h b/firmware/usbstack/usb_iap.h index 8825d56514..f807009830 100644 --- a/firmware/usbstack/usb_iap.h +++ b/firmware/usbstack/usb_iap.h @@ -21,6 +21,9 @@ #include "usb_core.h" #include "usb_class_driver.h" +/* [2] P.32 Table 2-8 USB Device Vendor Request to set available current from accessory (USB Device Mode only) */ +#define USB_REQ_APPLE_SET_AVAIL_CURRENT (0x40) + extern struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2]; int usb_iap_request_endpoints(struct usb_class_driver*); From c2e1094383fa0b3050506d8cc54ab63e5905449b Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 21 Nov 2025 15:42:05 +0900 Subject: [PATCH 57/88] playback: reserve an aa slot for iap Change-Id: I605017148b6f3c62021e63f58a1ddd8e229c5fbb --- apps/playback.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/playback.c b/apps/playback.c index eeb4aca86e..ef991ea52e 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -178,7 +178,11 @@ struct audio_resume_info static struct mutex id3_mutex SHAREDBSS_ATTR; /* (A,O)*/ /** For album art support **/ +#if defined(USB_ENABLE_IAP) +#define MAX_MULTIPLE_AA (SKINNABLE_SCREENS_COUNT + 1) +#else #define MAX_MULTIPLE_AA SKINNABLE_SCREENS_COUNT +#endif #ifdef HAVE_ALBUMART static int albumart_mode = -1; From e50ad40814b077d19f07447afec27b374e495e7a Mon Sep 17 00:00:00 2001 From: mojyack Date: Thu, 27 Nov 2025 17:16:14 +0900 Subject: [PATCH 58/88] usb: increase usb thread stack size Change-Id: I07283a68056e095efba8019dac2aa37d65c0ef6c --- firmware/usb.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firmware/usb.c b/firmware/usb.c index c3e35d22b3..ae9778c299 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -86,7 +86,12 @@ static int usb_mmc_countdown = 0; #ifndef USB_EXTRA_STACK # define USB_EXTRA_STACK 0x0 /*Define in firmware/export/config/[target].h*/ #endif -static long usb_stack[(DEFAULT_STACK_SIZE*4 + DUMP_BMP_LINESIZE + USB_EXTRA_STACK)/sizeof(long)]; +#ifdef USB_ENABLE_IAP +#define IAP_EXTRA_STACK DEFAULT_STACK_SIZE*2 +#else +#define IAP_EXTRA_STACK 0 +#endif +static long usb_stack[(DEFAULT_STACK_SIZE*4 + DUMP_BMP_LINESIZE + IAP_EXTRA_STACK + USB_EXTRA_STACK)/sizeof(long)]; static const char usb_thread_name[] = "usb"; static unsigned int usb_thread_entry = 0; static bool usb_monitor_enabled = false; From fad99773e3942e07ac53949d26a6505d2e67792a Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 12 Dec 2025 15:49:01 +0900 Subject: [PATCH 59/88] send iap status change notifications install iap event notification callbacks in various locations Change-Id: I637a3ad18cb07ca056ad9b678400ba11d2f8faad --- apps/bookmark.c | 3 +++ apps/gui/skin_engine/skin_touchsupport.c | 3 +++ apps/misc.c | 2 ++ apps/playback.c | 9 +++++++++ apps/playlist.c | 16 ++++++++++++++-- apps/settings_list.c | 5 ++++- firmware/usbstack/usb_core.c | 1 + 7 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/bookmark.c b/apps/bookmark.c index f00ace7c41..d7fe0b29b1 100644 --- a/apps/bookmark.c +++ b/apps/bookmark.c @@ -43,6 +43,7 @@ #include "file.h" #include "pathfuncs.h" #include "playlist_menu.h" +#include "iap-usb.h" /*#define LOGF_ENABLE*/ #include "logf.h" @@ -1125,7 +1126,9 @@ static bool play_bookmark(const char* bookmark) if (parse_bookmark(fnamebuf, sizeof(fnamebuf), bookmark, &resume_info, true)) { global_settings.repeat_mode = resume_info.repeat_mode; + iap_on_repeat_state(global_settings.repeat_mode); global_settings.playlist_shuffle = resume_info.shuffle; + iap_on_shuffle_state(global_settings.playlist_shuffle); #if defined(HAVE_PITCHCONTROL) sound_set_pitch(resume_info.pitch); dsp_set_timestretch(resume_info.speed); diff --git a/apps/gui/skin_engine/skin_touchsupport.c b/apps/gui/skin_engine/skin_touchsupport.c index eaa71c9b70..7098d8aa05 100644 --- a/apps/gui/skin_engine/skin_touchsupport.c +++ b/apps/gui/skin_engine/skin_touchsupport.c @@ -34,6 +34,7 @@ #include "playlist.h" #include "dsp_misc.h" #include "playback.h" +#include "iap-usb.h" /** Disarms all touchregions. */ void skin_disarm_touchregions(struct gui_wps *gwps) @@ -344,6 +345,7 @@ int skin_get_touchaction(struct gui_wps *gwps, int* edge_offset) case ACTION_TOUCH_SHUFFLE: global_settings.playlist_shuffle = !global_settings.playlist_shuffle; + iap_on_shuffle_state(global_settings.playlist_shuffle); replaygain_update(); if (global_settings.playlist_shuffle) playlist_randomise(NULL, current_tick, true); @@ -358,6 +360,7 @@ int skin_get_touchaction(struct gui_wps *gwps, int* edge_offset) const struct settings_list *rep_setting = find_setting(&global_settings.repeat_mode); option_select_next_val(rep_setting, false, true); + iap_on_repeat_state(global_settings.repeat_mode); audio_flush_and_reload_tracks(); action = ACTION_REDRAW; } break; diff --git a/apps/misc.c b/apps/misc.c index a44f106fb9..1780ccd9fe 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -29,6 +29,7 @@ #include "system.h" #include "lcd.h" #include "language.h" /* is_lang_rtl() */ +#include "iap-usb.h" #ifdef HAVE_DIRCACHE #include "dircache.h" @@ -870,6 +871,7 @@ void check_bootfile(bool do_rolo) void setvol(void) { sound_set_volume(global_status.volume); + iap_on_volume(global_status.volume); global_status.last_volume_change = current_tick; status_save(false); } diff --git a/apps/playback.c b/apps/playback.c index ef991ea52e..6a7e764248 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -48,6 +48,7 @@ #include "settings.h" #include "audiohw.h" #include "general.h" +#include "iap-usb.h" #include #ifdef HAVE_TAGCACHE @@ -1411,6 +1412,7 @@ static void audio_playlist_track_change(void) { send_track_event(PLAYBACK_EVENT_TRACK_CHANGE, track_event_flags, id3); + iap_on_track_playback_index(playlist_get_display_index() - 1, false); } position_key = pcmbuf_get_position_key(); @@ -2319,6 +2321,8 @@ static int audio_finish_load_track(struct track_info *infop) by the time PLAYBACK_EVENT_TRACK_CHANGE is sent */ send_track_event(PLAYBACK_EVENT_CUR_TRACK_READY, 0, id3_get(PLAYING_ID3)); + /* Also send pending notification */ + iap_on_track_playback_index(playlist_get_display_index() - 1, true); } #ifdef HAVE_CODEC_BUFFERING @@ -2746,6 +2750,7 @@ static void audio_finalise_track_change(void) if (pause_on_track_change || single_mode_do_pause(info.id3_hid)) { play_status = PLAY_PAUSED; + iap_on_play_status(play_status); pcmbuf_pause(true); pause_on_track_change = false; } @@ -3079,6 +3084,7 @@ static void audio_start_playback(const struct audio_resume_info *resume_info, /* Update our state */ play_status = PLAY_PLAYING; + iap_on_play_status(play_status); } /* Codec's position should be available as soon as it knows it */ @@ -3166,6 +3172,7 @@ static void audio_stop_playback(void) /* Update our state */ ff_rw_mode = false; play_status = PLAY_STOPPED; + iap_on_play_status(play_status); wipe_track_metadata(true); #ifdef HAVE_ALBUMART @@ -3187,6 +3194,7 @@ static void audio_on_pause(bool pause) return; play_status = pause ? PLAY_PAUSED : PLAY_PLAYING; + iap_on_play_status(play_status); if (!pause && codec_skip_pending) { @@ -3828,6 +3836,7 @@ void audio_pcmbuf_position_callback(unsigned long elapsed, off_t offset, struct mp3entry *id3 = id3_get(PLAYING_ID3); id3->elapsed = elapsed; id3->offset = offset; + iap_on_track_time_position(elapsed); } } diff --git a/apps/playlist.c b/apps/playlist.c index c30b323f87..198e2baa01 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -107,6 +107,7 @@ #include "rbunicode.h" #include "root_menu.h" #include "plugin.h" /* To borrow a temp buffer to rewrite a .m3u8 file */ +#include "iap-usb.h" #include "logdiskf.h" #ifdef HAVE_DIRCACHE #include "dircache.h" @@ -1217,6 +1218,8 @@ static int remove_all_tracks_unlocked(struct playlist_info *playlist) playlist->amount = 1; playlist->indices[0] |= PLAYLIST_QUEUED; playlist->flags = 0; /* Reset dirplay and modified flags */ + if (playlist == ¤t_playlist) + iap_on_tracks_count(playlist->amount); if (playlist->last_insert_pos == 0) playlist->last_insert_pos = -1; @@ -1424,6 +1427,8 @@ static int add_track_to_playlist_unlocked(struct playlist_info* playlist, dc_init_filerefs(playlist, insert_position, 1); playlist->amount++; + if (playlist == ¤t_playlist) + iap_on_tracks_count(playlist->amount); return insert_position; } @@ -1506,6 +1511,9 @@ static void find_and_set_playlist_index_unlocked(struct playlist_info* playlist, { playlist->index = playlist->first_index = i; + if (playlist == ¤t_playlist) + iap_on_track_playback_index(rotate_index(playlist, playlist->index), true); + break; } } @@ -2949,6 +2957,7 @@ int playlist_next(int steps) sort_playlist_unlocked(playlist, false, false); randomise_playlist_unlocked(playlist, current_tick, false, true); global_settings.playlist_shuffle = true; + iap_on_shuffle_state(global_settings.playlist_shuffle); playlist->started = true; playlist->index = 0; @@ -3537,8 +3546,9 @@ int playlist_resume(void) } } - if (global_status.resume_index != -1) + if (global_status.resume_index != -1) { playlist->index = global_status.resume_index; + } out: playlist_write_unlock(playlist); @@ -4047,8 +4057,10 @@ static int pl_save_update_control(struct playlist_info* playlist, /* Reset shuffle seed */ playlist->seed = 0; - if (playlist == ¤t_playlist) + if (playlist == ¤t_playlist) { global_settings.playlist_shuffle = false; + iap_on_shuffle_state(global_settings.playlist_shuffle); + } pl_close_control(playlist); close(old_fd); diff --git a/apps/settings_list.c b/apps/settings_list.c index e8508ab099..7601cf66ca 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -67,6 +67,7 @@ #endif #include "playlist.h" #include "tree.h" +#include "iap-usb.h" #include "voice_thread.h" @@ -790,15 +791,17 @@ static void shuffle_playlist_callback(bool shuffle) } } } + iap_on_shuffle_state(shuffle); } static void repeat_mode_callback(int repeat) { + (void)repeat; if ((audio_status() & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY) { audio_flush_and_reload_tracks(); } - (void)repeat; + iap_on_repeat_state(repeat); } static void treesort_callback(int value) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index bd8e3eaa58..2a10c36c7e 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -333,6 +333,7 @@ static struct usb_class_driver drivers[USB_NUM_DRIVERS] = #endif .set_interface = usb_iap_set_interface, .get_interface = usb_iap_get_interface, + .notify_event = usb_iap_notify_event, }, #endif }; From 6d387e2e7f1a2021b65fe8ff44d29d72f898dec8 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 21:09:03 +0900 Subject: [PATCH 60/88] usb: allow class drivers to override max packet size this is required to make hid endpoint of iap class driver work, especially on ipodvideo(arc). at least for arc, it is required to set mps as 64 instead of 512 on highspeed, or some accessories ignore incoming hid reports. Change-Id: I242060faced28a66204146a9c36ef10626d6d265 --- firmware/usbstack/usb_class_driver.h | 5 +++++ firmware/usbstack/usb_core.c | 20 +++++++++++++------- firmware/usbstack/usb_iap.c | 10 ++++++++++ firmware/usbstack/usb_iap.h | 1 + 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index f26324fe20..dc19c31ad5 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -119,6 +119,11 @@ struct usb_class_driver { * Mandatory function if alternate interface support is needed */ int (*get_interface)(int interface); + /* Asks the driver max packet size for the endpoint. + * Drivers can returns desired value in bytes, + * or -1 to use the device controller default */ + int (*get_max_packet_size)(int ep); + /* Invoked by USB_NOTIFY_CLASS_DRIVER Optional function */ void (*notify_event)(intptr_t data); diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 2a10c36c7e..f6b48a3185 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -333,6 +333,7 @@ static struct usb_class_driver drivers[USB_NUM_DRIVERS] = #endif .set_interface = usb_iap_set_interface, .get_interface = usb_iap_get_interface, + .get_max_packet_size = usb_iap_get_max_packet_size, .notify_event = usb_iap_notify_event, }, #endif @@ -661,21 +662,26 @@ static void init_deinit_endpoints(uint8_t conf_index, bool init) { for(int epnum = 0; epnum < USB_NUM_ENDPOINTS; epnum += 1) { for(int dir = 0; dir < 2; dir += 1) { struct ep_alloc_state* alloc = &ep_alloc_states[conf_index][epnum]; - if(alloc->owner[dir] == NULL) { + struct usb_class_driver* driver = alloc->owner[dir]; + if(driver == NULL) { continue; } int ep = epnum | (dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); - int ret = init ? - usb_drv_init_endpoint(ep, alloc->type[dir], -1) : - usb_drv_deinit_endpoint(ep); + int ret; + if(init) { + int ps = driver->get_max_packet_size ? driver->get_max_packet_size(ep) : -1; + ret = usb_drv_init_endpoint(ep, alloc->type[dir], ps); + } else { + ret = usb_drv_deinit_endpoint(ep); + } if(ret) { logf("usb_core: usb_drv_%s_endpoint failed ep=%d dir=%d", init ? "init" : "deinit", epnum, dir); continue; } if(init) { - ep_data[epnum].completion_handler[dir] = alloc->owner[dir]->transfer_complete; - ep_data[epnum].fast_completion_handler[dir] = alloc->owner[dir]->fast_transfer_complete; - ep_data[epnum].control_handler[dir] = alloc->owner[dir]->control_request; + ep_data[epnum].completion_handler[dir] = driver->transfer_complete; + ep_data[epnum].fast_completion_handler[dir] = driver->fast_transfer_complete; + ep_data[epnum].control_handler[dir] = driver->control_request; } } } diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index 3126ec6bc3..6608e12880 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -441,6 +441,16 @@ int usb_iap_get_interface(int intf) { return stream.alt; } +int usb_iap_get_max_packet_size(int ep) { + if(ep == AS_EP_IN) { + return 1024; + } else if(ep == HID_EP_IN) { + return 64; + } else { + panicf("unexpected endpoint number %d", ep); + } +} + void usb_iap_init(void) { LOG("init"); } diff --git a/firmware/usbstack/usb_iap.h b/firmware/usbstack/usb_iap.h index f807009830..c1990bc8d3 100644 --- a/firmware/usbstack/usb_iap.h +++ b/firmware/usbstack/usb_iap.h @@ -39,4 +39,5 @@ bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length); bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); int usb_iap_set_interface(int intf, int alt); int usb_iap_get_interface(int intf); +int usb_iap_get_max_packet_size(int ep); void usb_iap_notify_event(intptr_t data); From 82a0921399a14ee3b091d6957aca17a4c05bdb9b Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 3 May 2026 15:10:03 -0400 Subject: [PATCH 61/88] build: Fix yellow in 6d387e2e7f when panic is not supported Change-Id: I8b896ef05ca27b3abc05d5d6910d5f6b9741047c --- firmware/usbstack/usb_iap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index 6608e12880..bf6889e66e 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -24,6 +24,7 @@ #include "powermgmt.h" #include "timefuncs.h" #include "usb_drv.h" +#include "panic.h" #include "iap/audio.h" #include "iap/libiap/iap.h" @@ -448,6 +449,7 @@ int usb_iap_get_max_packet_size(int ep) { return 64; } else { panicf("unexpected endpoint number %d", ep); + return 0; } } From 3c10b21c10ca1c0f888ae30696e9a791199ded0d Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 21:23:52 +0900 Subject: [PATCH 62/88] usb: hold class_driver instance in each driver Change-Id: Ia2f857bffcc6c3cca4dee59791c48c628082595b --- firmware/usbstack/usb_audio.c | 109 +++++++++++++-- firmware/usbstack/usb_audio.h | 110 +--------------- firmware/usbstack/usb_charging_only.c | 11 +- firmware/usbstack/usb_charging_only.h | 8 +- firmware/usbstack/usb_core.c | 183 +++++--------------------- firmware/usbstack/usb_hid.c | 32 +++-- firmware/usbstack/usb_hid.h | 13 +- firmware/usbstack/usb_iap.c | 44 +++++-- firmware/usbstack/usb_iap.h | 17 +-- firmware/usbstack/usb_serial.c | 36 +++-- firmware/usbstack/usb_serial.h | 13 +- firmware/usbstack/usb_storage.c | 37 ++++-- firmware/usbstack/usb_storage.h | 14 +- 13 files changed, 258 insertions(+), 369 deletions(-) diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index fd135cf7a3..9dc96579e7 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -297,15 +297,15 @@ static int usb_as_playback_intf_alt; /* playback streaming interface alternate s static int as_playback_freq_idx; /* audio playback streaming frequency index (in hw_freq_sampr) */ -struct usb_class_driver_ep_allocation usb_audio_ep_allocs[2] = { +static struct usb_class_driver_ep_allocation ep_allocs[2] = { /* output isochronous endpoint */ {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_OUT, .optional = false}, /* input feedback isochronous endpoint */ {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false}, }; -#define EP_ISO_OUT (usb_audio_ep_allocs[0].ep) -#define EP_ISO_FEEDBACK_IN (usb_audio_ep_allocs[1].ep) +#define EP_ISO_OUT (ep_allocs[0].ep) +#define EP_ISO_FEEDBACK_IN (ep_allocs[1].ep) /* small buffer used for control transfers */ static unsigned char usb_buffer[128] USB_DEVBSS_ATTR; @@ -501,7 +501,12 @@ unsigned long usb_audio_get_playback_sampling_frequency(void) return hw_freq_sampr[as_playback_freq_idx]; } -void usb_audio_init(void) +/* + * Initialize the driver. Called by usb_core_init(). + * Currently initializes the sampling frequency values available + * to the AudioStreaming interface. + */ +static void usb_audio_init(void) { unsigned int i; /* initialized tSamFreq array */ @@ -577,14 +582,34 @@ unsigned int usb_audio_get_in_ep(void) return EP_ISO_FEEDBACK_IN; } -int usb_audio_set_first_interface(int interface) +/* + * Required function for the class driver. + * + * Called by allocate_interfaces_and_endpoints() to + * tell the class driver what its first interface number is. + * Returns the number of the interface available for the next + * class driver to use. + * + * We need 2 interfaces, AudioControl and AudioStreaming. + * Return interface+2. + */ +static int usb_audio_set_first_interface(int interface) { usb_interface = interface; logf("usbaudio: usb_interface=%d", usb_interface); return interface + 2; /* Audio Control and Audio Streaming */ } -int usb_audio_get_config_descriptor(unsigned char *dest, int max_packet_size) +/* + * Required function for the class driver. + * + * Called by request_handler_device_get_descriptor(), which expects + * this function to fill *dest with the configuration descriptor for this + * class driver. + * + * Return the size of this descriptor in bytes. + */ +static int usb_audio_get_config_descriptor(unsigned char *dest, int max_packet_size) { (void)max_packet_size; unsigned int i; @@ -721,7 +746,13 @@ static void usb_audio_stop_playback(void) send_fb = false; } -int usb_audio_set_interface(int intf, int alt) +/* + * Called by control_request_handler_drivers(). + * Deal with changing the interface between control and streaming. + * + * Return 0 for success, -1 otherwise. + */ +static int usb_audio_set_interface(int intf, int alt) { if(intf == usb_interface) { @@ -757,7 +788,13 @@ int usb_audio_set_interface(int intf, int alt) } } -int usb_audio_get_interface(int intf) +/* + * Called by control_request_handler_drivers(). + * Get the alternate of the given interface. + * + * Return the alternate of the given interface, -1 if unknown. + */ +static int usb_audio_get_interface(int intf) { if(intf == usb_interface) { @@ -1139,7 +1176,13 @@ static bool usb_audio_interface_request(struct usb_ctrlrequest* req, void *reqda } } -bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata, unsigned char* dest) +/* + * Called by control_request_handler_drivers(). + * Pass control requests down to the appropriate functions. + * + * Return true if this driver handles the request, false otherwise. + */ +static bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata, unsigned char* dest) { (void) reqdata; (void) dest; @@ -1156,7 +1199,12 @@ bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata, unsig } } -void usb_audio_init_connection(void) +/* + * Called by usb_core_do_set_config() when the + * connection is ready to be used. Currently just sets + * the audio sample rate to default. + */ +static void usb_audio_init_connection(void) { logf("usbaudio: init connection"); @@ -1181,7 +1229,13 @@ void usb_audio_init_connection(void) usb_audio_playing = false; } -void usb_audio_disconnect(void) +/* + * Called by usb_core_exit() AND usb_core_do_set_config(). + * + * Indicates to the Class driver that the connection is no + * longer active. Currently just calls usb_audio_stop_playback(). + */ +static void usb_audio_disconnect(void) { logf("usbaudio: disconnect"); @@ -1258,7 +1312,12 @@ int usb_audio_get_frames_dropped(void) return frames_dropped; } -void usb_audio_transfer_complete(int ep, int dir, int status, int length) +/* + * Dummy function. + * + * The fast_transfer_complete() function needs to be used instead. + */ +static void usb_audio_transfer_complete(int ep, int dir, int status, int length) { /* normal handler is too slow to handle the completion rate, because * of the low thread schedule rate */ @@ -1268,7 +1327,14 @@ void usb_audio_transfer_complete(int ep, int dir, int status, int length) (void) length; } -bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) +/* + * Called by usb_core_transfer_complete(). + * The normal transfer complete handler system is too slow to deal with + * ISO data at the rate required, so this is required. + * + * Return true if the transfer is handled, false otherwise. + */ +static bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) { (void) dir; bool retval = false; @@ -1428,3 +1494,20 @@ bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) return retval; } + +struct usb_class_driver usb_cdrv_audio = { + .needs_exclusive_storage = false, + .config = 1, + .ep_allocs_size = ARRAYLEN(ep_allocs), + .ep_allocs = ep_allocs, + .set_first_interface = usb_audio_set_first_interface, + .get_config_descriptor = usb_audio_get_config_descriptor, + .init_connection = usb_audio_init_connection, + .init = usb_audio_init, + .disconnect = usb_audio_disconnect, + .transfer_complete = usb_audio_transfer_complete, + .fast_transfer_complete = usb_audio_fast_transfer_complete, + .control_request = usb_audio_control_request, + .set_interface = usb_audio_set_interface, + .get_interface = usb_audio_get_interface, +}; diff --git a/firmware/usbstack/usb_audio.h b/firmware/usbstack/usb_audio.h index b6cd2461d7..a610fbe90a 100644 --- a/firmware/usbstack/usb_audio.h +++ b/firmware/usbstack/usb_audio.h @@ -30,64 +30,6 @@ * Relevant specifications are USB 2.0 and USB Audio Class 1.0. */ -extern struct usb_class_driver_ep_allocation usb_audio_ep_allocs[2]; - -/* - * usb_audio_set_first_interface(): - * - * Required function for the class driver. - * - * Called by allocate_interfaces_and_endpoints() to - * tell the class driver what its first interface number is. - * Returns the number of the interface available for the next - * class driver to use. - * - * We need 2 interfaces, AudioControl and AudioStreaming. - * Return interface+2. - */ -int usb_audio_set_first_interface(int interface); - -/* - * usb_audio_get_config_descriptor(): - * - * Required function for the class driver. - * - * Called by request_handler_device_get_descriptor(), which expects - * this function to fill *dest with the configuration descriptor for this - * class driver. - * - * Return the size of this descriptor in bytes. - */ -int usb_audio_get_config_descriptor(unsigned char *dest,int max_packet_size); - -/* - * usb_audio_init_connection(): - * - * Called by usb_core_do_set_config() when the - * connection is ready to be used. Currently just sets - * the audio sample rate to default. - */ -void usb_audio_init_connection(void); - -/* - * usb_audio_init(): - * - * Initialize the driver. Called by usb_core_init(). - * Currently initializes the sampling frequency values available - * to the AudioStreaming interface. - */ -void usb_audio_init(void); - -/* - * usb_audio_disconnect(): - * - * Called by usb_core_exit() AND usb_core_do_set_config(). - * - * Indicates to the Class driver that the connection is no - * longer active. Currently just calls usb_audio_stop_playback(). - */ -void usb_audio_disconnect(void); - /* * usb_audio_get_playing(): * @@ -103,56 +45,6 @@ bool usb_audio_get_playing(void); */ bool usb_audio_get_alloc_failed(void); -/* - * usb_audio_transfer_complete(): - * - * Dummy function. - * - * The fast_transfer_complete() function needs to be used instead. - */ -void usb_audio_transfer_complete(int ep,int dir, int status, int length); - -/* - * usb_audio_fast_transfer_complete(): - * - * Called by usb_core_transfer_complete(). - * The normal transfer complete handler system is too slow to deal with - * ISO data at the rate required, so this is required. - * - * Return true if the transfer is handled, false otherwise. - */ -bool usb_audio_fast_transfer_complete(int ep,int dir, int status, int length); - -/* - * usb_audio_control_request(): - * - * Called by control_request_handler_drivers(). - * Pass control requests down to the appropriate functions. - * - * Return true if this driver handles the request, false otherwise. - */ -bool usb_audio_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); - -/* - * usb_audio_set_interface(): - * - * Called by control_request_handler_drivers(). - * Deal with changing the interface between control and streaming. - * - * Return 0 for success, -1 otherwise. - */ -int usb_audio_set_interface(int intf, int alt); - -/* - * usb_audio_get_interface(): - * - * Called by control_request_handler_drivers(). - * Get the alternate of the given interface. - * - * Return the alternate of the given interface, -1 if unknown. - */ -int usb_audio_get_interface(int intf); - /* * usb_audio_get_playback_sampling_frequency(): * @@ -266,4 +158,6 @@ int usb_audio_get_cur_volume(void); bool usb_audio_get_active(void); +extern struct usb_class_driver usb_cdrv_audio; + #endif diff --git a/firmware/usbstack/usb_charging_only.c b/firmware/usbstack/usb_charging_only.c index 3fa384cfec..23d285923c 100644 --- a/firmware/usbstack/usb_charging_only.c +++ b/firmware/usbstack/usb_charging_only.c @@ -46,13 +46,13 @@ static struct usb_interface_descriptor __attribute__((aligned(2))) static int usb_interface; -int usb_charging_only_set_first_interface(int interface) +static int usb_charging_only_set_first_interface(int interface) { usb_interface = interface; return interface + 1; } -int usb_charging_only_get_config_descriptor(unsigned char *dest,int max_packet_size) +static int usb_charging_only_get_config_descriptor(unsigned char *dest,int max_packet_size) { (void)max_packet_size; unsigned char *orig_dest = dest; @@ -62,3 +62,10 @@ int usb_charging_only_get_config_descriptor(unsigned char *dest,int max_packet_s return (dest-orig_dest); } + +struct usb_class_driver usb_cdrv_charging_only = { + .needs_exclusive_storage = false, + .config = 1, + .set_first_interface = usb_charging_only_set_first_interface, + .get_config_descriptor = usb_charging_only_get_config_descriptor, +}; diff --git a/firmware/usbstack/usb_charging_only.h b/firmware/usbstack/usb_charging_only.h index 68b60956e2..1c49c9788c 100644 --- a/firmware/usbstack/usb_charging_only.h +++ b/firmware/usbstack/usb_charging_only.h @@ -21,12 +21,8 @@ #ifndef USB_CHARGING_ONLY_H #define USB_CHARGING_ONLY_H -#include "usb_ch9.h" +#include "usb_class_driver.h" -void usb_charging_only_init(void); -int usb_charging_only_set_first_interface(int interface); -int usb_charging_only_get_config_descriptor(unsigned char *dest,int max_packet_size); -bool usb_charging_only_control_request(struct usb_ctrlrequest* req); +extern struct usb_class_driver usb_cdrv_charging_only; #endif - diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index f6b48a3185..3f1dee129c 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -201,141 +201,25 @@ struct ep_alloc_state { static struct ep_alloc_state ep_alloc_states[NUM_CONFIGS][USB_NUM_ENDPOINTS]; -static struct usb_class_driver drivers[USB_NUM_DRIVERS] = +static struct usb_class_driver* drivers[USB_NUM_DRIVERS] = { #ifdef USB_ENABLE_STORAGE - [USB_DRIVER_MASS_STORAGE] = { - .enabled = false, - .needs_exclusive_storage = true, - .config = 1, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = ARRAYLEN(usb_storage_ep_allocs), - .ep_allocs = usb_storage_ep_allocs, - .set_first_interface = usb_storage_set_first_interface, - .get_config_descriptor = usb_storage_get_config_descriptor, - .init_connection = usb_storage_init_connection, - .init = usb_storage_init, - .disconnect = usb_storage_disconnect, - .transfer_complete = usb_storage_transfer_complete, - .control_request = usb_storage_control_request, -#ifdef HAVE_HOTSWAP - .notify_hotswap = usb_storage_notify_hotswap, -#endif - }, + [USB_DRIVER_MASS_STORAGE] = &usb_cdrv_storage, #endif #ifdef USB_ENABLE_SERIAL - [USB_DRIVER_SERIAL] = { - .enabled = false, - .needs_exclusive_storage = false, - .config = 1, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = ARRAYLEN(usb_serial_ep_allocs), - .ep_allocs = usb_serial_ep_allocs, - .set_first_interface = usb_serial_set_first_interface, - .get_config_descriptor = usb_serial_get_config_descriptor, - .init_connection = usb_serial_init_connection, - .init = usb_serial_init, - .disconnect = usb_serial_disconnect, - .transfer_complete = usb_serial_transfer_complete, - .control_request = usb_serial_control_request, -#ifdef HAVE_HOTSWAP - .notify_hotswap = NULL, -#endif - }, + [USB_DRIVER_SERIAL] = &usb_cdrv_serial, #endif #ifdef USB_ENABLE_CHARGING_ONLY - [USB_DRIVER_CHARGING_ONLY] = { - .enabled = false, - .needs_exclusive_storage = false, - .config = 1, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = 0, - .ep_allocs = NULL, - .set_first_interface = usb_charging_only_set_first_interface, - .get_config_descriptor = usb_charging_only_get_config_descriptor, - .init_connection = NULL, - .init = NULL, - .disconnect = NULL, - .transfer_complete = NULL, - .control_request = NULL, -#ifdef HAVE_HOTSWAP - .notify_hotswap = NULL, -#endif - }, + [USB_DRIVER_CHARGING_ONLY] = &usb_cdrv_charging_only, #endif #ifdef USB_ENABLE_HID - [USB_DRIVER_HID] = { - .enabled = false, - .needs_exclusive_storage = false, - .config = 1, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = ARRAYLEN(usb_hid_ep_allocs), - .ep_allocs = usb_hid_ep_allocs, - .set_first_interface = usb_hid_set_first_interface, - .get_config_descriptor = usb_hid_get_config_descriptor, - .init_connection = usb_hid_init_connection, - .init = usb_hid_init, - .disconnect = usb_hid_disconnect, - .transfer_complete = usb_hid_transfer_complete, - .control_request = usb_hid_control_request, -#ifdef HAVE_HOTSWAP - .notify_hotswap = NULL, -#endif - }, + [USB_DRIVER_HID] = &usb_cdrv_hid, #endif #ifdef USB_ENABLE_AUDIO - [USB_DRIVER_AUDIO] = { - .enabled = false, - .needs_exclusive_storage = false, - .config = 1, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = ARRAYLEN(usb_audio_ep_allocs), - .ep_allocs = usb_audio_ep_allocs, - .set_first_interface = usb_audio_set_first_interface, - .get_config_descriptor = usb_audio_get_config_descriptor, - .init_connection = usb_audio_init_connection, - .init = usb_audio_init, - .disconnect = usb_audio_disconnect, - .transfer_complete = usb_audio_transfer_complete, - .fast_transfer_complete = usb_audio_fast_transfer_complete, - .control_request = usb_audio_control_request, -#ifdef HAVE_HOTSWAP - .notify_hotswap = NULL, -#endif - .set_interface = usb_audio_set_interface, - .get_interface = usb_audio_get_interface, - }, + [USB_DRIVER_AUDIO] = &usb_cdrv_audio, #endif #ifdef USB_ENABLE_IAP - [USB_DRIVER_IAP] = { - .enabled = false, - .needs_exclusive_storage = false, - .config = 2, - .first_interface = 0, - .last_interface = 0, - .ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs), - .ep_allocs = usb_iap_ep_allocs, - .set_first_interface = usb_iap_set_first_interface, - .get_config_descriptor = usb_iap_get_config_descriptor, - .init_connection = usb_iap_init_connection, - .init = usb_iap_init, - .disconnect = usb_iap_disconnect, - .transfer_complete = usb_iap_transfer_complete, - .fast_transfer_complete = usb_iap_fast_transfer_complete, - .control_request = usb_iap_control_request, -#ifdef HAVE_HOTSWAP - .notify_hotswap = NULL, -#endif - .set_interface = usb_iap_set_interface, - .get_interface = usb_iap_get_interface, - .get_max_packet_size = usb_iap_get_max_packet_size, - .notify_event = usb_iap_notify_event, - }, + [USB_DRIVER_IAP] = &usb_cdrv_iap, #endif }; @@ -352,8 +236,8 @@ static void usb_core_control_request_handler(struct usb_ctrlrequest* req, void* static unsigned char response_data[256] USB_DEVBSS_ATTR; -#define is_active(driver) ((driver).enabled && (driver).config == usb_config) -#define has_if(driver, interface) ((interface) >= (driver).first_interface && (interface) < (driver).last_interface) +#define is_active(driver) ((driver)->enabled && (driver)->config == usb_config) +#define has_if(driver, interface) ((interface) >= (driver)->first_interface && (interface) < (driver)->last_interface) /** NOTE Serial Number * The serial number string is split into two parts: @@ -513,9 +397,14 @@ void usb_core_init(void) /* class driver init functions should be safe to call even if the driver * won't be used. This simplifies other logic (i.e. we don't need to know * yet which drivers will be enabled */ - for(i = 0; i < USB_NUM_DRIVERS; i++) - if(drivers[i].init != NULL) - drivers[i].init(); + for(i = 0; i < USB_NUM_DRIVERS; i++) { + drivers[i]->enabled = false; + drivers[i]->first_interface = 0; + drivers[i]->last_interface = 0; + if(drivers[i]->init != NULL) { + drivers[i]->init(); + } + } /* clear endpoint allocation state */ memset(ep_alloc_states, 0, sizeof(ep_alloc_states)); @@ -570,12 +459,12 @@ void usb_core_handle_transfer_completion( void usb_core_enable_driver(int driver, bool enabled) { - drivers[driver].enabled = enabled; + drivers[driver]->enabled = enabled; } bool usb_core_driver_enabled(int driver) { - return drivers[driver].enabled; + return drivers[driver]->enabled; } #ifdef HAVE_HOTSWAP @@ -583,8 +472,8 @@ void usb_core_hotswap_event(int volume, bool inserted) { int i; for(i = 0; i < USB_NUM_DRIVERS; i++) - if(drivers[i].enabled && drivers[i].notify_hotswap != NULL) - drivers[i].notify_hotswap(volume, inserted); + if(drivers[i]->enabled && drivers[i]->notify_hotswap != NULL) + drivers[i]->notify_hotswap(volume, inserted); } #endif @@ -651,7 +540,7 @@ static void usb_core_set_serial_function_id(void) int i, id = 0; for(i = 0; i < USB_NUM_DRIVERS; i++) - if(drivers[i].enabled) + if(drivers[i]->enabled) id |= 1 << i; usb_string_iSerial.wString[0] = hex[id]; @@ -700,7 +589,7 @@ static void allocate_interfaces_and_endpoints(void) int interface[NUM_CONFIGS] = {0}; for(int i = 0; i < USB_NUM_DRIVERS; i++) { - struct usb_class_driver* driver = &drivers[i]; + struct usb_class_driver* driver = drivers[i]; const uint8_t conf_index = driver->config - 1; if(!driver->enabled) { @@ -785,8 +674,8 @@ static void control_request_handler_drivers(struct usb_ctrlrequest* req, void* r bool handled = false; for(i = 0; i < USB_NUM_DRIVERS; i++) { - struct usb_class_driver* driver = &drivers[i]; - if(!is_active(*driver) || !has_if(*driver, interface) || driver->control_request == NULL) { + struct usb_class_driver* driver = drivers[i]; + if(!is_active(driver) || !has_if(driver, interface) || driver->control_request == NULL) { continue; } @@ -804,8 +693,8 @@ static void control_request_handler_drivers(struct usb_ctrlrequest* req, void* r int alt = -1; logf("usb_core: GET INTERFACE 0x%x", req->wIndex); - if(drivers[i].get_interface) - alt = drivers[i].get_interface(req->wIndex); + if(driver->get_interface) + alt = driver->get_interface(req->wIndex); if(alt >= 0 && alt < 255) { response_data[0] = alt; @@ -870,8 +759,8 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, v size = sizeof(struct usb_config_descriptor); for(i = 0; i < USB_NUM_DRIVERS; i++) { - if(drivers[i].enabled && drivers[i].config == index + 1 && drivers[i].get_config_descriptor) { - size += drivers[i].get_config_descriptor(&response_data[size], max_packet_size); + if(drivers[i]->enabled && drivers[i]->config == index + 1 && drivers[i]->get_config_descriptor) { + size += drivers[i]->get_config_descriptor(&response_data[size], max_packet_size); } } @@ -945,8 +834,8 @@ static int usb_core_do_set_config(uint8_t new_config) /* deactivate old config */ if(usb_config != 0) { for(int i = 0; i < USB_NUM_DRIVERS; i++) { - if(is_active(drivers[i]) && drivers[i].disconnect != NULL) { - drivers[i].disconnect(); + if(is_active(drivers[i]) && drivers[i]->disconnect != NULL) { + drivers[i]->disconnect(); } } init_deinit_endpoints(usb_config - 1, false); @@ -967,9 +856,9 @@ static int usb_core_do_set_config(uint8_t new_config) if(usb_config != 0) { init_deinit_endpoints(usb_config - 1, true); for(int i = 0; i < USB_NUM_DRIVERS; i++) { - if(is_active(drivers[i]) && drivers[i].init_connection != NULL) { - drivers[i].init_connection(); - require_exclusive |= drivers[i].needs_exclusive_storage; + if(is_active(drivers[i]) && drivers[i]->init_connection != NULL) { + drivers[i]->init_connection(); + require_exclusive |= drivers[i]->needs_exclusive_storage; } } } @@ -1302,8 +1191,8 @@ void usb_core_handle_notify(long id, intptr_t data) logf("usb_core: invalid notification destination index=%u", index); return; } - if(is_active(drivers[index]) && drivers[index].notify_event != NULL) { - drivers[index].notify_event(data & 0x00ffffff); + if(is_active(drivers[index]) && drivers[index]->notify_event != NULL) { + drivers[index]->notify_event(data & 0x00ffffff); } } break; default: diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c index ee99e29fdc..3b231cfa8c 100644 --- a/firmware/usbstack/usb_hid.c +++ b/firmware/usbstack/usb_hid.c @@ -121,11 +121,11 @@ static bool active = false; static bool currently_sending = false; static int usb_interface; -struct usb_class_driver_ep_allocation usb_hid_ep_allocs[1] = { +static struct usb_class_driver_ep_allocation ep_allocs[1] = { {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false}, }; -#define EP_IN (usb_hid_ep_allocs[0].ep) +#define EP_IN (ep_allocs[0].ep) static void usb_hid_try_send_drv(void); @@ -173,7 +173,7 @@ static void pack_parameter(unsigned char **dest, bool is_signed, bool mark_size, } } -int usb_hid_set_first_interface(int interface) +static int usb_hid_set_first_interface(int interface) { usb_interface = interface; @@ -519,7 +519,7 @@ static void descriptor_hid_get(unsigned char **dest) PACK_DATA(dest, hid_descriptor); } -int usb_hid_get_config_descriptor(unsigned char *dest, int max_packet_size) +static int usb_hid_get_config_descriptor(unsigned char *dest, int max_packet_size) { (void)max_packet_size; @@ -543,7 +543,7 @@ int usb_hid_get_config_descriptor(unsigned char *dest, int max_packet_size) return (int)(dest - orig_dest); } -void usb_hid_init_connection(void) +static void usb_hid_init_connection(void) { logf("hid: init connection"); active = true; @@ -551,7 +551,7 @@ void usb_hid_init_connection(void) } /* called by usb_core_init() */ -void usb_hid_init(void) +static void usb_hid_init(void) { logf("hid: init"); @@ -565,7 +565,7 @@ void usb_hid_init(void) currently_sending = false; } -void usb_hid_disconnect(void) +static void usb_hid_disconnect(void) { logf("hid: disconnect"); active = false; @@ -573,7 +573,7 @@ void usb_hid_disconnect(void) } /* called by usb_core_transfer_complete() */ -void usb_hid_transfer_complete(int ep, int dir, int status, int length) +static void usb_hid_transfer_complete(int ep, int dir, int status, int length) { (void)ep; (void)length; @@ -678,7 +678,7 @@ static int usb_hid_get_report(struct usb_ctrlrequest *req, unsigned char* dest) } /* called by usb_core_control_request() */ -bool usb_hid_control_request(struct usb_ctrlrequest *req, void *reqdata, unsigned char *dest) +static bool usb_hid_control_request(struct usb_ctrlrequest *req, void *reqdata, unsigned char *dest) { (void)reqdata; @@ -828,3 +828,17 @@ void usb_hid_send(usage_page_t usage_page, int id) usb_hid_try_send_drv(); } + +struct usb_class_driver usb_cdrv_hid = { + .needs_exclusive_storage = false, + .config = 1, + .ep_allocs_size = ARRAYLEN(ep_allocs), + .ep_allocs = ep_allocs, + .set_first_interface = usb_hid_set_first_interface, + .get_config_descriptor = usb_hid_get_config_descriptor, + .init_connection = usb_hid_init_connection, + .init = usb_hid_init, + .disconnect = usb_hid_disconnect, + .transfer_complete = usb_hid_transfer_complete, + .control_request = usb_hid_control_request, +}; diff --git a/firmware/usbstack/usb_hid.h b/firmware/usbstack/usb_hid.h index 379b17d2bf..7a62b4e353 100644 --- a/firmware/usbstack/usb_hid.h +++ b/firmware/usbstack/usb_hid.h @@ -25,17 +25,8 @@ #include "usb_class_driver.h" #include "usb_hid_usage_tables.h" -extern struct usb_class_driver_ep_allocation usb_hid_ep_allocs[1]; - -int usb_hid_set_first_interface(int interface); -int usb_hid_get_config_descriptor(unsigned char *dest, int max_packet_size); -void usb_hid_init_connection(void); -void usb_hid_init(void); -void usb_hid_disconnect(void); -void usb_hid_transfer_complete(int ep, int dir, int status, int length); -bool usb_hid_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); - void usb_hid_send(usage_page_t usage_page, int id); -#endif +extern struct usb_class_driver usb_cdrv_hid; +#endif diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index bf6889e66e..99492f791b 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -23,6 +23,7 @@ #include "playback.h" #include "powermgmt.h" #include "timefuncs.h" +#include "usb_core.h" #include "usb_drv.h" #include "panic.h" @@ -320,7 +321,7 @@ static int tick_callback(struct timeout* tmo) { return HZ / 10; } -int usb_iap_set_first_interface(int interface) { +static int usb_iap_set_first_interface(int interface) { ctrl.interface = interface + 0; stream.interface = interface + 1; hid.interface = interface + 2; @@ -329,7 +330,7 @@ int usb_iap_set_first_interface(int interface) { #define PACK_DESC(desc) pack_data(&dest, &desc, ((struct usb_descriptor_header*)&desc)->bLength) -int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) { +static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) { (void)max_packet_size; unsigned char* orig_dest = dest; @@ -372,7 +373,7 @@ int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) { return dest - orig_dest; } -void usb_iap_init_connection(void) { +static void usb_iap_init_connection(void) { stream.sample_rate = 48000; last_charge_state = -1; last_minute = -1; @@ -422,7 +423,7 @@ cleanup_audio: iap_audio_deinit(); } -int usb_iap_set_interface(int intf, int alt) { +static int usb_iap_set_interface(int intf, int alt) { LOG("set interface interface=%d alt=%d", intf, alt); check_act(intf == stream.interface, return -1); if(alt == 0) { @@ -436,13 +437,13 @@ int usb_iap_set_interface(int intf, int alt) { return 0; } -int usb_iap_get_interface(int intf) { +static int usb_iap_get_interface(int intf) { LOG("get interface interface=%d", intf); check_act(intf == stream.interface, return -1); return stream.alt; } -int usb_iap_get_max_packet_size(int ep) { +static int usb_iap_get_max_packet_size(int ep) { if(ep == AS_EP_IN) { return 1024; } else if(ep == HID_EP_IN) { @@ -453,11 +454,11 @@ int usb_iap_get_max_packet_size(int ep) { } } -void usb_iap_init(void) { +static void usb_iap_init(void) { LOG("init"); } -void usb_iap_disconnect(void) { +static void usb_iap_disconnect(void) { iap_initialized = false; audio_pause(); mixer_switch_sink(PCM_SINK_BUILTIN); @@ -472,7 +473,7 @@ void usb_iap_disconnect(void) { LOG("disconnected"); } -void usb_iap_transfer_complete(int ep, int dir, int status, int length) { +static void usb_iap_transfer_complete(int ep, int dir, int status, int length) { (void)length; if((ep | dir) == HID_EP_IN) { @@ -486,7 +487,7 @@ void usb_iap_transfer_complete(int ep, int dir, int status, int length) { } } -bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length) { +static bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length) { (void)status; (void)length; return (ep | dir) == AS_EP_IN; @@ -634,7 +635,7 @@ static bool control_request_if_endpoint(struct usb_ctrlrequest* req, void* reqda return false; } -bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { +static bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { const uint8_t req_recipient = req->bRequestType & USB_RECIP_MASK; const uint8_t req_type = req->bRequestType & USB_TYPE_MASK; #if 0 @@ -651,7 +652,7 @@ bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigne return false; } -void usb_iap_notify_event(intptr_t data) { +static void usb_iap_notify_event(intptr_t data) { switch(data) { case Notify_Tick: { struct IAPContext* ctx = _iap_acquire_ctx(true); @@ -689,3 +690,22 @@ void usb_iap_notify_event(intptr_t data) { } break; } } + +struct usb_class_driver usb_cdrv_iap = { + .needs_exclusive_storage = false, + .config = 2, + .ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs), + .ep_allocs = usb_iap_ep_allocs, + .set_first_interface = usb_iap_set_first_interface, + .get_config_descriptor = usb_iap_get_config_descriptor, + .init_connection = usb_iap_init_connection, + .init = usb_iap_init, + .disconnect = usb_iap_disconnect, + .transfer_complete = usb_iap_transfer_complete, + .fast_transfer_complete = usb_iap_fast_transfer_complete, + .control_request = usb_iap_control_request, + .set_interface = usb_iap_set_interface, + .get_interface = usb_iap_get_interface, + .get_max_packet_size = usb_iap_get_max_packet_size, + .notify_event = usb_iap_notify_event, +}; diff --git a/firmware/usbstack/usb_iap.h b/firmware/usbstack/usb_iap.h index c1990bc8d3..dcf1336f2d 100644 --- a/firmware/usbstack/usb_iap.h +++ b/firmware/usbstack/usb_iap.h @@ -18,7 +18,6 @@ * ****************************************************************************/ #pragma once -#include "usb_core.h" #include "usb_class_driver.h" /* [2] P.32 Table 2-8 USB Device Vendor Request to set available current from accessory (USB Device Mode only) */ @@ -26,18 +25,4 @@ extern struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2]; -int usb_iap_request_endpoints(struct usb_class_driver*); -int usb_iap_set_first_interface(int interface); -int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size); -void usb_iap_init_connection(void); -bool usb_iap_set_alt_interface(int interface, int alt); -int usb_iap_get_alt_interface(int interface); -void usb_iap_init(void); -void usb_iap_disconnect(void); -void usb_iap_transfer_complete(int ep, int dir, int state, int length); -bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length); -bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); -int usb_iap_set_interface(int intf, int alt); -int usb_iap_get_interface(int intf); -int usb_iap_get_max_packet_size(int ep); -void usb_iap_notify_event(intptr_t data); +extern struct usb_class_driver usb_cdrv_iap; diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index 5c3b084b4b..80aaaaef5d 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -206,26 +206,26 @@ static int buffer_length; static int buffer_transitlength; static bool active = false; -struct usb_class_driver_ep_allocation usb_serial_ep_allocs[3] = { +static struct usb_class_driver_ep_allocation ep_allocs[3] = { {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false}, {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false}, {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = true}, }; -#define EP_IN (usb_serial_ep_allocs[0].ep) -#define EP_OUT (usb_serial_ep_allocs[1].ep) -#define EP_INT (usb_serial_ep_allocs[2].ep) +#define EP_IN (ep_allocs[0].ep) +#define EP_OUT (ep_allocs[1].ep) +#define EP_INT (ep_allocs[2].ep) static int control_interface, data_interface; -int usb_serial_set_first_interface(int interface) +static int usb_serial_set_first_interface(int interface) { control_interface = interface; data_interface = interface + 1; return interface + 2; } -int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size) +static int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size) { unsigned char *orig_dest = dest; @@ -270,7 +270,7 @@ int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size) } /* called by usb_core_control_request() */ -bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) +static bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { bool handled = false; @@ -329,7 +329,7 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsi return handled; } -void usb_serial_init_connection(void) +static void usb_serial_init_connection(void) { /* prime rx endpoint */ usb_drv_recv_nonblocking(EP_OUT, receive_buffer, RECV_BUFFER_SIZE); @@ -344,7 +344,7 @@ void usb_serial_init_connection(void) } /* called by usb_code_init() */ -void usb_serial_init(void) +static void usb_serial_init(void) { logf("serial: init"); buffer_start = 0; @@ -352,7 +352,7 @@ void usb_serial_init(void) buffer_transitlength = 0; } -void usb_serial_disconnect(void) +static void usb_serial_disconnect(void) { active = false; } @@ -412,7 +412,7 @@ void usb_serial_send(const unsigned char *data,int length) } /* called by usb_core_transfer_complete() */ -void usb_serial_transfer_complete(int ep,int dir, int status, int length) +static void usb_serial_transfer_complete(int ep,int dir, int status, int length) { (void)ep; (void)length; @@ -441,3 +441,17 @@ void usb_serial_transfer_complete(int ep,int dir, int status, int length) break; } } + +struct usb_class_driver usb_cdrv_serial = { + .needs_exclusive_storage = false, + .config = 1, + .ep_allocs_size = ARRAYLEN(ep_allocs), + .ep_allocs = ep_allocs, + .set_first_interface = usb_serial_set_first_interface, + .get_config_descriptor = usb_serial_get_config_descriptor, + .init_connection = usb_serial_init_connection, + .init = usb_serial_init, + .disconnect = usb_serial_disconnect, + .transfer_complete = usb_serial_transfer_complete, + .control_request = usb_serial_control_request, +}; diff --git a/firmware/usbstack/usb_serial.h b/firmware/usbstack/usb_serial.h index e78273cb8e..a71e91b920 100644 --- a/firmware/usbstack/usb_serial.h +++ b/firmware/usbstack/usb_serial.h @@ -23,17 +23,8 @@ #include "usb_class_driver.h" -extern struct usb_class_driver_ep_allocation usb_serial_ep_allocs[3]; - -int usb_serial_set_first_interface(int interface); -int usb_serial_get_config_descriptor(unsigned char *dest,int max_packet_size); -void usb_serial_init_connection(void); -void usb_serial_init(void); -void usb_serial_disconnect(void); -void usb_serial_transfer_complete(int ep,int dir, int status, int length); -bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char *dest); - void usb_serial_send(const unsigned char *data, int length); -#endif +extern struct usb_class_driver usb_cdrv_serial; +#endif diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index 4aadf1e0f1..17677c44cd 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -322,13 +322,13 @@ static bool locked[NUM_DRIVES]; static int usb_interface; -struct usb_class_driver_ep_allocation usb_storage_ep_allocs[2] = { +static struct usb_class_driver_ep_allocation ep_allocs[2] = { {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false}, {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false}, }; -#define EP_IN (usb_storage_ep_allocs[0].ep) -#define EP_OUT (usb_storage_ep_allocs[1].ep) +#define EP_IN (ep_allocs[0].ep) +#define EP_OUT (ep_allocs[1].ep) #if defined(HAVE_MULTIDRIVE) static bool skip_first = 0; @@ -378,7 +378,7 @@ static bool check_disk_present(IF_MD_NONVOID(int volume)) } #ifdef HAVE_HOTSWAP -void usb_storage_notify_hotswap(int volume,bool inserted) +static void usb_storage_notify_hotswap(int volume,bool inserted) { logf("notify %d",inserted); if(inserted && check_disk_present(IF_MD(volume))) { @@ -401,18 +401,18 @@ void usb_set_skip_first_drive(bool skip) #endif /* called by usb_core_init() */ -void usb_storage_init(void) +static void usb_storage_init(void) { logf("usb_storage_init done"); } -int usb_storage_set_first_interface(int interface) +static int usb_storage_set_first_interface(int interface) { usb_interface = interface; return interface + 1; } -int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_size) +static int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_size) { unsigned char *orig_dest = dest; @@ -436,7 +436,7 @@ int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_size) #else static int usb_handle = 0; #endif -void usb_storage_init_connection(void) +static void usb_storage_init_connection(void) { logf("ums: set config"); /* prime rx endpoint. We only need room for commands */ @@ -493,7 +493,7 @@ void usb_storage_disconnect(void) } /* called by usb_core_transfer_complete() */ -void usb_storage_transfer_complete(int ep,int dir,int status,int length) +static void usb_storage_transfer_complete(int ep,int dir,int status,int length) { (void)ep; struct command_block_wrapper* cbw = (void*)cbw_buffer; @@ -694,7 +694,7 @@ static void usb_storage_send_smart(uint8_t cmd) #endif /* STORAGE_ATA */ /* called by usb_core_control_request() */ -bool usb_storage_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) +static bool usb_storage_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) { bool handled = false; @@ -1479,3 +1479,20 @@ static void fill_inquiry(IF_MD_NONVOID(int lun)) tb.inquiry->DeviceTypeModifier = DEVICE_REMOVABLE; } + +struct usb_class_driver usb_cdrv_storage = { + .needs_exclusive_storage = true, + .config = 1, + .ep_allocs_size = ARRAYLEN(ep_allocs), + .ep_allocs = ep_allocs, + .set_first_interface = usb_storage_set_first_interface, + .get_config_descriptor = usb_storage_get_config_descriptor, + .init_connection = usb_storage_init_connection, + .init = usb_storage_init, + .disconnect = usb_storage_disconnect, + .transfer_complete = usb_storage_transfer_complete, + .control_request = usb_storage_control_request, +#ifdef HAVE_HOTSWAP + .notify_hotswap = usb_storage_notify_hotswap, +#endif +}; diff --git a/firmware/usbstack/usb_storage.h b/firmware/usbstack/usb_storage.h index 8dbe965882..16bc172ce5 100644 --- a/firmware/usbstack/usb_storage.h +++ b/firmware/usbstack/usb_storage.h @@ -23,18 +23,6 @@ #include "usb_class_driver.h" -extern struct usb_class_driver_ep_allocation usb_storage_ep_allocs[2]; - -int usb_storage_set_first_interface(int interface); -int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_size); -void usb_storage_init_connection(void); -void usb_storage_disconnect(void); -void usb_storage_init(void); -void usb_storage_transfer_complete(int ep,int dir,int state,int length); -bool usb_storage_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); -#ifdef HAVE_HOTSWAP -void usb_storage_notify_hotswap(int volume,bool inserted); -#endif - +extern struct usb_class_driver usb_cdrv_storage; #endif From 732f7bcfd96f64f481f3a94a62a7c50087836e31 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 21:29:21 +0900 Subject: [PATCH 63/88] usb: allow class drivers to return error from init_connection() Change-Id: Idcd48dd00d9d2f06c49a347e02a41a5de7252431 --- firmware/usbstack/usb_audio.c | 6 +++--- firmware/usbstack/usb_class_driver.h | 10 +++++++++- firmware/usbstack/usb_core.c | 15 ++++++++++----- firmware/usbstack/usb_hid.c | 3 ++- firmware/usbstack/usb_iap.c | 9 ++++----- firmware/usbstack/usb_serial.c | 3 ++- firmware/usbstack/usb_storage.c | 3 ++- 7 files changed, 32 insertions(+), 17 deletions(-) diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index 9dc96579e7..fad3d12b8c 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -1204,14 +1204,13 @@ static bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata * connection is ready to be used. Currently just sets * the audio sample rate to default. */ -static void usb_audio_init_connection(void) +static int usb_audio_init_connection(void) { logf("usbaudio: init connection"); // make sure we can get the buffers first... - // TODO: disable this driver when failed if (usb_audio_request_buf()) - return; + return -1; usbaudio_active = true; dsp = dsp_get_config(CODEC_IDX_AUDIO); @@ -1227,6 +1226,7 @@ static void usb_audio_init_connection(void) set_playback_sampling_frequency(HW_SAMPR_DEFAULT); tmp_saved_vol = sound_current(SOUND_VOLUME); usb_audio_playing = false; + return 0; } /* diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index dc19c31ad5..5d63ce0b89 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -38,7 +38,14 @@ struct usb_class_driver_ep_allocation { struct usb_class_driver { /* First some runtime data */ + + /* there are three possible runtime states for class driver: + * !enabled -> disabled, invisible from host + * enabled && !error -> enabled, healthy + * enabled && error -> recognized by host, but failed */ bool enabled; + bool error; + int first_interface; int last_interface; @@ -69,8 +76,9 @@ struct usb_class_driver { /* Tells the driver that a usb connection has been set up and is now ready to use. + Returns 0 on success and -1 on error. Optional function */ - void (*init_connection)(void); + int (*init_connection)(void); /* Initialises the driver. This can be called multiple times, and should not perform any action that can disturb other threads diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 3f1dee129c..814d502242 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -236,7 +236,7 @@ static void usb_core_control_request_handler(struct usb_ctrlrequest* req, void* static unsigned char response_data[256] USB_DEVBSS_ATTR; -#define is_active(driver) ((driver)->enabled && (driver)->config == usb_config) +#define is_active(driver) ((driver)->enabled && !(driver)->error && (driver)->config == usb_config) #define has_if(driver, interface) ((interface) >= (driver)->first_interface && (interface) < (driver)->last_interface) /** NOTE Serial Number @@ -399,6 +399,7 @@ void usb_core_init(void) * yet which drivers will be enabled */ for(i = 0; i < USB_NUM_DRIVERS; i++) { drivers[i]->enabled = false; + drivers[i]->error = false; drivers[i]->first_interface = 0; drivers[i]->last_interface = 0; if(drivers[i]->init != NULL) { @@ -472,7 +473,7 @@ void usb_core_hotswap_event(int volume, bool inserted) { int i; for(i = 0; i < USB_NUM_DRIVERS; i++) - if(drivers[i]->enabled && drivers[i]->notify_hotswap != NULL) + if(is_active(drivers[i]) && drivers[i]->notify_hotswap != NULL) drivers[i]->notify_hotswap(volume, inserted); } #endif @@ -856,10 +857,14 @@ static int usb_core_do_set_config(uint8_t new_config) if(usb_config != 0) { init_deinit_endpoints(usb_config - 1, true); for(int i = 0; i < USB_NUM_DRIVERS; i++) { - if(is_active(drivers[i]) && drivers[i]->init_connection != NULL) { - drivers[i]->init_connection(); - require_exclusive |= drivers[i]->needs_exclusive_storage; + if(!is_active(drivers[i])) { + continue; } + if(drivers[i]->init_connection != NULL && drivers[i]->init_connection() < 0) { + drivers[i]->error = true; + continue; + } + require_exclusive |= drivers[i]->needs_exclusive_storage; } } diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c index 3b231cfa8c..ac06691df4 100644 --- a/firmware/usbstack/usb_hid.c +++ b/firmware/usbstack/usb_hid.c @@ -543,11 +543,12 @@ static int usb_hid_get_config_descriptor(unsigned char *dest, int max_packet_siz return (int)(dest - orig_dest); } -static void usb_hid_init_connection(void) +static int usb_hid_init_connection(void) { logf("hid: init connection"); active = true; currently_sending = false; + return 0; } /* called by usb_core_init() */ diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index 99492f791b..2d6d3d3022 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -373,7 +373,7 @@ static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_siz return dest - orig_dest; } -static void usb_iap_init_connection(void) { +static int usb_iap_init_connection(void) { stream.sample_rate = 48000; last_charge_state = -1; last_minute = -1; @@ -381,10 +381,8 @@ static void usb_iap_init_connection(void) { iap_debug_reset_timestamp(); - /* TODO: disable iap on error */ - /* init audio sink */ - check_act(iap_audio_init(), return); + check_act(iap_audio_init(), return -1); /* init libiap */ if(!iap_ctx_mutex_initialized) { @@ -416,11 +414,12 @@ static void usb_iap_init_connection(void) { iap_initialized = true; LOG("initialized"); - return; + return 0; cleanup_audio: _iap_release_ctx(); iap_audio_deinit(); + return -1; } static int usb_iap_set_interface(int intf, int alt) { diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index 80aaaaef5d..5490d1db11 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -329,7 +329,7 @@ static bool usb_serial_control_request(struct usb_ctrlrequest* req, void* reqdat return handled; } -static void usb_serial_init_connection(void) +static int usb_serial_init_connection(void) { /* prime rx endpoint */ usb_drv_recv_nonblocking(EP_OUT, receive_buffer, RECV_BUFFER_SIZE); @@ -341,6 +341,7 @@ static void usb_serial_init_connection(void) sendout(); } active=true; + return 0; } /* called by usb_code_init() */ diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index 17677c44cd..c4bdbf9841 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -436,7 +436,7 @@ static int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_ #else static int usb_handle = 0; #endif -static void usb_storage_init_connection(void) +static int usb_storage_init_connection(void) { logf("ums: set config"); /* prime rx endpoint. We only need room for commands */ @@ -483,6 +483,7 @@ static void usb_storage_init_connection(void) ejected[i] = !check_disk_present(IF_MD(i)); queue_broadcast(SYS_USB_LUN_LOCKED, (i<<16)+0); } + return 0; } void usb_storage_disconnect(void) From bbdada76900bf76adbc12d7751bc318dfeb48207 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 22:45:47 +0900 Subject: [PATCH 64/88] usb: enable cpu boost for specific class drivers add needs_cpu_boost field to usb_class_driver and manage boost state in usb_core, similar to needs_exclusive_storage. Change-Id: Ieb0cd7bedda5b24bb0d209d5ce907db30f4815db --- firmware/usb.c | 9 --------- firmware/usbstack/usb_audio.c | 1 + firmware/usbstack/usb_charging_only.c | 1 + firmware/usbstack/usb_class_driver.h | 3 +++ firmware/usbstack/usb_core.c | 14 ++++++++++++++ firmware/usbstack/usb_hid.c | 1 + firmware/usbstack/usb_iap.c | 1 + firmware/usbstack/usb_serial.c | 1 + firmware/usbstack/usb_storage.c | 1 + 9 files changed, 23 insertions(+), 9 deletions(-) diff --git a/firmware/usb.c b/firmware/usb.c index ae9778c299..406a635140 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -266,23 +266,14 @@ static inline void usb_slave_mode(bool on) if(on) { - trigger_cpu_boost(); -#ifdef HAVE_PRIORITY_SCHEDULING - thread_set_priority(thread_self(), PRIORITY_REALTIME); -#endif disk_unmount_all(); } else { -#ifdef HAVE_PRIORITY_SCHEDULING - thread_set_priority(thread_self(), PRIORITY_SYSTEM); -#endif /* Entered exclusive mode */ rc = disk_mount_all(); if(rc <= 0) /* no partition */ panicf("mount: %d",rc); - - cancel_cpu_boost(); } } diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index fad3d12b8c..aceb3bf5a5 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -1497,6 +1497,7 @@ static bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int le struct usb_class_driver usb_cdrv_audio = { .needs_exclusive_storage = false, + .needs_cpu_boost = false, .config = 1, .ep_allocs_size = ARRAYLEN(ep_allocs), .ep_allocs = ep_allocs, diff --git a/firmware/usbstack/usb_charging_only.c b/firmware/usbstack/usb_charging_only.c index 23d285923c..66683593db 100644 --- a/firmware/usbstack/usb_charging_only.c +++ b/firmware/usbstack/usb_charging_only.c @@ -65,6 +65,7 @@ static int usb_charging_only_get_config_descriptor(unsigned char *dest,int max_p struct usb_class_driver usb_cdrv_charging_only = { .needs_exclusive_storage = false, + .needs_cpu_boost = false, .config = 1, .set_first_interface = usb_charging_only_set_first_interface, .get_config_descriptor = usb_charging_only_get_config_descriptor, diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index 5d63ce0b89..a48d19ac62 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -54,6 +54,9 @@ struct usb_class_driver { /* Set this to true if the driver needs exclusive disk access (e.g. usb storage) */ bool needs_exclusive_storage; + /* Set this to true if the driver needs to enable cpu boost */ + bool needs_cpu_boost; + /* USB config number this driver belongs to */ uint8_t config; diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 814d502242..8b7e4510fd 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -823,6 +823,11 @@ static void usb_core_do_set_addr(uint8_t address) usb_state = ADDRESS; } +#if !defined(HAVE_PRIORITY_SCHEDULING) +#define thread_set_priority(...) +#define thread_get_priority(...) +#endif /* HAVE_PRIORITY_SCHEDULING */ + static int usb_core_do_set_config(uint8_t new_config) { logf("usb_core: SET_CONFIG %d to %d", usb_config, new_config); @@ -852,6 +857,7 @@ static int usb_core_do_set_config(uint8_t new_config) usb_state = usb_config == 0 ? ADDRESS : CONFIGURED; bool require_exclusive = false; + bool require_cpu_boost = false; /* activate new config */ if(usb_config != 0) { @@ -865,6 +871,7 @@ static int usb_core_do_set_config(uint8_t new_config) continue; } require_exclusive |= drivers[i]->needs_exclusive_storage; + require_cpu_boost |= drivers[i]->needs_cpu_boost; } } @@ -876,6 +883,13 @@ static int usb_core_do_set_config(uint8_t new_config) } else { usb_release_exclusive_storage(); } + if(require_cpu_boost) { + trigger_cpu_boost(); + thread_set_priority(thread_self(), PRIORITY_REALTIME); + } else { + thread_set_priority(thread_self(), PRIORITY_SYSTEM); + cancel_cpu_boost(); + } #ifdef HAVE_USB_CHARGING_ENABLE usb_charging_maxcurrent_change(usb_charging_maxcurrent()); diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c index ac06691df4..fef3ef45aa 100644 --- a/firmware/usbstack/usb_hid.c +++ b/firmware/usbstack/usb_hid.c @@ -832,6 +832,7 @@ void usb_hid_send(usage_page_t usage_page, int id) struct usb_class_driver usb_cdrv_hid = { .needs_exclusive_storage = false, + .needs_cpu_boost = false, .config = 1, .ep_allocs_size = ARRAYLEN(ep_allocs), .ep_allocs = ep_allocs, diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index 2d6d3d3022..df847fc8ad 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -692,6 +692,7 @@ static void usb_iap_notify_event(intptr_t data) { struct usb_class_driver usb_cdrv_iap = { .needs_exclusive_storage = false, + .needs_cpu_boost = true, .config = 2, .ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs), .ep_allocs = usb_iap_ep_allocs, diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index 5490d1db11..d23df7f19b 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -445,6 +445,7 @@ static void usb_serial_transfer_complete(int ep,int dir, int status, int length) struct usb_class_driver usb_cdrv_serial = { .needs_exclusive_storage = false, + .needs_cpu_boost = false, .config = 1, .ep_allocs_size = ARRAYLEN(ep_allocs), .ep_allocs = ep_allocs, diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index c4bdbf9841..055668a56f 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -1483,6 +1483,7 @@ static void fill_inquiry(IF_MD_NONVOID(int lun)) struct usb_class_driver usb_cdrv_storage = { .needs_exclusive_storage = true, + .needs_cpu_boost = true, .config = 1, .ep_allocs_size = ARRAYLEN(ep_allocs), .ep_allocs = ep_allocs, From 02638c1cb8f0a226de877139de83e656789f36db Mon Sep 17 00:00:00 2001 From: Vencislav Atanasov Date: Mon, 4 May 2026 02:51:44 +0300 Subject: [PATCH 65/88] s5l87xx: (Re)name the SHA-1 registers Synced with the s5l8702-sha1 Linux driver in the freemyipod fork Change-Id: I5243346205f7883f4271d4b272936dd125adb496 --- apps/plugins/crypt_firmware.c | 12 +++---- firmware/export/s5l87xx.h | 38 +++++++++++++------- firmware/target/arm/s5l8702/crypto-s5l8702.c | 24 ++++++------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/apps/plugins/crypt_firmware.c b/apps/plugins/crypt_firmware.c index 0d632befdb..3ccab090f5 100644 --- a/apps/plugins/crypt_firmware.c +++ b/apps/plugins/crypt_firmware.c @@ -103,18 +103,18 @@ static void aes_decrypt(void* data, uint32_t size) static void calc_hash(uint32_t* data, uint32_t size, uint32_t* result) { uint32_t ptr, i; - uint32_t config = 2; + uint32_t config = SHA1_CONFIG_GO; PWRCONEXT &= ~0x4; for (ptr = 0; ptr < (size >> 2); ptr += 0x10) { - for (i = 0; i < 0x10; i++) SHA1DATAIN[i] = data[ptr + i]; - SHA1CONFIG = config; - config = 0xA; - while ((SHA1CONFIG & 1) != 0); + for (i = 0; i < 0x10; i++) SHA1_DATA[i] = data[ptr + i]; + SHA1_CONFIG = config; + config = SHA1_CONFIG_CONT | SHA1_CONFIG_GO; + while ((SHA1_CONFIG & SHA1_CONFIG_BUSY) != 0); } - for (i = 0; i < 5; i ++) result[i] = SHA1RESULT[i]; + for (i = 0; i < 5; i ++) result[i] = SHA1_RESULT[i]; PWRCONEXT |= 0x4; } diff --git a/firmware/export/s5l87xx.h b/firmware/export/s5l87xx.h index bb3aab16ba..60317e60d0 100644 --- a/firmware/export/s5l87xx.h +++ b/firmware/export/s5l87xx.h @@ -26,9 +26,10 @@ #include #endif -#define REG16_PTR_T volatile uint16_t * -#define REG32_PTR_T volatile uint32_t * -#define VOID_PTR_PTR_T void* volatile* +#define REG_BIT(x) (1 << (x)) +#define REG16_PTR_T volatile uint16_t * +#define REG32_PTR_T volatile uint32_t * +#define VOID_PTR_PTR_T void* volatile* #if CONFIG_CPU==S5L8700 || CONFIG_CPU==S5L8701 #define CACHEALIGN_BITS (4) /* 2^4 = 16 bytes */ @@ -1612,19 +1613,30 @@ Information for them was gathered solely by reverse-engineering Apple's firmware #define SHA1_BASE 0x38000000 #endif -#define SHA1CONFIG (*((REG32_PTR_T)(SHA1_BASE))) -#define SHA1RESET (*((REG32_PTR_T)(SHA1_BASE + 0x04))) +#define SHA1_CONFIG (*((REG32_PTR_T)(SHA1_BASE))) +#define SHA1_SWRESET (*((REG32_PTR_T)(SHA1_BASE + 0x04))) +#define SHA1_INT_SRC (*((REG32_PTR_T)(SHA1_BASE + 0x08))) +#define SHA1_INT_MASK (*((REG32_PTR_T)(SHA1_BASE + 0x0C))) +#define SHA1_ENDIAN (*((REG32_PTR_T)(SHA1_BASE + 0x10))) -#if CONFIG_CPU == S5L8720 -#define SHA1UNK10 (*((REG32_PTR_T)(SHA1_BASE + 0x10))) -#endif +// Result is 20 bytes (160 bits) 0x20-0x33 +#define SHA1_RESULT ((REG32_PTR_T)(SHA1_BASE + 0x20)) -#define SHA1RESULT ((REG32_PTR_T)(SHA1_BASE + 0x20)) -#define SHA1DATAIN ((REG32_PTR_T)(SHA1_BASE + 0x40)) +// Input is 64 bytes (512 bits) 0x40-0x7F +#define SHA1_DATA ((REG32_PTR_T)(SHA1_BASE + 0x40)) -#if CONFIG_CPU == S5L8720 -#define SHA1UNK80 (*((REG32_PTR_T)(SHA1_BASE + 0x80))) -#endif +#define SHA1_MASTER_MODE (*((REG32_PTR_T)(SHA1_BASE + 0x80))) +#define SHA1_MS_START_ADDR (*((REG32_PTR_T)(SHA1_BASE + 0x84))) +#define SHA1_VERSION (*((REG32_PTR_T)(SHA1_BASE + 0x88))) +#define SHA1_MS_SIZE (*((REG32_PTR_T)(SHA1_BASE + 0x8C))) +#define SHA1_FIFO_PARAM (*((REG32_PTR_T)(SHA1_BASE + 0x90))) +#define SHA1_FIFO_CMD (*((REG32_PTR_T)(SHA1_BASE + 0x94))) +#define SHA1_TX_FIFO_STAT (*((REG32_PTR_T)(SHA1_BASE + 0x98))) +#define SHA1_TX_FIFO (*((REG32_PTR_T)(SHA1_BASE + 0xA0))) + +#define SHA1_CONFIG_BUSY REG_BIT(0) +#define SHA1_CONFIG_GO REG_BIT(1) +#define SHA1_CONFIG_CONT REG_BIT(3) /* Clickwheel controller - S5L8701+ */ #if CONFIG_CPU==S5L8701 || CONFIG_CPU==S5L8702 || CONFIG_CPU==S5L8720 diff --git a/firmware/target/arm/s5l8702/crypto-s5l8702.c b/firmware/target/arm/s5l8702/crypto-s5l8702.c index a6f148112f..bac6c7caec 100644 --- a/firmware/target/arm/s5l8702/crypto-s5l8702.c +++ b/firmware/target/arm/s5l8702/crypto-s5l8702.c @@ -64,13 +64,13 @@ void sha1(void* data, uint32_t size, void* hash) uint32_t* databuf = (uint32_t*)data; uint32_t* hashbuf = (uint32_t*)hash; clockgate_enable(CLOCKGATE_SHA, true); - SHA1RESET = 1; - while (SHA1CONFIG & 1); - SHA1RESET = 0; - SHA1CONFIG = 0; + SHA1_SWRESET = 1; + while (SHA1_CONFIG & SHA1_CONFIG_BUSY); + SHA1_SWRESET = 0; + SHA1_CONFIG = 0; #if CONFIG_CPU == S5L8720 - SHA1UNK10 = 0; - SHA1UNK80 = 0; + SHA1_ENDIAN = 0; // little endian + SHA1_MASTER_MODE = 0; // slave mode #endif while (!done) { @@ -93,15 +93,15 @@ void sha1(void* data, uint32_t size, void* hash) tmp8[0x3f] = (size << 3) & 0xff; done = true; } - for (i = 0; i < 16; i++) SHA1DATAIN[i] = tmp32[i]; + for (i = 0; i < 16; i++) SHA1_DATA[i] = tmp32[i]; } else - for (i = 0; i < 16; i++) SHA1DATAIN[i] = *databuf++; - SHA1CONFIG |= 2; - while (SHA1CONFIG & 1); - SHA1CONFIG |= 8; + for (i = 0; i < 16; i++) SHA1_DATA[i] = *databuf++; + SHA1_CONFIG |= SHA1_CONFIG_GO; + while (SHA1_CONFIG & SHA1_CONFIG_BUSY); + SHA1_CONFIG |= SHA1_CONFIG_CONT; } - for (i = 0; i < 5; i++) *hashbuf++ = SHA1RESULT[i]; + for (i = 0; i < 5; i++) *hashbuf++ = SHA1_RESULT[i]; clockgate_enable(CLOCKGATE_SHA, false); } From 89d24f3bd44994bcbcab8dbb1965a3e832e7f781 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Mon, 4 May 2026 03:07:32 +0200 Subject: [PATCH 66/88] list: fix GUI_EVENT_THEME_CHANGED timing issue Initialize a list's dirty_tick to last_dirty_tick instead of to the current tick. Issue probably only affects the sim: To force a list to reinitialize using the GUI_EVENT_THEME_CHANGED event, last_dirty_tick is set to the current tick. list_is_dirty() checks whether the list viewport needs to be re-initialized by comparing the list's dirty tick to last_dirty_tick, and seeing if time has passed. In some scenarios though, the list's vp may be initialized, become immediately dirty, and list_is_dirty is called, all in the same tick. Change-Id: Ia379117a07bbaf545e0a16d35e74888955893441 --- apps/gui/list.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/gui/list.c b/apps/gui/list.c index 9bbe09f325..f5f02f2cb8 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -59,17 +59,17 @@ static bool list_is_dirty(struct gui_synclist *list) return TIME_BEFORE(list->dirty_tick, last_dirty_tick); } -static void list_force_reinit(unsigned short id, void *param, void *last_dirty_tick) +static void list_force_reinit(unsigned short id, void *param) { (void)id; (void)param; - *(int *)last_dirty_tick = current_tick; + last_dirty_tick = current_tick; } void list_init(void) { last_dirty_tick = current_tick; - add_event_ex(GUI_EVENT_THEME_CHANGED, false, list_force_reinit, &last_dirty_tick); + add_event(GUI_EVENT_THEME_CHANGED, list_force_reinit); } static void list_init_viewports(struct gui_synclist *list) @@ -83,7 +83,7 @@ static void list_init_viewports(struct gui_synclist *list) gui_synclist_set_viewport_defaults(list->parent[i], i); } } - list->dirty_tick = current_tick; + list->dirty_tick = last_dirty_tick; } static int list_nb_lines(struct gui_synclist *list, enum screen_type screen) @@ -187,7 +187,6 @@ void gui_synclist_init(struct gui_synclist * gui_list, gui_list->title_icon = Icon_NOICON; gui_list->scheduled_talk_tick = gui_list->last_talked_tick = 0; - gui_list->dirty_tick = current_tick; #ifdef HAVE_LCD_COLOR gui_list->title_color = -1; From c145d19e853cbb4c05bd03ae061b73118868c6ea Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 24 Apr 2026 19:25:52 +0200 Subject: [PATCH 67/88] gui: align display updates, reduce UI glitches Based on commits ce33902 and 8990d52 (without PictureFlow) from "Rockpod" fork by Nux Li (https://github.com/nuxcodes/rockpod), with some adjustments. Addresses flickering when: - plugin is opened/closed - activity changes - theme is toggled - QuickScreen is opened In these cases, skin_render will not immediately update the display anymore, but instead will wait until the UI viewport is ready to be drawn as well, so we don't produce unnecessary visual glitches. Change-Id: I8bed8f06221d3e767a32450f199e69d742bc61cd --- apps/gui/bitmap/list-skinned.c | 3 +-- apps/gui/bitmap/list.c | 3 +-- apps/gui/quickscreen.c | 3 +-- apps/gui/skin_engine/skin_engine.h | 5 +++++ apps/gui/skin_engine/skin_render.c | 28 +++++++++++++++++++++++++++- apps/gui/viewport.c | 4 ++++ apps/gui/wps.c | 4 ++++ apps/misc.c | 13 +++++++++++++ apps/plugin.c | 3 +++ 9 files changed, 59 insertions(+), 7 deletions(-) diff --git a/apps/gui/bitmap/list-skinned.c b/apps/gui/bitmap/list-skinned.c index 64a7f49ad6..b9bd18c2d1 100644 --- a/apps/gui/bitmap/list-skinned.c +++ b/apps/gui/bitmap/list-skinned.c @@ -281,8 +281,7 @@ bool skinlist_draw(struct screen *display, struct gui_synclist *list) } current_column = -1; current_row = -1; - display->set_viewport(parent); - display->update_viewport(); + skin_render_deferred(display, parent); current_drawing_line = list->selected_item; return true; } diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c index 1d4942839a..34ae79e316 100644 --- a/apps/gui/bitmap/list.c +++ b/apps/gui/bitmap/list.c @@ -446,8 +446,7 @@ void list_draw(struct screen *display, struct gui_synclist *list) callback_draw_item(&list_info); } - display->set_viewport(parent); - display->update_viewport(); + skin_render_deferred(display, parent); display->set_viewport(last_vp); } diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c index b76a4fd386..a5712ed66c 100644 --- a/apps/gui/quickscreen.c +++ b/apps/gui/quickscreen.c @@ -242,8 +242,7 @@ static void gui_quickscreen_draw(const struct gui_quickscreen *qs, (vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8); } - display->set_viewport(parent); - display->update_viewport(); + skin_render_deferred(display, parent); display->set_viewport(last_vp); } diff --git a/apps/gui/skin_engine/skin_engine.h b/apps/gui/skin_engine/skin_engine.h index fbaf1bf68a..f753dc8087 100644 --- a/apps/gui/skin_engine/skin_engine.h +++ b/apps/gui/skin_engine/skin_engine.h @@ -51,6 +51,11 @@ void skin_disarm_touchregions(struct gui_wps *gwps); void skin_update(enum skinnable_screens skin, enum screen_type screen, unsigned int update_type); +/* Defer updates in skin_render */ +void skin_defer_rendering(bool deferred); +/* Render viewport together with deferred updates */ +void skin_render_deferred(struct screen *display, struct viewport *vp); + bool skin_has_sbs(struct gui_wps *gwps); diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index 77443eb8eb..8a7cea5414 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c @@ -84,6 +84,8 @@ static void skin_render_playlistviewer(struct playlistviewer* viewer, unsigned long refresh_type); static char* skin_buffer; +static bool defer_rendering; +static bool dirty; static inline struct skin_element* get_child(OFFSETTYPE(struct skin_element**) children, int child) @@ -841,6 +843,27 @@ void skin_render_viewport(struct skin_element* viewport, struct gui_wps *gwps, wps_display_images(gwps, &skin_viewport->vp); } +void skin_defer_rendering(bool deferred) +{ + defer_rendering = deferred; +} + +void skin_render_deferred(struct screen *display, struct viewport *vp) +{ + if (dirty) + { + dirty = false; + display->set_viewport(NULL); + display->update(); + sb_skin_force_next_update(); + } + else + { + display->set_viewport(vp); + display->update_viewport(); + } +} + void skin_render(struct gui_wps *gwps, unsigned refresh_mode) { const int vp_is_appearing = (VP_DRAW_WASHIDDEN|VP_DRAW_HIDEABLE); @@ -940,7 +963,10 @@ void skin_render(struct gui_wps *gwps, unsigned refresh_mode) } /* Restore the default viewport */ display->set_viewport_ex(NULL, VP_FLAG_VP_SET_CLEAN); - display->update(); + if (defer_rendering) + dirty = true; + else + display->update(); } static __attribute__((noinline)) diff --git a/apps/gui/viewport.c b/apps/gui/viewport.c index b50cba7d74..6bc51401f5 100644 --- a/apps/gui/viewport.c +++ b/apps/gui/viewport.c @@ -154,7 +154,11 @@ static void toggle_theme(enum screen_type screen, bool force) } intptr_t force = first_boot?0:1; + skin_defer_rendering(true); send_event(GUI_EVENT_ACTIONUPDATE, (void*)force); + skin_defer_rendering(false); + if (!first_boot) + sb_skin_force_next_update(); } else { diff --git a/apps/gui/wps.c b/apps/gui/wps.c index 0e52323dc2..6d8597d495 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -533,7 +533,11 @@ static void gwps_leave_wps(bool theme_enabled) viewports drawn by the WPS. May need further thought... */ struct wps_data *sbs = skin_get_gwps(CUSTOM_STATUSBAR, i)->data; if (gwps->data->use_extra_framebuffer && sbs->use_extra_framebuffer) + { + skin_defer_rendering(true); skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); + skin_defer_rendering(false); + } #endif viewportmanager_theme_undo(i, skin_has_sbs(gwps)); } diff --git a/apps/misc.c b/apps/misc.c index 1780ccd9fe..0c5d73d2ee 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -65,6 +65,7 @@ #include "list.h" #include "fixedpoint.h" #include "open_plugin.h" +#include "statusbar-skinned.h" #include "debug.h" @@ -1795,8 +1796,14 @@ static void push_current_activity_refresh(enum current_activity screen, bool ref { skinlist_set_cfg(i, NULL); if (refresh) + { + skin_defer_rendering(true); skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); + skin_defer_rendering(false); + } } + if (refresh) + sb_skin_force_next_update(); } static void pop_current_activity_refresh(bool refresh) @@ -1806,8 +1813,14 @@ static void pop_current_activity_refresh(bool refresh) { skinlist_set_cfg(i, NULL); if (refresh) + { + skin_defer_rendering(true); skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); + skin_defer_rendering(false); + } } + if (refresh) + sb_skin_force_next_update(); } void push_current_activity(enum current_activity screen) diff --git a/apps/plugin.c b/apps/plugin.c index 57f0cb3d97..6c689b21e6 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -1022,8 +1022,11 @@ int plugin_load(const char* plugin, const void* parameter) pop_current_activity_without_refresh(); if (get_current_activity() != ACTIVITY_WPS) { + skin_defer_rendering(true); FOR_NB_SCREENS(i) skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); + skin_defer_rendering(false); + sb_skin_force_next_update(); } if (!pfn_tsr_exit) From 42841d493f24c32da2df67d0534844401db55189 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sun, 3 May 2026 21:51:20 +0200 Subject: [PATCH 68/88] gui: inbuilt statusbar: defer viewport update Don't independently update the statusbar viewport when rendering the skin Change-Id: Idb5a983c5b08fe60db7ab239d6dfbc60190768fb --- apps/gui/statusbar.c | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/gui/statusbar.c b/apps/gui/statusbar.c index 9eda934d2e..d4dd6d0590 100644 --- a/apps/gui/statusbar.c +++ b/apps/gui/statusbar.c @@ -331,7 +331,6 @@ void gui_statusbar_draw(struct gui_statusbar * bar, bool force_redraw, struct vi } #endif display->setfont(FONT_UI); - display->update_viewport(); display->set_viewport(last_vp); bar->lastinfo = bar->info; } From 41caf678fe6324b284a2df94900ec5a78a55c5d6 Mon Sep 17 00:00:00 2001 From: mojyack Date: Sun, 28 Dec 2025 15:48:53 +0900 Subject: [PATCH 69/88] usb: allow more flexible endpoint allocation the capabilities of endpoint of several devices such as dwc2 change during runtime, so they cannot be determined during driver initialization. therefore, allocation using ep_specs is inappropriate. to support these devices, add functions to the driver that determine whether endpoints are available and make allocation more flexible. tested with ipodvideo(arc) and erosqnative(designware) Change-Id: I8005c17f3d763cd17306bf49918e1cd8084bdeff --- firmware/drivers/isp1583.c | 15 +- firmware/drivers/m66591.c | 36 ++-- firmware/drivers/usb-designware.c | 129 ++++++++------- firmware/export/usb-designware.h | 10 ++ firmware/export/usb_drv.h | 29 +++- firmware/target/arm/as3525/usb-drv-as3525.c | 44 ++--- firmware/target/arm/rk27xx/usb-drv-rk27xx.c | 29 ++-- .../sansa-connect/tnetv105_usb_drv.c | 20 ++- firmware/target/arm/usb-drv-arc.c | 88 +++++----- firmware/target/arm/usb-s3c6400x.c | 22 ++- firmware/target/arm/usb-tcc.c | 17 +- .../target/mips/ingenic_jz47xx/usb-jz4740.c | 17 +- .../target/mips/ingenic_jz47xx/usb-jz4760.c | 20 ++- firmware/usbstack/usb_core.c | 155 +++++++++--------- 14 files changed, 348 insertions(+), 283 deletions(-) diff --git a/firmware/drivers/isp1583.c b/firmware/drivers/isp1583.c index 39b9d1190e..93aba17a7b 100644 --- a/firmware/drivers/isp1583.c +++ b/firmware/drivers/isp1583.c @@ -626,18 +626,17 @@ void usb_drv_cancel_all_transfers(void) endpoints[i].halt[0] = endpoints[i].halt[1] = 1; } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { - (void)max_packet_size; /* FIXME: support max packet size override */ - (void)type; - (void)endpoint; - return 0; + /* FIXME: support max packet size override */ + (void)ctx; + (void)ep; } -int usb_drv_deinit_endpoint(int endpoint) +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { - (void)endpoint; - return 0; + (void)ctx; + (void)ep; } static void bus_reset(void) diff --git a/firmware/drivers/m66591.c b/firmware/drivers/m66591.c index cf41aced31..6095e085e3 100644 --- a/firmware/drivers/m66591.c +++ b/firmware/drivers/m66591.c @@ -640,8 +640,11 @@ void usb_drv_set_test_mode(int mode) { M66591_TESTMODE |= mode; } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)max_packet_size; /* FIXME: support max packet size override */ +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { + /* FIXME: support max packet size override */ + const int epnum = EP_NUM(ep); + const int epdir = EP_DIR(ep); + const int type = ctx->type[epnum][epdir]; int pipecfg = 0; @@ -651,41 +654,38 @@ int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { } else if(type == USB_ENDPOINT_XFER_BULK) { pipecfg |= 1<<13; } else { - /* Not a supported type */ - return -1; + panicf("mxx: unsupported type %d", type); } - int num = endpoint & USB_ENDPOINT_NUMBER_MASK; - int dir = endpoint & USB_ENDPOINT_DIR_MASK; - if (dir == USB_DIR_IN) { + if (epdir == DIR_IN) { pipecfg |= (1<<4); } - M66591_eps[num].dir = dir; + M66591_eps[epnum].dir = epdir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT; - M66591_PIPE_CFGSEL=num; + M66591_PIPE_CFGSEL = epnum; /* Enable pipe (15) */ pipecfg |= 1<<15; - pipe_handshake(num, PIPE_SHAKE_NAK); + pipe_handshake(epnum, PIPE_SHAKE_NAK); /* Setup the flags */ M66591_PIPE_CFGWND=pipecfg; - pipe_init(num); + pipe_init(epnum); - logf("mxx: ep req ep#: %d config: 0x%04x", num, M66591_PIPE_CFGWND); - - return 0; + logf("mxx: ep req ep#: %d config: 0x%04x", epnum, M66591_PIPE_CFGWND); } /* Used by stack to tell the helper functions that the pipe is not in use */ -int usb_drv_deinit_endpoint(int endpoint) { - int num = endpoint & USB_ENDPOINT_NUMBER_MASK; +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { + (void)ctx; + + int num = ep & USB_ENDPOINT_NUMBER_MASK; if (num < 1 || num > USB_NUM_ENDPOINTS) { - return -1; + return; } int flags = disable_irq_save(); @@ -695,8 +695,6 @@ int usb_drv_deinit_endpoint(int endpoint) { M66591_eps[num].dir = -1; restore_irq(flags); - - return 0; } /* Periodically called to check if a cable was plugged into the device */ diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index d1274f5e41..61fa4acc70 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -24,6 +24,8 @@ #include #include +#include "usb-designware.h" + #include "config.h" #include "cpu.h" #include "system.h" @@ -35,8 +37,6 @@ #include "usb_ch9.h" #include "usb_core.h" -#include "usb-designware.h" - /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ #include "logf.h" @@ -155,9 +155,6 @@ struct usb_dw_ep0 struct usb_ctrlrequest pending_req; }; -struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS]; /* filled in usb_drv_init */ -uint8_t usb_drv_ep_specs_flags = 0; - static const char* const dw_dir_str[USB_DW_NUM_DIRS] = { [USB_DW_EPDIR_IN] = "IN", @@ -195,7 +192,6 @@ static uint32_t usb_endpoints; /* available EPs mask */ (usually 1), otherwise it is the number of dedicated Tx FIFOs (not counting NPTX FIFO that is always dedicated for IN0). */ static int n_ptxfifos; -static uint16_t ptxfifo_usage; static uint32_t hw_maxbytes; static uint32_t hw_maxpackets; @@ -672,47 +668,28 @@ static void usb_dw_unconfigure_ep(int epnum, enum usb_dw_epdir epdir) #endif ep_periodic_msk &= ~(1 << epnum); #endif - ptxfifo_usage &= ~(1 << GET_DTXFNUM(epnum)); } usb_dw_flush_endpoint(epnum, epdir); DWC_EPCTL(epnum, epdir) = epctl; } -static int usb_dw_configure_ep(int epnum, - enum usb_dw_epdir epdir, int type, int maxpktsize) +static void usb_dw_configure_ep(const struct usb_drv_ep_alloc_ctx* ctx, int epnum, enum usb_dw_epdir epdir, int type, int maxpktsize) { uint32_t epctl = SETD0PIDEF|EPTYP(type)|USBAEP|maxpktsize; - if (epdir == USB_DW_EPDIR_IN) + if (epdir == USB_DW_EPDIR_IN && ctx->assigned_txfifos[epnum] > 0) { - /* - * If the hardware has dedicated fifos, we must give each - * IN EP a unique tx-fifo even if it is non-periodic. - */ #ifdef USB_DW_SHARED_FIFO + ep_periodic_msk |= (1 << epnum); #ifndef USB_DW_ARCH_SLAVE epctl |= DWC_DIEPCTL(epnum) & NEXTEP(0xf); #endif - if (type == USB_ENDPOINT_XFER_INT) #endif - { - int fnum; - for (fnum = 1; fnum <= n_ptxfifos; fnum++) - if (~ptxfifo_usage & (1 << fnum)) - break; - if (fnum > n_ptxfifos) - return -1; /* no available fifos */ - ptxfifo_usage |= (1 << fnum); - epctl |= DTXFNUM(fnum); -#ifdef USB_DW_SHARED_FIFO - ep_periodic_msk |= (1 << epnum); -#endif - } + epctl |= DTXFNUM(ctx->assigned_txfifos[epnum]); } DWC_EPCTL(epnum, epdir) = epctl; - return 0; /* ok */ } static void usb_dw_reset_endpoints(void) @@ -753,7 +730,6 @@ static void usb_dw_reset_endpoints(void) usb_dw_unconfigure_ep(ep, USB_DW_EPDIR_IN); } - ptxfifo_usage = 0; #ifdef USB_DW_SHARED_FIFO ep_periodic_msk = 0; #endif @@ -1509,17 +1485,6 @@ static void usb_dw_init(void) /* Soft reconnect */ udelay(3000); DWC_DCTL &= ~SDIS; - - /* Fill endpoint spec table FIXME: should be done in usb_drv_startup() */ - usb_drv_ep_specs[0].type[DIR_OUT] = USB_ENDPOINT_XFER_CONTROL; - usb_drv_ep_specs[0].type[DIR_IN] = USB_ENDPOINT_XFER_CONTROL; - for(int i = 1; i < USB_NUM_ENDPOINTS; i += 1) { - bool out_avail = usb_endpoints & (1 << (i + USB_DW_DIR_OFF(USB_DW_EPDIR_OUT))); - usb_drv_ep_specs[i].type[DIR_OUT] = out_avail ? USB_ENDPOINT_TYPE_ANY : USB_ENDPOINT_TYPE_NONE; - - bool in_avail = usb_endpoints & (1 << (i + USB_DW_DIR_OFF(USB_DW_EPDIR_IN))); - usb_drv_ep_specs[i].type[DIR_IN] = in_avail ? USB_ENDPOINT_TYPE_ANY : USB_ENDPOINT_TYPE_NONE; - } } static void usb_dw_exit(void) @@ -1617,12 +1582,65 @@ void INT_USB_FUNC(void) usb_dw_irq(); } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) +void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx) +{ + memset(ctx, 0, sizeof(*ctx)); +} + +bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int max_packet_size) { (void)max_packet_size; /* FIXME: support max packet size override */ - enum usb_dw_epdir epdir = (EP_DIR(endpoint) == DIR_IN) ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; - struct usb_dw_ep* dw_ep = usb_dw_get_ep(EP_NUM(endpoint), epdir); + const uint8_t epnum = EP_NUM(ep); + const uint8_t epdir = EP_DIR(ep); + + if(ep == EP_CONTROL) + { + return false; + } + + enum usb_dw_epdir dwdir = epdir == DIR_IN ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; + if(!(usb_endpoints & (1 << (epnum + USB_DW_DIR_OFF(dwdir))))) + { + return false; + } + + bool need_fifo = epdir == DIR_IN; +#ifdef USB_DW_SHARED_FIFO + /* in shared fifo mode, only periodic endpoints need dedicated fifo */ + need_fifo &= type == USB_ENDPOINT_XFER_ISOC || type == USB_ENDPOINT_XFER_INT; +#endif + if(!need_fifo) + { + goto ok; + } + + for (int fnum = 1; fnum <= n_ptxfifos; fnum++) + { + if (~ctx->txfifo_usage & (1 << fnum)) + { + ctx->txfifo_usage |= 1 << fnum; + ctx->assigned_txfifos[epnum] = fnum; + goto ok; + } + } + + return false; + +ok: + ctx->type[epnum][epdir] = type; + return true; +} + +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + const int epnum = EP_NUM(ep); + const int epdir_ = EP_DIR(ep); + const int type = ctx->type[epnum][epdir_]; + + enum usb_dw_epdir epdir = (epdir_ == DIR_IN) ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; + struct usb_dw_ep* dw_ep = usb_dw_get_ep(EP_NUM(ep), epdir); int maxpktsize; if(type == EPTYP_ISOCHRONOUS) @@ -1635,32 +1653,23 @@ int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) } usb_dw_target_disable_irq(); - int res = usb_dw_configure_ep(EP_NUM(endpoint), epdir, type, maxpktsize); + usb_dw_configure_ep(ctx, epnum, epdir, type, maxpktsize); usb_dw_target_enable_irq(); - if(res >= 0) - { - dw_ep->active = true; - return 0; - } - else - { - return -1; - } + dw_ep->active = true; } -int usb_drv_deinit_endpoint(int endpoint) +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { - enum usb_dw_epdir epdir = (EP_DIR(endpoint) == DIR_IN) ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; - struct usb_dw_ep* dw_ep = usb_dw_get_ep(EP_NUM(endpoint), epdir); + (void)ctx; + enum usb_dw_epdir epdir = (EP_DIR(ep) == DIR_IN) ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; + struct usb_dw_ep* dw_ep = usb_dw_get_ep(EP_NUM(ep), epdir); usb_dw_target_disable_irq(); - usb_dw_unconfigure_ep(EP_NUM(endpoint), epdir); + usb_dw_unconfigure_ep(EP_NUM(ep), epdir); usb_dw_target_enable_irq(); dw_ep->active = false; - - return 0; } int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length) diff --git a/firmware/export/usb-designware.h b/firmware/export/usb-designware.h index ccef6d3d0d..424390e3b5 100644 --- a/firmware/export/usb-designware.h +++ b/firmware/export/usb-designware.h @@ -25,6 +25,7 @@ #include #include "config.h" +#include "cpu.h" #ifndef REG32_PTR_T #define REG32_PTR_T volatile uint32_t * @@ -288,4 +289,13 @@ extern void usb_dw_target_enable_irq(void); extern void usb_dw_target_disable_irq(void); extern void usb_dw_target_clear_irq(void); +/* endpoint allocation */ +struct usb_drv_ep_alloc_ctx_dw +{ + int8_t type[USB_NUM_ENDPOINTS][2]; + uint16_t txfifo_usage; + uint8_t assigned_txfifos[USB_NUM_ENDPOINTS]; +}; +#define usb_drv_ep_alloc_ctx usb_drv_ep_alloc_ctx_dw + #endif /* __USB_DESIGNWARE_H__ */ diff --git a/firmware/export/usb_drv.h b/firmware/export/usb_drv.h index 6c8949200d..405ea1d9dd 100644 --- a/firmware/export/usb_drv.h +++ b/firmware/export/usb_drv.h @@ -62,19 +62,42 @@ enum usb_control_response { USB_CONTROL_RECEIVE, }; +/* endpoint allocation: + * there are two ways to implement endpoint allocation. + * 1. define usb_drv_ep_specs and usb_drv_ep_specs_flags in the driver. + * this is the simplest option when the types accepted by each endpoint are mutually independent. + * these variables are set only once during driver initialization and should not be modified afterward. + * 2. define usb_drv_ep_alloc_ctx and implement usb_drv_ep_reset_alloc_ctx and usb_drv_ep_allocate in the driver. + * if the available endpoint types change based on allocation status, + * these functions can be overridden to allow the driver to track the endpoint state. + * */ + +#ifndef usb_drv_ep_alloc_ctx +/* option 1 */ #define USB_ENDPOINT_TYPE_ANY (-1) #define USB_ENDPOINT_TYPE_NONE (-2) - struct usb_drv_ep_spec { int8_t type[2]; /* USB_ENDPOINT_TYPE_{ANY,NONE} USB_ENDPOINT_XFER_* */ }; - extern struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS]; #define USB_ENDPOINT_SPEC_FORCE_IO_TYPE_MATCH (1 << 0) #define USB_ENDPOINT_SPEC_IO_EXCLUSIVE (1 << 1) extern uint8_t usb_drv_ep_specs_flags; +struct usb_drv_ep_alloc_ctx { + int8_t type[USB_NUM_ENDPOINTS][2]; + int max_packet_size[USB_NUM_ENDPOINTS][2]; +}; +#else +/* option 2 */ +void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx); +bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int max_packet_size); +#endif + +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep); +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep); + /* one-time initialisation of the USB driver */ void usb_drv_startup(void); void usb_drv_int_enable(bool enable); /* Target implemented */ @@ -98,8 +121,6 @@ int usb_drv_port_speed(void); void usb_drv_cancel_all_transfers(void); void usb_drv_set_test_mode(int mode); bool usb_drv_connected(void); -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size); -int usb_drv_deinit_endpoint(int endpoint); #ifdef USB_HAS_ISOCHRONOUS /* returns the last received frame number (the 11-bit number contained in the last SOF): * - full-speed: the host sends one SOF every 1ms (so 1000 SOF/s) diff --git a/firmware/target/arm/as3525/usb-drv-as3525.c b/firmware/target/arm/as3525/usb-drv-as3525.c index 863b553164..56216bec5e 100644 --- a/firmware/target/arm/as3525/usb-drv-as3525.c +++ b/firmware/target/arm/as3525/usb-drv-as3525.c @@ -343,37 +343,37 @@ int usb_drv_port_speed(void) return (USB_DEV_STS & USB_DEV_STS_MASK_SPD) ? 0 : 1; } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)max_packet_size; +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + const int epnum = EP_NUM(ep); + const int epdir = EP_DIR(ep); + const int type = ctx->type[epnum][epdir]; - int i = EP_NUM(endpoint); -// int d = EP_DIR(endpoint) == DIR_IN ? 0 : 1; - - if (EP_DIR(endpoint) == DIR_IN) { - USB_IEP_CTRL(i) = USB_EP_CTRL_FLUSH | - USB_EP_CTRL_SNAK | - USB_EP_CTRL_ACT | - (type << 4); - USB_DEV_EP_INTR_MASK &= ~(1<max_pkt_length = mps << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; + else + qh->max_pkt_length = mps << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL; + + qh->dtd.next_td_ptr = QH_NEXT_TERMINATE; +} + + /* manual: 32.14.1 Device Controller Initialization */ void usb_drv_init(void) { @@ -494,8 +530,8 @@ void usb_drv_init(void) * will cause undefined behavior for the data pid tracking on the active * endpoint/direction. */ for(int ep_num=1;ep_nummax_pkt_length = max_packet_size << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; - else - qh->max_pkt_length = max_packet_size << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL; - - qh->dtd.next_td_ptr = QH_NEXT_TERMINATE; - - return 0; +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { + const int ep_num = EP_NUM(ep); + const int ep_dir = EP_DIR(ep); + init_endpoint(ep, ctx->type[ep_num][ep_dir], ctx->max_packet_size[ep_num][ep_dir]); } -int usb_drv_deinit_endpoint(int endpoint) { - int ep_num = EP_NUM(endpoint); - int ep_dir = EP_DIR(endpoint); +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { + (void)ctx; + int ep_num = EP_NUM(ep); + int ep_dir = EP_DIR(ep); logf("ep deinit: %d %s", ep_num, XFER_DIR_STR(ep_dir)); @@ -1032,8 +1038,6 @@ int usb_drv_deinit_endpoint(int endpoint) { } else { REG_ENDPTCTRL(ep_num) &= ~EPCTRL_RX_ENABLE & ~EPCTRL_RX_TYPE; } - - return 0; } static void prepare_td(struct transfer_descriptor* td, diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c index e570d07e31..c26072d70c 100644 --- a/firmware/target/arm/usb-s3c6400x.c +++ b/firmware/target/arm/usb-s3c6400x.c @@ -588,19 +588,23 @@ void INT_USB_FUNC(void) GINTSTS = sts; } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)max_packet_size; /* FIXME: support max packet size override */ +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + (void)ctx; - int num = EP_NUM(endpoint); - int dir = EP_DIR(endpoint); - bool out = dir == DIR_OUT; - DEPCTL(num, out) = (DEPCTL(num, out) & ~(DEPCTL_eptype_bits << DEPCTL_eptype_bitp)) + const int epnum = EP_NUM(ep); + const int epdir = EP_DIR(ep); + const int type = ctx->type[epnum][epdir]; + + bool out = epdir == DIR_OUT; + DEPCTL(epnum, out) = (DEPCTL(epnum, out) & ~(DEPCTL_eptype_bits << DEPCTL_eptype_bitp)) | DEPCTL_setd0pid | (type << DEPCTL_eptype_bitp) | DEPCTL_usbactep; - return 0; } -int usb_drv_deinit_endpoint(int endpoint) { - return 0; +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + (void)ctx; } void usb_drv_cancel_all_transfers() diff --git a/firmware/target/arm/usb-tcc.c b/firmware/target/arm/usb-tcc.c index ba055b4cb7..ab2260e48e 100644 --- a/firmware/target/arm/usb-tcc.c +++ b/firmware/target/arm/usb-tcc.c @@ -100,16 +100,17 @@ static struct tcc_ep tcc_endpoints[] = { static bool usb_drv_write_ep(struct tcc_ep *ep); static void usb_set_speed(int); -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)max_packet_size; /* FIXME: support max packet size override */ - - tcc_endpoints[EP_NUM(endpoint)].dir = EP_DIR(endpoint) == DIR_IN ? USB_DIR_IN : USB_DIR_OUT; - return 0; +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + (void)ctx; + tcc_endpoints[EP_NUM(ep)].dir = EP_DIR(ep) == DIR_IN ? USB_DIR_IN : USB_DIR_OUT; } -int usb_drv_deinit_endpoint(int endpoint) { - tcc_endpoints[EP_NUM(endpoint)].dir = -1; - return 0; +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + (void)ctx; + tcc_endpoints[EP_NUM(ep)].dir = -1; } static inline void pullup_on(void) diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c index c923d8d97a..13542b0a5d 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c @@ -836,14 +836,15 @@ void usb_drv_cancel_all_transfers(void) restore_irq(flags); } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)endpoint; - (void)type; - (void)max_packet_size; /* FIXME: support max packet size override */ - return 0; +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + (void)ctx; + (void)ep; } -int usb_drv_deinit_endpoint(int endpoint) { - (void)endpoint; - return 0; +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + (void)ctx; + (void)ep; } diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c index 1e44a0a7aa..b8f4e05070 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c @@ -1192,12 +1192,12 @@ void usb_drv_cancel_all_transfers(void) restore_irq(flags); } -int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { - (void)max_packet_size; /* FIXME: support max packet size override */ - (void)type; - - int num = EP_NUM(endpoint); - int dir = EP_DIR(endpoint); +void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + /* FIXME: support max packet size override */ + (void)ctx; + int num = EP_NUM(ep); + int dir = EP_DIR(ep); int index = num * 2 + (dir == DIR_OUT ? 1 : 0); endpoints[index].allocated = true; if(dir == DIR_IN) @@ -1207,9 +1207,11 @@ int usb_drv_init_endpoint(int endpoint, int type, int max_packet_size) { return 0; } -int usb_drv_deinit_endpoint(int endpoint) { - int num = EP_NUM(endpoint); - int dir = EP_DIR(endpoint); +void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) +{ + (void)ctx; + int num = EP_NUM(ep); + int dir = EP_DIR(ep); int index = num * 2 + (dir == DIR_OUT ? 1 : 0); endpoints[index].allocated = false; if(dir == DIR_IN) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 8b7e4510fd..c498ee4cbf 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -28,7 +28,6 @@ #include "usb.h" #include "usb_ch9.h" -#include "usb_drv.h" #include "usb_core.h" #include "usb_class_driver.h" @@ -57,6 +56,12 @@ #include "usb_iap.h" #endif +/* include order matters, include driver header before usb_drv.h */ +#if CONFIG_USBOTG == USBOTG_DESIGNWARE +#include "usb-designware.h" +#endif +#include "usb_drv.h" + /* TODO: Move target-specific stuff somewhere else (serial number reading) */ #if defined(IPOD_ARCH) && defined(CPU_PP) @@ -179,8 +184,6 @@ static int usb_no_host_callback(struct timeout *tmo) } #endif -static int usb_core_num_interfaces[NUM_CONFIGS]; - typedef void (*completion_handler_t)(int ep, int dir, int status, int length); typedef bool (*fast_completion_handler_t)(int ep, int dir, int status, int length); typedef bool (*control_handler_t)(struct usb_ctrlrequest* req, void* reqdata, @@ -199,7 +202,13 @@ struct ep_alloc_state { struct usb_class_driver* owner[2]; }; -static struct ep_alloc_state ep_alloc_states[NUM_CONFIGS][USB_NUM_ENDPOINTS]; +struct config_state { + struct usb_drv_ep_alloc_ctx ep_alloc_ctx; + struct ep_alloc_state ep_alloc_states[USB_NUM_ENDPOINTS]; + uint8_t num_interfaces; +}; + +struct config_state config_states[NUM_CONFIGS]; static struct usb_class_driver* drivers[USB_NUM_DRIVERS] = { @@ -407,9 +416,6 @@ void usb_core_init(void) } } - /* clear endpoint allocation state */ - memset(ep_alloc_states, 0, sizeof(ep_alloc_states)); - initialized = true; usb_state = DEFAULT; usb_config = 0; @@ -548,52 +554,86 @@ static void usb_core_set_serial_function_id(void) } /* synchronize endpoint initialization state to allocation state */ -static void init_deinit_endpoints(uint8_t conf_index, bool init) { +static void init_deinit_endpoints(int config, bool init) { for(int epnum = 0; epnum < USB_NUM_ENDPOINTS; epnum += 1) { for(int dir = 0; dir < 2; dir += 1) { - struct ep_alloc_state* alloc = &ep_alloc_states[conf_index][epnum]; - struct usb_class_driver* driver = alloc->owner[dir]; + struct config_state* cstate = &config_states[config - 1]; + struct ep_alloc_state* astate = &cstate->ep_alloc_states[epnum]; + struct usb_class_driver* driver = astate->owner[dir]; if(driver == NULL) { continue; } int ep = epnum | (dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); - int ret; - if(init) { - int ps = driver->get_max_packet_size ? driver->get_max_packet_size(ep) : -1; - ret = usb_drv_init_endpoint(ep, alloc->type[dir], ps); - } else { - ret = usb_drv_deinit_endpoint(ep); - } - if(ret) { - logf("usb_core: usb_drv_%s_endpoint failed ep=%d dir=%d", init ? "init" : "deinit", epnum, dir); - continue; - } if(init) { + usb_drv_ep_init(&cstate->ep_alloc_ctx, ep); ep_data[epnum].completion_handler[dir] = driver->transfer_complete; ep_data[epnum].fast_completion_handler[dir] = driver->fast_transfer_complete; ep_data[epnum].control_handler[dir] = driver->control_request; + } else { + usb_drv_ep_deinit(&cstate->ep_alloc_ctx, ep); } } } } +#ifndef usb_drv_ep_alloc_ctx +/* default endpoint allocator using usb_drv_ep_specs table */ +static void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx) { + for(int i = 0; i < USB_NUM_ENDPOINTS; i += 1) { + ctx->type[i][0] = -1; + ctx->type[i][1] = -1; + } +} + +static bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int max_packet_size) { + const uint8_t epnum = EP_NUM(ep); + const uint8_t epdir = EP_DIR(ep); + + struct usb_drv_ep_spec* spec = &usb_drv_ep_specs[epnum]; + + const int8_t spec_type = spec->type[epdir]; + if(spec_type != type && spec_type != USB_ENDPOINT_TYPE_ANY) { + return false; + } + + const int8_t other_type = ctx->type[epnum][!epdir]; + if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_IO_EXCLUSIVE && other_type != -1) { + /* the other side is allocated */ + return false; + } + + if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_FORCE_IO_TYPE_MATCH && other_type != -1 && other_type != type) { + /* the other side is allocated with another type */ + return false; + } + + ctx->type[epnum][epdir] = type; + ctx->max_packet_size[epnum][epdir] = max_packet_size; + + return true; +} +#endif + static void allocate_interfaces_and_endpoints(void) { if(usb_config != 0) { /* deinit currently used endpoints */ - init_deinit_endpoints(usb_config - 1, false); + init_deinit_endpoints(usb_config, false); } +retry: /* reset allocations */ - memset(ep_alloc_states, 0, sizeof(ep_alloc_states)); - - int interface[NUM_CONFIGS] = {0}; + for(int i = 0; i < NUM_CONFIGS; i += 1) { + config_states[i].num_interfaces = 0; + memset(config_states[i].ep_alloc_states, 0, sizeof(config_states[i].ep_alloc_states)); + usb_drv_ep_reset_alloc_ctx(&config_states[i].ep_alloc_ctx); + } for(int i = 0; i < USB_NUM_DRIVERS; i++) { struct usb_class_driver* driver = drivers[i]; - const uint8_t conf_index = driver->config - 1; + struct config_state* cstate = &config_states[driver->config - 1]; - if(!driver->enabled) { + if(!driver->enabled || driver->error) { continue; } @@ -603,69 +643,38 @@ static void allocate_interfaces_and_endpoints(void) struct usb_class_driver_ep_allocation* req = &driver->ep_allocs[reqnum]; req->ep = 0; for(int epnum = 1; epnum < USB_NUM_ENDPOINTS; epnum += 1) { - struct usb_drv_ep_spec* spec = &usb_drv_ep_specs[epnum]; - /* ep type check */ - const int8_t spec_type = spec->type[req->dir]; - if(spec_type != req->type && spec_type != USB_ENDPOINT_TYPE_ANY) { - continue; - } /* free check */ - struct ep_alloc_state* alloc = &ep_alloc_states[conf_index][epnum]; + struct ep_alloc_state* alloc = &cstate->ep_alloc_states[epnum]; if(alloc->owner[req->dir] != NULL) { continue; } - /* this ep's requested direction is free */ - - /* another checks */ - if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_IO_EXCLUSIVE) { - /* check for the other direction type */ - if(alloc->owner[!req->dir] != NULL) { - /* the other side is allocated */ - continue; - } - } - if(usb_drv_ep_specs_flags & USB_ENDPOINT_SPEC_FORCE_IO_TYPE_MATCH) { - /* check for other direction type */ - if(alloc->owner[!req->dir] != NULL && alloc->type[!req->dir] != req->type) { - /* the other side is allocated with another type */ - continue; - } + /* driver specific check */ + const int ep = epnum | (req->dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); + const int ps = driver->get_max_packet_size ? driver->get_max_packet_size(ep) : -1; + if(!usb_drv_ep_allocate(&cstate->ep_alloc_ctx, ep, req->type, ps)) { + continue; } /* all checks passed, assign it */ - const int ep = epnum | (req->dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); req->ep = ep; alloc->owner[req->dir] = driver; alloc->type[req->dir] = req->type; break; } if(req->ep == 0 && !req->optional) { - /* no matching ep found, disable the driver */ + /* no matching ep found, retry allocation excluding this driver */ + logf("usb_core: no endpoint allocated for driver %d", i); driver->enabled = false; - /* also revert all allocations for this driver */ - for(reqnum = reqnum - 1; reqnum >= 0; reqnum -= 1) { - const uint8_t ep = driver->ep_allocs[reqnum].ep; - const uint8_t epnum = EP_NUM(ep); - const uint8_t epdir = EP_DIR(ep); - const uint8_t dir = epdir == USB_DIR_OUT ? DIR_OUT : DIR_IN; - ep_alloc_states[conf_index][epnum].owner[dir] = NULL; - } - break; + goto retry; } } - if(!driver->enabled) { - continue; - } - /* assign interfaces */ - driver->first_interface = interface[conf_index]; - interface[conf_index] = driver->set_first_interface(interface[conf_index]); - driver->last_interface = interface[conf_index]; + driver->first_interface = cstate->num_interfaces; + cstate->num_interfaces = driver->set_first_interface(cstate->num_interfaces); + driver->last_interface = cstate->num_interfaces; } - - memcpy(usb_core_num_interfaces, interface, sizeof(interface)); } @@ -765,7 +774,7 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, v } } - config_descriptor.bNumInterfaces = usb_core_num_interfaces[index]; + config_descriptor.bNumInterfaces = config_states[index].num_interfaces; config_descriptor.bConfigurationValue = index + 1; config_descriptor.wTotalLength = (uint16_t)size; memcpy(&response_data[0], &config_descriptor, sizeof(struct usb_config_descriptor)); @@ -844,7 +853,7 @@ static int usb_core_do_set_config(uint8_t new_config) drivers[i]->disconnect(); } } - init_deinit_endpoints(usb_config - 1, false); + init_deinit_endpoints(usb_config, false); /* clear any pending transfer completions, * because they are depend on contents of ep_data */ @@ -861,7 +870,7 @@ static int usb_core_do_set_config(uint8_t new_config) /* activate new config */ if(usb_config != 0) { - init_deinit_endpoints(usb_config - 1, true); + init_deinit_endpoints(usb_config, true); for(int i = 0; i < USB_NUM_DRIVERS; i++) { if(!is_active(drivers[i])) { continue; From d92b42c70f8c82a56ee6d91884b54e542a3f463f Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Mon, 4 May 2026 14:04:26 -0400 Subject: [PATCH 70/88] Fix Yellow & Red in 41caf678fe Change-Id: If111595271289878097c2a8b30bec3f18390a49c --- firmware/export/usb-designware.h | 1 + firmware/target/arm/rk27xx/usb-drv-rk27xx.c | 3 --- .../target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c | 4 ++-- firmware/target/mips/ingenic_jz47xx/usb-jz4760.c | 2 -- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/firmware/export/usb-designware.h b/firmware/export/usb-designware.h index 424390e3b5..e5e4b6073d 100644 --- a/firmware/export/usb-designware.h +++ b/firmware/export/usb-designware.h @@ -24,6 +24,7 @@ #define __USB_DESIGNWARE_H__ #include +#include #include "config.h" #include "cpu.h" diff --git a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c index 2ea7395e01..1451c6265e 100644 --- a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c +++ b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c @@ -295,8 +295,6 @@ void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) else RXCON(endp) = (epnum << 8) | RXEPEN | RXNAK | RXACKINTEN | RXCFINTE | RXERRINTEN; EN_INT |= 1 << (epnum + 7); - - return 0; } void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) @@ -307,7 +305,6 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) /* disable interrupt from this endpoint */ EN_INT &= ~(1 << (num + 7)); - return 0; } /* Set the address (usually it's in a register). diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c index abfbf0d233..8e6a8610bd 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c @@ -1496,7 +1496,7 @@ void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) int num = EP_NUM(ep); int dir = EP_DIR(ep); - return tnetv_gadget_ep_enable(num, dir == DIR_IN); + tnetv_gadget_ep_enable(num, dir == DIR_IN); } void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) @@ -1505,5 +1505,5 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) int num = EP_NUM(ep); int dir = EP_DIR(ep); - return tnetv_gadget_ep_disable(num, dir == DIR_IN); + tnetv_gadget_ep_disable(num, dir == DIR_IN); } diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c index b8f4e05070..28a8d3619a 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c @@ -1204,7 +1204,6 @@ void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) REG_USB_INTRINE |= USB_INTR_EP(num); else REG_USB_INTROUTE |= USB_INTR_EP(num); - return 0; } void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) @@ -1218,5 +1217,4 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) REG_USB_INTRINE &= ~USB_INTR_EP(num); else REG_USB_INTROUTE &= ~USB_INTR_EP(num); - return 0; } From 8873cbb57e0c7759bad6b03eb83f94f1961dfefa Mon Sep 17 00:00:00 2001 From: mojyack Date: Sat, 3 Jan 2026 18:05:22 +0900 Subject: [PATCH 71/88] usb: designware: support max packet size override Change-Id: I75d3aca1599ce064c604c96f2b5ed4c041f975b8 --- firmware/drivers/usb-designware.c | 23 ++++++++++++----------- firmware/export/usb-designware.h | 2 ++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index 61fa4acc70..9968778dde 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -1589,8 +1589,6 @@ void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx) bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int max_packet_size) { - (void)max_packet_size; /* FIXME: support max packet size override */ - const uint8_t epnum = EP_NUM(ep); const uint8_t epdir = EP_DIR(ep); @@ -1629,12 +1627,12 @@ bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, int ok: ctx->type[epnum][epdir] = type; + ctx->max_packet_size[epnum][epdir] = max_packet_size; return true; } void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { - /* FIXME: support max packet size override */ const int epnum = EP_NUM(ep); const int epdir_ = EP_DIR(ep); const int type = ctx->type[epnum][epdir_]; @@ -1642,18 +1640,21 @@ void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) enum usb_dw_epdir epdir = (epdir_ == DIR_IN) ? USB_DW_EPDIR_IN : USB_DW_EPDIR_OUT; struct usb_dw_ep* dw_ep = usb_dw_get_ep(EP_NUM(ep), epdir); - int maxpktsize; - if(type == EPTYP_ISOCHRONOUS) + int mps = ctx->max_packet_size[epnum][epdir_]; + if(mps == -1) { - maxpktsize = 1023; - } - else - { - maxpktsize = usb_drv_port_speed() ? 512 : 64; + if(type == EPTYP_ISOCHRONOUS) + { + mps = 1023; + } + else + { + mps = usb_drv_port_speed() ? 512 : 64; + } } usb_dw_target_disable_irq(); - usb_dw_configure_ep(ctx, epnum, epdir, type, maxpktsize); + usb_dw_configure_ep(ctx, epnum, epdir, type, mps); usb_dw_target_enable_irq(); dw_ep->active = true; diff --git a/firmware/export/usb-designware.h b/firmware/export/usb-designware.h index e5e4b6073d..993ebcfbc1 100644 --- a/firmware/export/usb-designware.h +++ b/firmware/export/usb-designware.h @@ -294,6 +294,8 @@ extern void usb_dw_target_clear_irq(void); struct usb_drv_ep_alloc_ctx_dw { int8_t type[USB_NUM_ENDPOINTS][2]; + int max_packet_size[USB_NUM_ENDPOINTS][2]; + uint16_t txfifo_usage; uint8_t assigned_txfifos[USB_NUM_ENDPOINTS]; }; From 142e1864efd4a826fc3439cc992c0dbeb0ab16b2 Mon Sep 17 00:00:00 2001 From: mojyack Date: Wed, 7 Jan 2026 13:31:49 +0900 Subject: [PATCH 72/88] usb: fix get_max_packet_size is called before endpoints are allocated retrive the requirements like others rather than callback Change-Id: I20efce76a418ebd7aa6943f02e53b3f7a8fd2797 --- firmware/usbstack/usb_audio.c | 4 ++-- firmware/usbstack/usb_class_driver.h | 14 +++++--------- firmware/usbstack/usb_core.c | 3 +-- firmware/usbstack/usb_hid.c | 2 +- firmware/usbstack/usb_iap.c | 16 ++-------------- firmware/usbstack/usb_serial.c | 6 +++--- firmware/usbstack/usb_storage.c | 4 ++-- 7 files changed, 16 insertions(+), 33 deletions(-) diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c index aceb3bf5a5..2b633e34b1 100644 --- a/firmware/usbstack/usb_audio.c +++ b/firmware/usbstack/usb_audio.c @@ -299,9 +299,9 @@ static int as_playback_freq_idx; /* audio playback streaming frequency index (in static struct usb_class_driver_ep_allocation ep_allocs[2] = { /* output isochronous endpoint */ - {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_OUT, .optional = false}, + {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_OUT, .optional = false, .mps = -1}, /* input feedback isochronous endpoint */ - {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false}, + {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false, .mps = -1}, }; #define EP_ISO_OUT (ep_allocs[0].ep) diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index a48d19ac62..b6e5c16941 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -30,10 +30,11 @@ /* Common api, implemented by all class drivers */ struct usb_class_driver_ep_allocation { - uint8_t type; /* by driver, required ep type. USB_ENDPOINT_XFER_* */ - uint8_t dir; /* by driver, required ep dir. DIR_{IN,OUT} */ - uint8_t ep; /* by core, allocated ep. > 0 are valid but can be 0 if optional==true */ - bool optional; /* by driver, set true to mark this requirement to be optional */ + uint8_t ep; /* by core, allocated ep. > 0 are valid but can be 0 if optional==true */ + uint8_t type:2; /* by driver, required ep type. USB_ENDPOINT_XFER_* */ + uint8_t dir:1; /* by driver, required ep dir. DIR_{IN,OUT} */ + bool optional:1; /* by driver, set true to mark this requirement to be optional */ + int16_t mps; /* by driver, desired max packet size, or -1 for device driver default */ }; struct usb_class_driver { @@ -130,11 +131,6 @@ struct usb_class_driver { * Mandatory function if alternate interface support is needed */ int (*get_interface)(int interface); - /* Asks the driver max packet size for the endpoint. - * Drivers can returns desired value in bytes, - * or -1 to use the device controller default */ - int (*get_max_packet_size)(int ep); - /* Invoked by USB_NOTIFY_CLASS_DRIVER Optional function */ void (*notify_event)(intptr_t data); diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index c498ee4cbf..4961f653f3 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -651,8 +651,7 @@ retry: /* driver specific check */ const int ep = epnum | (req->dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); - const int ps = driver->get_max_packet_size ? driver->get_max_packet_size(ep) : -1; - if(!usb_drv_ep_allocate(&cstate->ep_alloc_ctx, ep, req->type, ps)) { + if(!usb_drv_ep_allocate(&cstate->ep_alloc_ctx, ep, req->type, req->mps)) { continue; } diff --git a/firmware/usbstack/usb_hid.c b/firmware/usbstack/usb_hid.c index fef3ef45aa..bd0e590a04 100644 --- a/firmware/usbstack/usb_hid.c +++ b/firmware/usbstack/usb_hid.c @@ -122,7 +122,7 @@ static bool currently_sending = false; static int usb_interface; static struct usb_class_driver_ep_allocation ep_allocs[1] = { - {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false}, + {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false, .mps = -1}, }; #define EP_IN (ep_allocs[0].ep) diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index df847fc8ad..df2a0e575c 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -40,9 +40,9 @@ struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2] = { /* uac input */ - {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false}, + {.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false, .mps = 1024}, /* hid input */ - {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false}, + {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false, .mps = 64}, }; /* interface 0 (audio control) */ @@ -442,17 +442,6 @@ static int usb_iap_get_interface(int intf) { return stream.alt; } -static int usb_iap_get_max_packet_size(int ep) { - if(ep == AS_EP_IN) { - return 1024; - } else if(ep == HID_EP_IN) { - return 64; - } else { - panicf("unexpected endpoint number %d", ep); - return 0; - } -} - static void usb_iap_init(void) { LOG("init"); } @@ -706,6 +695,5 @@ struct usb_class_driver usb_cdrv_iap = { .control_request = usb_iap_control_request, .set_interface = usb_iap_set_interface, .get_interface = usb_iap_get_interface, - .get_max_packet_size = usb_iap_get_max_packet_size, .notify_event = usb_iap_notify_event, }; diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index d23df7f19b..3dbf41828b 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -207,9 +207,9 @@ static int buffer_transitlength; static bool active = false; static struct usb_class_driver_ep_allocation ep_allocs[3] = { - {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false}, - {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false}, - {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = true}, + {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false, .mps = -1}, + {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false, .mps = -1}, + {.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = true, .mps = -1}, }; #define EP_IN (ep_allocs[0].ep) diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index 055668a56f..2a24f71f36 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -323,8 +323,8 @@ static bool locked[NUM_DRIVES]; static int usb_interface; static struct usb_class_driver_ep_allocation ep_allocs[2] = { - {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false}, - {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false}, + {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_IN, .optional = false, .mps = -1}, + {.type = USB_ENDPOINT_XFER_BULK, .dir = DIR_OUT, .optional = false, .mps = -1}, }; #define EP_IN (ep_allocs[0].ep) From d8a473015992af74125f35a08e33f45c8310cb0d Mon Sep 17 00:00:00 2001 From: mojyack Date: Tue, 5 May 2026 01:10:11 +0900 Subject: [PATCH 73/88] tools: fix reggen is not cleaned Change-Id: Iacb3f7b2c3104435a3188c9b24eb8f4374ef8b59 --- tools/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Makefile b/tools/Makefile index 525048d39e..ee015fca94 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -13,7 +13,7 @@ LDFLAGS := CLEANALL := scramble descramble iriver bmp2rb rdf2binary convbdf \ generate_rocklatin mkboot ipod_fw codepages uclpack mi4 gigabeat lngdump \ telechips gigabeats creative hmac-sha1 rbspeexenc mkzenboot mk500boot \ - convttf mkspl-x1000 wavtrim voicefont + convttf mkspl-x1000 wavtrim voicefont reggen all: scramble descramble rdf2binary mkboot mkzenboot convbdf codepages \ uclpack rbspeexenc voicefont mk500boot mkspl-x1000 From 1d5aa533218e1be8d6194ea55e972d7e99b029a9 Mon Sep 17 00:00:00 2001 From: Skye Date: Mon, 4 May 2026 01:09:31 +0900 Subject: [PATCH 74/88] playback: do not try to switch to a sampr the current sink doesn't support Change-Id: I73c18365cb3010ca45c6d41ae390b8de095c7589 --- apps/playback.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/playback.c b/apps/playback.c index 6a7e764248..8102f8d917 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -4247,12 +4247,22 @@ void audio_set_crossfade(int enable) static unsigned long audio_guess_frequency(struct mp3entry *id3) { const struct pcm_sink_caps* caps = pcm_sink_caps(pcm_current_sink()); + bool have_44 = false; + bool have_48 = false; for (size_t i = 0; i < caps->num_samprs; i += 1) { + if (caps->samprs[i] == SAMPR_44) + have_44 = true; + if (caps->samprs[i] == SAMPR_48) + have_48 = true; if (id3->frequency == caps->samprs[i]) return id3->frequency; } - return (id3->frequency % 4000) ? SAMPR_44 : SAMPR_48; + unsigned long fallback = (id3->frequency % 4000) ? SAMPR_44 : SAMPR_48; + if ((fallback == SAMPR_44 && have_44) || (fallback == SAMPR_48 && have_48)) + return fallback; + else + return caps->samprs[caps->default_freq]; } static bool audio_auto_change_frequency(struct mp3entry *id3, bool play) From e37111c3eb8906bbde1d59a269e09b5a2e0cf936 Mon Sep 17 00:00:00 2001 From: Skye Date: Wed, 6 May 2026 20:41:43 +0900 Subject: [PATCH 75/88] erosq: make volume buttons work in quickscreen Change-Id: If0efdf1cb05bf665815e772bcc7063654e8f21ac --- apps/keymaps/keymap-erosq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/keymaps/keymap-erosq.c b/apps/keymaps/keymap-erosq.c index b1e4d246a8..50d91f8ce9 100644 --- a/apps/keymaps/keymap-erosq.c +++ b/apps/keymaps/keymap-erosq.c @@ -129,6 +129,10 @@ static const struct button_mapping button_context_quickscreen[] = { { ACTION_QS_LEFT, BUTTON_SCROLL_BACK, BUTTON_NONE }, { ACTION_QS_DOWN, BUTTON_NEXT|BUTTON_REL, BUTTON_NONE }, { ACTION_QS_DOWN, BUTTON_NEXT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_QS_VOLUP, BUTTON_VOL_UP, BUTTON_NONE }, + { ACTION_QS_VOLUP, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_QS_VOLDOWN, BUTTON_VOL_DOWN, BUTTON_NONE }, + { ACTION_QS_VOLDOWN, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, { ACTION_STD_CANCEL, BUTTON_BACK, BUTTON_NONE }, { ACTION_STD_CONTEXT,BUTTON_MENU|BUTTON_REPEAT, BUTTON_MENU }, From 7aca1d46b89f758d2885c031517cfa61b9a9f9a7 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Wed, 6 May 2026 09:49:52 +0200 Subject: [PATCH 76/88] quickscreen: fix possible flickering for GUI_EVENT_NEED_UI_UPDATE Immediately redraw when skin engine does refresh, so themes that draw over UI viewport don't cause visible flickering Change-Id: I6f314cdfbd1136c710b9fee7526673e2f8b98849 --- apps/gui/quickscreen.c | 80 ++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c index a5712ed66c..821e9a2735 100644 --- a/apps/gui/quickscreen.c +++ b/apps/gui/quickscreen.c @@ -53,30 +53,20 @@ struct gui_quickscreen { const struct settings_list *items[QUICKSCREEN_ITEM_COUNT]; + struct viewport parent[NB_SCREENS]; + struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT]; + struct viewport vp_icons[NB_SCREENS]; }; -static bool redraw; - -static void quickscreen_update_callback(unsigned short id, - void *data, void *userdata) -{ - (void)id; - (void)data; - (void)userdata; - - redraw = true; -} - -static void quickscreen_fix_viewports(struct gui_quickscreen *qs, - struct screen *display, - struct viewport *parent, - struct viewport - vps[QUICKSCREEN_ITEM_COUNT], - struct viewport *vp_icons) +static void quickscreen_fix_viewports(struct gui_quickscreen *qs, enum screen_type screen) { int char_height, width, pad = 0; int left_width = 0, right_width = 0, vert_lines; unsigned char *s; + struct screen *display = &screens[screen]; + struct viewport *parent = &qs->parent[screen]; + struct viewport *vps = qs->vps[screen]; + struct viewport *vp_icons = &qs->vp_icons[screen]; int nb_lines = viewport_get_nb_lines(parent); /* nb_lines only returns the number of fully visible lines, small screens @@ -181,16 +171,15 @@ static void quickscreen_fix_viewports(struct gui_quickscreen *qs, vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT; } -static void gui_quickscreen_draw(const struct gui_quickscreen *qs, - struct screen *display, - struct viewport *parent, - struct viewport vps[QUICKSCREEN_ITEM_COUNT], - struct viewport *vp_icons) +static void gui_quickscreen_draw(struct gui_quickscreen *qs, enum screen_type screen) { - int i; + int temp, i; char buf[MAX_PATH]; unsigned const char *title, *value; - int temp; + struct screen *display = &screens[screen]; + struct viewport *parent = &qs->parent[screen]; + struct viewport *vps = qs->vps[screen]; + struct viewport *vp_icons = &qs->vp_icons[screen]; struct viewport *last_vp = display->set_viewport(parent); display->clear_viewport(); @@ -246,6 +235,16 @@ static void gui_quickscreen_draw(const struct gui_quickscreen *qs, display->set_viewport(last_vp); } +static void quickscreen_update_callback(unsigned short id, + void *data, void *userdata) +{ + (void)id; + (void)data; + + FOR_NB_SCREENS(i) + gui_quickscreen_draw((struct gui_quickscreen *) userdata, i); +} + static void talk_qs_option(const struct settings_list *opt, bool enqueue) { if (!global_settings.talk_menu || !opt) @@ -345,9 +344,6 @@ static int quickscreen_touchscreen_button(void) static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb) { int button; - struct viewport parent[NB_SCREENS]; - struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT]; - struct viewport vp_icons[NB_SCREENS]; int ret = QUICKSCREEN_OK; /* To quit we need either : * - a second press on the button that made us enter @@ -357,15 +353,13 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter push_current_activity(ACTIVITY_QUICKSCREEN); - add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_update_callback, NULL); - FOR_NB_SCREENS(i) { screens[i].set_viewport(NULL); screens[i].scroll_stop(); - viewportmanager_theme_enable(i, true, &parent[i]); - quickscreen_fix_viewports(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]); - gui_quickscreen_draw(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]); + viewportmanager_theme_enable(i, true, &qs->parent[i]); + quickscreen_fix_viewports(qs, i); + gui_quickscreen_draw(qs, i); } *usb = false; /* Announce current selection on entering this screen. This is all @@ -382,14 +376,9 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter #ifdef HAVE_TOUCHSCREEN action_gesture_reset(); #endif - while (true) { - if (redraw) - { - redraw = false; - FOR_NB_SCREENS(i) - gui_quickscreen_draw(qs, &screens[i], &parent[i], - vps[i], &vp_icons[i]); - } + add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_update_callback, qs); + while (true) + { button = get_action(CONTEXT_QUICKSCREEN, HZ/5); #ifdef HAVE_TOUCHSCREEN if (button == ACTION_TOUCHSCREEN) @@ -404,7 +393,8 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter { ret |= QUICKSCREEN_CHANGED; can_quit = true; - redraw = true; + FOR_NB_SCREENS(i) + gui_quickscreen_draw(qs, i); } else if (button == button_enter) can_quit = true; @@ -429,12 +419,14 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter if (button == ACTION_STD_CANCEL) break; } + remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_update_callback, qs); + /* Notify that we're exiting this screen */ cond_talk_ids_fq(VOICE_OK); FOR_NB_SCREENS(i) { /* stop scrolling before exiting */ for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++) - screens[i].scroll_stop_viewport(&vps[i][j]); + screens[i].scroll_stop_viewport(&qs->vps[i][j]); viewportmanager_theme_undo(i, !(ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU)); } @@ -443,8 +435,6 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter else pop_current_activity(); - remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_update_callback, NULL); - return ret; } From 34054eaa428715b853b3cfb9867a7f1021861685 Mon Sep 17 00:00:00 2001 From: JJ Style Date: Mon, 6 Apr 2026 22:28:03 +0100 Subject: [PATCH 77/88] dart_scorer: scale better on smaller devices Calculates sizes of strings to position properly on device, and reduce length of some strings so it scales better on smaller devices as it was unusable on a sansa clip zip Change-Id: Iad16c15cf85cb79fc9a9ee7146aa40c1c741c26d --- apps/plugins/dart_scorer.c | 51 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/plugins/dart_scorer.c b/apps/plugins/dart_scorer.c index dbf63be598..6e06d4d737 100644 --- a/apps/plugins/dart_scorer.c +++ b/apps/plugins/dart_scorer.c @@ -9,7 +9,7 @@ #define BUTTON_COLS 5 #define REC_HEIGHT (int)(LCD_HEIGHT / (BUTTON_ROWS + 1)) -#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS) +#define REC_WIDTH (int)(LCD_WIDTH / BUTTON_COLS) #define Y_7_POS (LCD_HEIGHT) /* Leave room for the border */ #define Y_6_POS (Y_7_POS - REC_HEIGHT) /* y6 = 63 */ @@ -48,6 +48,7 @@ #define NUM_PLAYERS 2 #define MAX_UNDO 100 +#define CENTRE_X (LCD_WIDTH/2) static const struct button_mapping *plugin_contexts[] = {pla_main_ctx}; @@ -72,12 +73,12 @@ static bool loaded = false; /* has a save been loaded? */ int btn_row, btn_col; /* current position index for button */ int prev_btn_row, prev_btn_col; /* previous cursor position */ unsigned char *buttonChar[6][5] = { - {"", "Single", "Double", "Triple", ""}, + {"", "x1", "x2", "x3", ""}, {"1", "2", "3", "4", "5"}, {"6", "7", "8", "9", "10"}, {"11", "12", "13", "14", "15"}, {"16", "17", "18", "19", "20"}, - {"", "Missed", "Bull", "Undo", ""}}; + {"", "Miss", "Bull", "Undo", ""}}; int modifier; static int do_dart_scorer_pause_menu(void); @@ -146,22 +147,34 @@ static void draw(void) { rb->lcd_clear_display(); - char buf[32]; + unsigned int w,h; + char buf[16]; - int x = 5; - int y = 10; - for (int i = 0; i < NUM_PLAYERS; ++i, x = x + 95) - { - char *turn_marker = (i == settings.turn) ? "*" : ""; - rb->snprintf(buf, sizeof(buf), "%sPlayer %d: %d", turn_marker, i + 1, settings.scores[i]); - rb->lcd_putsxy(x, y, buf); - } - int throws_x = (LCD_WIDTH / 2) - 10; - char throws_buf[3]; + char *turn_marker; + + /* player 1 score */ + turn_marker = (0 == settings.turn) ? "*" : ""; +#if LCD_HEIGHT <= 64 || LCD_WIDTH <= 96 + rb->snprintf(buf, sizeof(buf), "%sP1 %d", turn_marker, settings.scores[0]); +#else + rb->snprintf(buf, sizeof(buf), "%sPlayer 1: %d", turn_marker, settings.scores[0]); +#endif + rb->lcd_putsxy(0, 10, buf); + + /* player 2 score */ + turn_marker = (1 == settings.turn) ? "*" : ""; +#if LCD_HEIGHT <= 64 || LCD_WIDTH <= 96 + rb->snprintf(buf, sizeof(buf), "%sP2 %d", turn_marker, settings.scores[1]); +#else + rb->snprintf(buf, sizeof(buf), "%sPlayer 2: %d", turn_marker, settings.scores[1]); +#endif + rb->lcd_getstringsize(buf, &w, &h); + rb->lcd_putsxy(LCD_WIDTH - w, 10, buf); + + int throws_x = CENTRE_X - 5; for (int i = 0; i < settings.throws; ++i, throws_x += 5) { - rb->strcat(throws_buf, "1"); - rb->lcd_putsxy(throws_x, y, "|"); + rb->lcd_putsxy(throws_x, 10, "|"); } drawButtons(); @@ -324,11 +337,11 @@ static enum plugin_status do_game(bool newgame) switch (button) { case DARTS_SELECT: - if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "Single"))) + if ((!rb->strcmp(selected, "")) || (!rb->strcmp(selected, "x1"))) modifier = 1; - else if (!rb->strcmp(selected, "Double")) + else if (!rb->strcmp(selected, "x2")) modifier = 2; - else if (!rb->strcmp(selected, "Triple")) + else if (!rb->strcmp(selected, "x3")) modifier = 3; else if (!rb->strcmp(selected, "Undo")) { From 20194cb60654ed5c5b453b6424868ba74ee0d5cb Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Wed, 6 May 2026 23:55:01 +0200 Subject: [PATCH 78/88] gui: wps: render SBS and WPS in one batch For themes that display the SBS on the WPS, update display only once, instead of separately. Change-Id: I773207ef2ddbe185eb287505950ba8322624d380 --- apps/gui/wps.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/gui/wps.c b/apps/gui/wps.c index 6d8597d495..99a1f6917e 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -599,7 +599,10 @@ static void gwps_enter_wps(bool theme_enabled) skin_backdrop_show(gwps->data->backdrop_id); #endif display->clear_display(); + if (skin_has_sbs(gwps)) + skin_defer_rendering(true); skin_update(WPS, i, SKIN_REFRESH_ALL); + skin_defer_rendering(false); } #ifdef HAVE_TOUCHSCREEN From 05f1a6605d894a9448b3d44d9c56dc33571c17f5 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Thu, 7 May 2026 00:17:46 +0200 Subject: [PATCH 79/88] gui: skin_engine: fix dirty & force_waiting ignoring multiple screens In part regression introduced in c145d19e85. force_waiting not taking multiple screens into account appears to be pre-existing issue. Change-Id: Iabfc2933470145eb512c8f2763fb350e170cb1fa --- apps/gui/skin_engine/skin_render.c | 10 +++++----- apps/gui/statusbar-skinned.c | 15 +++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index 8a7cea5414..b28a31abc3 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c @@ -85,7 +85,7 @@ static void skin_render_playlistviewer(struct playlistviewer* viewer, static char* skin_buffer; static bool defer_rendering; -static bool dirty; +static bool dirty[NB_SCREENS]; static inline struct skin_element* get_child(OFFSETTYPE(struct skin_element**) children, int child) @@ -297,7 +297,7 @@ static bool do_non_text_tags(struct gui_wps *gwps, struct skin_draw_info *info, { struct skin_albumart *aa = SKINOFFSETTOPTR(skin_buffer, data->albumart); if (aa) - { + { int handle = playback_current_aa_hid(data->playback_aa_slot); #if CONFIG_TUNER if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF)) @@ -850,9 +850,9 @@ void skin_defer_rendering(bool deferred) void skin_render_deferred(struct screen *display, struct viewport *vp) { - if (dirty) + if (dirty[display->screen_type]) { - dirty = false; + dirty[display->screen_type] = false; display->set_viewport(NULL); display->update(); sb_skin_force_next_update(); @@ -964,7 +964,7 @@ void skin_render(struct gui_wps *gwps, unsigned refresh_mode) /* Restore the default viewport */ display->set_viewport_ex(NULL, VP_FLAG_VP_SET_CLEAN); if (defer_rendering) - dirty = true; + dirty[display->screen_type] = true; else display->update(); } diff --git a/apps/gui/statusbar-skinned.c b/apps/gui/statusbar-skinned.c index 1aaef617c9..715c3d6377 100644 --- a/apps/gui/statusbar-skinned.c +++ b/apps/gui/statusbar-skinned.c @@ -182,17 +182,18 @@ int sb_get_backdrop(enum screen_type screen) return -1; } #endif -static bool force_waiting = false; +static bool force_waiting[NB_SCREENS]; void sb_skin_update(enum screen_type screen, bool force) { struct wps_data *data = skin_get_gwps(CUSTOM_STATUSBAR, screen)->data; - static long next_update[NB_SCREENS] = {0}; + static long next_update[NB_SCREENS]; int i = screen; if (!data->wps_loaded) return; - if (TIME_AFTER(current_tick, next_update[i]) || force || force_waiting) + if (TIME_AFTER(current_tick, next_update[i]) || force || + force_waiting[i]) { - force_waiting = false; + force_waiting[i] = false; #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) /* currently, all remotes are readable without backlight * so still update those */ @@ -214,7 +215,8 @@ void do_sbs_update_callback(unsigned short id, void *param) /* the WPS handles changing the actual id3 data in the id3 pointers * we imported, we just want a full update */ skin_request_full_update(CUSTOM_STATUSBAR); - force_waiting = true; + FOR_NB_SCREENS(i) + force_waiting[i] = true; /* force timeout in wps main loop, so that the update is instantly */ button_queue_post(BUTTON_NONE, 0); } @@ -226,7 +228,8 @@ void sb_skin_set_update_delay(int delay) void sb_skin_force_next_update(void) { - force_waiting = true; + FOR_NB_SCREENS(i) + force_waiting[i] = true; } /* This creates and loads a ".sbs" based on the user settings for: From ce403586e0b0f34d2f5035235907eb64cd91c28e Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Thu, 7 May 2026 01:45:51 +0200 Subject: [PATCH 80/88] playlist_viewer: show loading splash for current playlist after delay Provide UI feedback if playlist can't be displayed in time. Change-Id: I5faabc2690aaeb6989f342dc284e1e7e38ba1d7d --- apps/playlist_viewer.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c index 4ec8dd59bf..176d15a989 100644 --- a/apps/playlist_viewer.c +++ b/apps/playlist_viewer.c @@ -124,6 +124,8 @@ struct playlist_viewer { struct playlist_buffer buffer; struct mp3entry *id3; bool allow_view_text_plugin; + unsigned long loading_tick; + bool is_open; }; struct playlist_search_data @@ -214,6 +216,13 @@ static void playlist_buffer_load_entries(struct playlist_buffer *pb, int index, for (i = 0; i < num_entries; i++) { + /* provide UI feedback if opening Playlist Viewer is taking too long */ + if (!viewer.is_open && TIME_AFTER(current_tick, viewer.loading_tick)) + { + viewer.loading_tick += HZ*10; + splash(0, ID2P(LANG_WAIT)); + } + int len = playlist_entry_load(&(pb->tracks[i]), index, p, remaining); if (len < 0) { @@ -663,6 +672,7 @@ static void close_playlist_viewer(void) } playlist_close(viewer.playlist); } + viewer.is_open = false; } #if defined(HAVE_HOTKEY) || defined(HAVE_TAGCACHE) @@ -960,9 +970,12 @@ static bool open_playlist_viewer(const char* filename, struct gui_synclist *playlist_lists, bool reload, int *most_recent_selection) { + viewer.loading_tick = current_tick + HZ/3; push_current_activity(ACTIVITY_PLAYLISTVIEWER); - if (!playlist_viewer_init(&viewer, filename, reload, most_recent_selection)) + if (playlist_viewer_init(&viewer, filename, reload, most_recent_selection)) + viewer.is_open = true; + else return false; update_gui(playlist_lists, true); From ae871d25a9d7781b34e14d3d5c22da2b254ba91a Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 8 May 2026 11:32:41 +0200 Subject: [PATCH 81/88] gui: skin_engine: reduce updates Slight optimization of c145d19. Not supposed to result in any visible difference. - if UI viewport is drawn for GUI_EVENT_NEED_UI_UPDATE, it doesn't need to do a viewport update; skin_render already updates the display - skin_render_deferred shouldn't need to request that the skin perform an immediate update Change-Id: Id03cf89357eaf0d61af1e928c94942d8c4882dba --- apps/gui/skin_engine/skin_render.c | 11 +++++++---- apps/plugins/lib/simple_viewer.c | 7 ++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index b28a31abc3..eefbc80ee7 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c @@ -850,12 +850,14 @@ void skin_defer_rendering(bool deferred) void skin_render_deferred(struct screen *display, struct viewport *vp) { + if (defer_rendering) + return; + if (dirty[display->screen_type]) { dirty[display->screen_type] = false; display->set_viewport(NULL); display->update(); - sb_skin_force_next_update(); } else { @@ -955,17 +957,18 @@ void skin_render(struct gui_wps *gwps, unsigned refresh_mode) skin_backdrop_show(data->backdrop_id); #endif + dirty[display->screen_type] = defer_rendering; if (((refresh_mode&SKIN_REFRESH_ALL) == SKIN_REFRESH_ALL)) { + defer_rendering = true; /* If this is the UI viewport then let the UI know * to redraw itself */ send_event(GUI_EVENT_NEED_UI_UPDATE, NULL); + defer_rendering = dirty[display->screen_type]; } /* Restore the default viewport */ display->set_viewport_ex(NULL, VP_FLAG_VP_SET_CLEAN); - if (defer_rendering) - dirty[display->screen_type] = true; - else + if (!defer_rendering) display->update(); } diff --git a/apps/plugins/lib/simple_viewer.c b/apps/plugins/lib/simple_viewer.c index bffea2080a..27a30ef0b5 100644 --- a/apps/plugins/lib/simple_viewer.c +++ b/apps/plugins/lib/simple_viewer.c @@ -38,6 +38,7 @@ struct view_info { int line_count; /* number of lines */ int line; /* current first line */ int start; /* possition of first line in text */ + bool ui_update_cb; }; static bool isbrchr(const unsigned char *str, int len) @@ -156,6 +157,7 @@ static int init_view(struct view_info *info, info->line_count = 0; info->line = 0; info->start = 0; + info->ui_update_cb = false; if (!info->sbs_has_title) { @@ -223,7 +225,9 @@ static void draw_text(struct view_info *info) info->line_count, info->line, info->line + max_show, VERTICAL); } display->set_viewport(NULL); - display->update(); + if (!info->ui_update_cb) + display->update(); + info->ui_update_cb = false; } static void scroll_up(struct view_info *info, int n) @@ -269,6 +273,7 @@ static void ui_update_cb(unsigned short id, void* param, void* user_data) (void)id; (void)param; struct view_info *info = (struct view_info *) user_data; + info->ui_update_cb = true; draw_text(info); } From 325a028af44593f6a2ad01ea11be93304191393d Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Fri, 8 May 2026 17:00:15 +0200 Subject: [PATCH 82/88] plugins: properties: clear UI viewport at startup Otherwise, with themes that adjust the viewport for the current activity, the background behind the progress bar may look glitchy while scanning items, until the list is displayed Change-Id: I27a207b37c3209eae9bc61e1fd0862fb5872be57 --- apps/plugins/properties.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index 4eb727b7bb..fc251e4900 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -329,6 +329,15 @@ enum plugin_status plugin_start(const void* parameter) { static struct dir_stats stats; const char *file = parameter; + static struct viewport ui_vp; + + /* clear UI vp */ + struct screen* display = rb->screens[SCREEN_MAIN]; + rb->viewport_set_defaults(&ui_vp, SCREEN_MAIN); + struct viewport *last_vp = display->set_viewport(&ui_vp); + display->clear_viewport(); + display->set_viewport(last_vp); + #ifdef HAVE_TOUCHSCREEN rb->touchscreen_set_mode(rb->global_settings->touch_mode); #endif From bc528c4079dcbf152529e45c8aa61768d63451a8 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Sat, 9 May 2026 07:03:58 +0200 Subject: [PATCH 83/88] plugins: properties: don't clear UI viewport for dirs Accidentally made the vp flash in previous commit. Dir scanning doesn't display a progress bar and already handles its own drawing Change-Id: Id0e67d62081dfe4b22e91c775cd80af2e55a4b69 --- apps/plugins/properties.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index fc251e4900..f61f8bcabb 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -331,13 +331,6 @@ enum plugin_status plugin_start(const void* parameter) const char *file = parameter; static struct viewport ui_vp; - /* clear UI vp */ - struct screen* display = rb->screens[SCREEN_MAIN]; - rb->viewport_set_defaults(&ui_vp, SCREEN_MAIN); - struct viewport *last_vp = display->set_viewport(&ui_vp); - display->clear_viewport(); - display->set_viewport(last_vp); - #ifdef HAVE_TOUCHSCREEN rb->touchscreen_set_mode(rb->global_settings->touch_mode); #endif @@ -349,6 +342,17 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_OK; } + /* erase background behind progress bar to prevent glitches + for themes adjusting viewport for context menu activity */ + if (props_type != PROPS_DIR) + { + struct screen* display = rb->screens[SCREEN_MAIN]; + rb->viewport_set_defaults(&ui_vp, SCREEN_MAIN); + struct viewport *last_vp = display->set_viewport(&ui_vp); + display->clear_viewport(); + display->set_viewport(last_vp); + } + if (props_type == PROPS_MUL_ID3) ret = assemble_track_info(NULL, NULL); else if (props_type != PROPS_ID3) From 075d1deac337a0310b90c6e2f948965c89dc98cb Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 3 May 2026 16:41:50 +0100 Subject: [PATCH 84/88] pcm_mixer: remove some unused preprocessor symbols Change-Id: I0c9c96f082cd4bb8313ade9a8d8da7fa2ba087e4 --- firmware/asm/arm/pcm-mixer-armv4.c | 3 --- firmware/asm/arm/pcm-mixer-armv5.c | 3 --- firmware/asm/arm/pcm-mixer-armv6.c | 2 -- firmware/asm/m68k/pcm-mixer.c | 2 -- 4 files changed, 10 deletions(-) diff --git a/firmware/asm/arm/pcm-mixer-armv4.c b/firmware/asm/arm/pcm-mixer-armv4.c index dc2edb781c..cb138fc565 100644 --- a/firmware/asm/arm/pcm-mixer-armv4.c +++ b/firmware/asm/arm/pcm-mixer-armv4.c @@ -19,9 +19,6 @@ * ****************************************************************************/ -#define MIXER_OPTIMIZED_WRITE_SAMPLES -#define MIXER_OPTIMIZED_MIX_SAMPLES - /* Mix channels' samples and apply gain factors */ static FORCE_INLINE void mix_samples(void *out, const void *src0, diff --git a/firmware/asm/arm/pcm-mixer-armv5.c b/firmware/asm/arm/pcm-mixer-armv5.c index add1522fd9..399e6a4373 100644 --- a/firmware/asm/arm/pcm-mixer-armv5.c +++ b/firmware/asm/arm/pcm-mixer-armv5.c @@ -19,9 +19,6 @@ * ****************************************************************************/ -#define MIXER_OPTIMIZED_WRITE_SAMPLES -#define MIXER_OPTIMIZED_MIX_SAMPLES - /* Mix channels' samples and apply gain factors */ static FORCE_INLINE void mix_samples(void *out, const void *src0, diff --git a/firmware/asm/arm/pcm-mixer-armv6.c b/firmware/asm/arm/pcm-mixer-armv6.c index c1334bcd6a..7bf83ecf66 100644 --- a/firmware/asm/arm/pcm-mixer-armv6.c +++ b/firmware/asm/arm/pcm-mixer-armv6.c @@ -18,8 +18,6 @@ * KIND, either express or implied. * ****************************************************************************/ -#define MIXER_OPTIMIZED_MIX_SAMPLES -#define MIXER_OPTIMIZED_WRITE_SAMPLES /* Mix channels' samples and apply gain factors */ static FORCE_INLINE void mix_samples(void *out, diff --git a/firmware/asm/m68k/pcm-mixer.c b/firmware/asm/m68k/pcm-mixer.c index 2969546e56..cee2074cd1 100644 --- a/firmware/asm/m68k/pcm-mixer.c +++ b/firmware/asm/m68k/pcm-mixer.c @@ -19,8 +19,6 @@ * ****************************************************************************/ -#define MIXER_OPTIMIZED_MIX_SAMPLES -#define MIXER_OPTIMIZED_WRITE_SAMPLES static struct emac_context { unsigned long saved; From 9bda6389cef198facd027dbe38afc9694b01cb77 Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Mon, 11 May 2026 10:44:23 +0200 Subject: [PATCH 85/88] quickscreen: fix UI update when USB connected Regression introduced in commit 7aca1d4. Quickscreen kept redrawing itself over the USB screen, because remove_event_ex was only called after returning from it. Change-Id: I8a187809781cef46d13ed45392efecb28435a9df --- apps/gui/quickscreen.c | 98 ++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c index 821e9a2735..b0d74d43a5 100644 --- a/apps/gui/quickscreen.c +++ b/apps/gui/quickscreen.c @@ -50,15 +50,17 @@ #define MARGIN 10 #define CENTER_ICONAREA_SIZE (MARGIN+8*2) -struct gui_quickscreen +struct quickscreen { const struct settings_list *items[QUICKSCREEN_ITEM_COUNT]; struct viewport parent[NB_SCREENS]; struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT]; struct viewport vp_icons[NB_SCREENS]; + int button_enter; + enum quickscreen_return result; }; -static void quickscreen_fix_viewports(struct gui_quickscreen *qs, enum screen_type screen) +static void quickscreen_fix_viewports(struct quickscreen *qs, enum screen_type screen) { int char_height, width, pad = 0; int left_width = 0, right_width = 0, vert_lines; @@ -171,7 +173,7 @@ static void quickscreen_fix_viewports(struct gui_quickscreen *qs, enum screen_ty vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT; } -static void gui_quickscreen_draw(struct gui_quickscreen *qs, enum screen_type screen) +static void quickscreen_draw(struct quickscreen *qs, enum screen_type screen) { int temp, i; char buf[MAX_PATH]; @@ -193,12 +195,12 @@ static void gui_quickscreen_draw(struct gui_quickscreen *qs, enum screen_type sc title = P2STR(ID2P(qs->items[i]->lang_id)); temp = option_value_as_int(qs->items[i]); value = option_get_valuestring(qs->items[i], - buf, MAX_PATH, temp); + buf, sizeof buf, temp); if (viewport_get_nb_lines(vp) < 2) { char text[MAX_PATH]; - snprintf(text, MAX_PATH, "%s: %s", title, value); + snprintf(text, sizeof text, "%s: %s", title, value); display->puts_scroll(0, 0, text); } else @@ -235,14 +237,13 @@ static void gui_quickscreen_draw(struct gui_quickscreen *qs, enum screen_type sc display->set_viewport(last_vp); } -static void quickscreen_update_callback(unsigned short id, - void *data, void *userdata) +static void quickscreen_draw_cb(unsigned short id, void *data, void *userdata) { (void)id; (void)data; - FOR_NB_SCREENS(i) - gui_quickscreen_draw((struct gui_quickscreen *) userdata, i); + FOR_NB_SCREENS(i) + quickscreen_draw((struct quickscreen *) userdata, i); } static void talk_qs_option(const struct settings_list *opt, bool enqueue) @@ -261,7 +262,7 @@ static void talk_qs_option(const struct settings_list *opt, bool enqueue) * - button : the key we are going to analyse * returns : true if the button corresponded to an action, false otherwise */ -static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button) +static bool quickscreen_do_button(struct quickscreen * qs, int button) { int item; bool previous = false; @@ -341,10 +342,27 @@ static int quickscreen_touchscreen_button(void) } #endif -static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb) +static void cleanup(void *parameter) +{ + struct quickscreen *qs = (struct quickscreen *) parameter; + remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_draw_cb, qs); + + FOR_NB_SCREENS(i) + { + for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++) + screens[i].scroll_stop_viewport(&qs->vps[i][j]); + viewportmanager_theme_undo(i, !(qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU)); + } + /* Eliminate flashing of parent during transition to Shortcuts */ + if (qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU) + pop_current_activity_without_refresh(); + else + pop_current_activity(); +} + +static void quickscreen_run(struct quickscreen * qs) { int button; - int ret = QUICKSCREEN_OK; /* To quit we need either : * - a second press on the button that made us enter * - an action taken while pressing the enter button, @@ -359,9 +377,8 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter screens[i].scroll_stop(); viewportmanager_theme_enable(i, true, &qs->parent[i]); quickscreen_fix_viewports(qs, i); - gui_quickscreen_draw(qs, i); + quickscreen_draw(qs, i); } - *usb = false; /* Announce current selection on entering this screen. This is all queued up, but can be interrupted as soon as a setting is changed. */ @@ -376,7 +393,7 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter #ifdef HAVE_TOUCHSCREEN action_gesture_reset(); #endif - add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_update_callback, qs); + add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_draw_cb, qs); while (true) { button = get_action(CONTEXT_QUICKSCREEN, HZ/5); @@ -384,19 +401,20 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter if (button == ACTION_TOUCHSCREEN) button = quickscreen_touchscreen_button(); #endif - if (default_event_handler(button) == SYS_USB_CONNECTED) + if (default_event_handler_ex(button, cleanup, qs) + == SYS_USB_CONNECTED) { - *usb = true; - break; + qs->result |= QUICKSCREEN_IN_USB; + return; } - if (gui_quickscreen_do_button(qs, button)) + if (quickscreen_do_button(qs, button)) { - ret |= QUICKSCREEN_CHANGED; + qs->result |= QUICKSCREEN_CHANGED; can_quit = true; FOR_NB_SCREENS(i) - gui_quickscreen_draw(qs, i); + quickscreen_draw(qs, i); } - else if (button == button_enter) + else if (button == qs->button_enter) can_quit = true; else if (button == ACTION_QS_VOLUP) { adjust_volume(1); @@ -410,38 +428,25 @@ static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter } else if (button == ACTION_STD_CONTEXT) { - ret |= QUICKSCREEN_GOTO_SHORTCUTS_MENU; + qs->result |= QUICKSCREEN_GOTO_SHORTCUTS_MENU; break; } - if ((button == button_enter) && can_quit) + if ((button == qs->button_enter) && can_quit) break; if (button == ACTION_STD_CANCEL) break; } - remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_update_callback, qs); - /* Notify that we're exiting this screen */ cond_talk_ids_fq(VOICE_OK); - FOR_NB_SCREENS(i) - { /* stop scrolling before exiting */ - for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++) - screens[i].scroll_stop_viewport(&qs->vps[i][j]); - viewportmanager_theme_undo(i, !(ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU)); - } - - if (ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU) /* Eliminate flashing of parent during */ - pop_current_activity_without_refresh(); /* transition to Shortcuts */ - else - pop_current_activity(); - - return ret; + cleanup(qs); } int quick_screen_quick(int button_enter) { - struct gui_quickscreen qs; - bool usb = false; + struct quickscreen qs; + qs.button_enter = button_enter; + qs.result = QUICKSCREEN_OK; for (int i = 0; i < 4; ++i) { @@ -451,13 +456,12 @@ int quick_screen_quick(int button_enter) qs.items[i] = NULL; } - int ret = gui_syncquickscreen_run(&qs, button_enter, &usb); - if (ret & QUICKSCREEN_CHANGED) + quickscreen_run(&qs); + + if (qs.result & QUICKSCREEN_CHANGED) settings_save(); - if (usb) - return QUICKSCREEN_IN_USB; - return ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU ? QUICKSCREEN_GOTO_SHORTCUTS_MENU : - QUICKSCREEN_OK; + + return qs.result & ~QUICKSCREEN_CHANGED; } /* stuff to make the quickscreen configurable */ From 51abd937d50c05935e2949433da9088c0bc3c1cb Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Mon, 11 May 2026 14:49:25 +0200 Subject: [PATCH 86/88] playlist viewer: retrieve track name id3 from db Metadata for formatting the trackname can be read from the database instead of from disk. I think this was changed by mistake, when a single function was added for both Show Track Info and track name formatting. Change-Id: I903d36bb513898242505f01562340924bf642a12 --- apps/playlist_viewer.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c index 176d15a989..8e28606652 100644 --- a/apps/playlist_viewer.c +++ b/apps/playlist_viewer.c @@ -52,6 +52,10 @@ #include "yesno.h" #include "playback.h" +#if defined (HAVE_TAGCACHE) && defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) +#include "tagcache.h" +#endif + /* Maximum number of tracks we can have loaded at one time */ #define MAX_PLAYLIST_ENTRIES 200 @@ -285,9 +289,15 @@ static bool retrieve_id3_tags(const int index, const char* name, struct mp3entry copy_mp3entry(id3, audio_current_track()); /* retrieve id3 from RAM */ id3_retrieval_successful = true; } - else +#if defined (HAVE_TAGCACHE) && defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) + else if (flags & METADATA_EXCLUDE_ID3_PATH) + /* retrieve id3 from database */ + id3_retrieval_successful = tagcache_fill_tags(id3, name); +#endif + + if (!id3_retrieval_successful) { - /* Read from disk, the database, doesn't store frequency, file size or codec (g4470) ChrisS*/ + /* Read from disk: retrieves frequency, file size, and codec */ id3_retrieval_successful = get_metadata_ex(id3, -1, name, flags); } return id3_retrieval_successful; From 25551180ba02be7d964ed28acbd9e492808c8ef5 Mon Sep 17 00:00:00 2001 From: Skye Date: Sun, 3 May 2026 11:17:12 +0900 Subject: [PATCH 87/88] libc: add basic maximum field width support to sscanf Change-Id: Iba6d8af32435beccf9a77d801b0eaac91549bc89 --- firmware/libc/sscanf.c | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/firmware/libc/sscanf.c b/firmware/libc/sscanf.c index 1bbb5abcbf..23c0d450d6 100644 --- a/firmware/libc/sscanf.c +++ b/firmware/libc/sscanf.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -21,7 +22,8 @@ static inline bool my_isxdigit(char c) static int parse_dec(int (*peek)(void *userp), void (*pop)(void *userp), void *userp, - long *vp) + long *vp, + int max_len) { long v = 0; int n = 0; @@ -45,7 +47,7 @@ static int parse_dec(int (*peek)(void *userp), (*pop)(userp); n++; ch = (*peek)(userp); - } while (my_isdigit(ch)); + } while (my_isdigit(ch) && n < max_len); *vp = minus ? -v : v; return n; @@ -55,13 +57,14 @@ static int parse_chars(int (*peek)(void *userp), void (*pop)(void *userp), void *userp, char *vp, - bool fake) + bool fake, + int max_len) { int n = 0; char *pt=vp; - while (!my_isspace((*peek)(userp))) + while (!my_isspace((*peek)(userp)) && n < max_len) { if(fake==false) *(pt++) = (*peek)(userp); @@ -79,7 +82,8 @@ static int parse_chars(int (*peek)(void *userp), static int parse_hex(int (*peek)(void *userp), void (*pop)(void *userp), void *userp, - unsigned long *vp) + unsigned long *vp, + int max_len) { unsigned long v = 0; int n = 0; @@ -101,7 +105,7 @@ static int parse_hex(int (*peek)(void *userp), (*pop)(userp); n++; ch = (*peek)(userp); - } while (my_isxdigit(ch)); + } while (my_isxdigit(ch) && n < max_len); *vp = v; return n; @@ -151,11 +155,24 @@ static int scan(int (*peek)(void *userp), skip=false; } + int max_len = 0; + + while('0' <= ch && ch <= '9') + { + max_len *= 10; + max_len += ch - '0'; + ch=*fmt++; + } + + if (max_len == 0) { + max_len = INT_MAX; + } + switch (ch) { case 'x': n_chars += skip_spaces(peek, pop, userp); - if ((r = parse_hex(peek, pop, userp, &ulval)) >= 0) + if ((r = parse_hex(peek, pop, userp, &ulval, max_len)) >= 0) { if(skip==false) { @@ -169,7 +186,7 @@ static int scan(int (*peek)(void *userp), break; case 'd': n_chars += skip_spaces(peek, pop, userp); - if ((r = parse_dec(peek, pop, userp, &lval)) >= 0) + if ((r = parse_dec(peek, pop, userp, &lval, max_len)) >= 0) { if(skip==false) { @@ -194,7 +211,7 @@ static int scan(int (*peek)(void *userp), switch (ch) { case 'x': - if ((r = parse_hex(peek, pop, userp, &ulval)) >= 0) + if ((r = parse_hex(peek, pop, userp, &ulval, max_len)) >= 0) { if(skip==false) { @@ -207,7 +224,7 @@ static int scan(int (*peek)(void *userp), return n; break; case 'd': - if ((r = parse_dec(peek, pop, userp, &lval)) >= 0) + if ((r = parse_dec(peek, pop, userp, &lval, max_len)) >= 0) { if(skip==false) { @@ -228,7 +245,7 @@ static int scan(int (*peek)(void *userp), break; case 's': n_chars += skip_spaces(peek, pop, userp); - n_chars += parse_chars(peek,pop, userp,skip?0:va_arg(ap, char *), skip ); + n_chars += parse_chars(peek,pop, userp,skip?0:va_arg(ap, char *), skip, max_len); if(skip==false) { n++; From 7ab521cba681f16fdf6112929e4b3c6ed3185ad0 Mon Sep 17 00:00:00 2001 From: Skye Date: Fri, 1 May 2026 12:30:31 +0900 Subject: [PATCH 88/88] libc: add actual sprintf to sprintf.c Change-Id: Iba19b587781da3191c7674a6a141c0c4fbf8b344 --- firmware/libc/sprintf.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/firmware/libc/sprintf.c b/firmware/libc/sprintf.c index a56f454c34..a78b5ed665 100644 --- a/firmware/libc/sprintf.c +++ b/firmware/libc/sprintf.c @@ -109,3 +109,21 @@ overflow: errno = EOVERFLOW; return -1; } + +int sprintf(char *buf, const char *fmt, ...) +{ + int bytes; + struct for_snprintf pr; + va_list ap; + + pr.ptr = buf; + pr.rem = INT_MAX; + + va_start(ap, fmt); + bytes = vuprintf(sprfunc, &pr, fmt, ap); + va_end(ap); + + *pr.ptr = '\0'; + + return bytes; +}