From 3c6b9bb45892a00cde3f0603e78cde1b39c4bfe0 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Thu, 22 Jan 2026 23:59:00 -0500 Subject: [PATCH 01/65] FS#13748 - The imageviewer plugin decodes the file infinitely when opening a empty JPEG file. Couldn't reproduce this on most of my devices or the sims turns out the bug depends on uninitialized memory being FF and the file empty Uninitialized memory fixed what I saw at least.. Change-Id: Ie267af0a20685003ee28ff42d008dd7942a7c6fc --- apps/plugins/imageviewer/jpegp/BUFFILEGETC.c | 1 + apps/plugins/imageviewer/jpegp/FILEGETC.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c b/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c index 5124636731..a833851b1b 100644 --- a/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c +++ b/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c @@ -74,6 +74,7 @@ extern int TELL(void) extern void *OPEN(char *f) { + memset(buff, 0, sizeof(buff)); printf("Opening %s\n", f); cur_buff_pos = length = file_pos = 0; fd = rb->open(f,O_RDONLY); diff --git a/apps/plugins/imageviewer/jpegp/FILEGETC.c b/apps/plugins/imageviewer/jpegp/FILEGETC.c index bb7f0c485f..5b03deb25c 100644 --- a/apps/plugins/imageviewer/jpegp/FILEGETC.c +++ b/apps/plugins/imageviewer/jpegp/FILEGETC.c @@ -4,8 +4,8 @@ static int fd; extern int GETC(void) { - unsigned char x; - rb->read(fd, &x, 1); + unsigned char x = 0; + rb->read(fd, &x, 1) return x; } From c80723b5397e0d0a5832c1f055aea8097491c688 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 23 Jan 2026 11:02:36 -0500 Subject: [PATCH 02/65] announce_status plugin command line arguments + force_enque_next I had commented out 'talk_force_enqueue_next()' in the voice prompts pretty sure it was because really long prompts just keep droning on problem being as soon as the plugin returns in some cases the prompt gets cut off by the parent calling shutup on us so it is now a setting (on by default) Now allows command line arguments so you can announce whatever prompt you want whenever / wherever you want (checkout open plugins for a way to create a plugin with command line arguments) you can then add this .opx shortcut to your shortcuts or quick screen and as before you can use a hotkey which also will allow command line args.. Change-Id: Id8883c0a4e85e1cb813959ab72e89817f388a397 --- apps/plugins/announce_status.c | 53 ++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c index 214a3812c2..927bac720c 100644 --- a/apps/plugins/announce_status.c +++ b/apps/plugins/announce_status.c @@ -84,7 +84,7 @@ void thread_create(void); void thread(void); /* the thread running it all */ void thread_quit(void); static int voice_general_info(bool testing); -static unsigned char* voice_info_group(unsigned char* current_token, bool testing); +static const char* voice_info_group(const char* current_token, bool testing); int plugin_main(const void* parameter); /* main loop */ enum plugin_status plugin_start(const void* parameter); /* entry */ @@ -115,8 +115,9 @@ static struct int bin_added; bool show_prompt; + bool force_enqueue; - unsigned char wps_fmt[MAX_ANNOUNCE_WPS+1]; + char wps_fmt[MAX_ANNOUNCE_WPS+1]; } gAnnounce; static struct configdata config[] = @@ -126,6 +127,7 @@ static struct configdata config[] = {TYPE_INT, 0, 10, { .int_p = &gAnnounce.grouping }, "Grouping", NULL}, {TYPE_INT, 0, 10000, { .int_p = &gAnnounce.bin_added }, "Added", NULL}, {TYPE_BOOL, 0, 1, { .bool_p = &gAnnounce.show_prompt }, "Prompt", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gAnnounce.force_enqueue }, "Enqueue", NULL}, {TYPE_STRING, 0, MAX_ANNOUNCE_WPS+1, { .string = (char*)&gAnnounce.wps_fmt }, "Fmt", NULL}, }; @@ -165,6 +167,7 @@ static void config_set_defaults(void) gAnnounce.grouping = 0; gAnnounce.wps_fmt[0] = '\0'; gAnnounce.show_prompt = true; + gAnnounce.force_enqueue = true; } static void config_reset_voice(void) @@ -176,7 +179,7 @@ static void config_reset_voice(void) if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) { - rb->splash(100, "ERROR!"); + rb->splashf(100, "%s", "ERROR!"); return; } @@ -194,7 +197,8 @@ void announce(void) voice_general_info(false); if (rb->talk_id(VOICE_PAUSE, true) < 0) rb->beep_play(800, 100, 1000); - //rb->talk_force_enqueue_next(); + if (gAnnounce.force_enqueue) + rb->talk_force_enqueue_next(); } static void announce_test(void) @@ -202,8 +206,8 @@ static void announce_test(void) rb->talk_force_shutup(); rb->sleep(HZ / 2); voice_info_group(gAnnounce.wps_fmt, true); - rb->splash(HZ, "..."); - //rb->talk_force_enqueue_next(); + if (gAnnounce.force_enqueue) + rb->talk_force_enqueue_next(); } static void announce_add(const char *str) @@ -298,7 +302,7 @@ static int announce_menu_cb(int action, case 8: /*Clear All*/ gAnnounce.wps_fmt[0] = '\0'; gAnnounce.bin_added = 0; - rb->splash(HZ / 2, ID2P(LANG_RESET_DONE_CLEAR)); + rb->splashf(HZ / 2, "%s", ID2P(LANG_RESET_DONE_CLEAR)); break; case 9: /* inspect it */ kbd_p = kbd_buf; @@ -364,6 +368,7 @@ static int settings_menu(void) ID2P(LANG_ANNOUNCE_ON), ID2P(LANG_GROUPING), ID2P(LANG_ANNOUNCEMENT_FMT), + ID2P(LANG_QUEUE_FIRST), ID2P(VOICE_BLANK), ID2P(LANG_MENU_QUIT), ID2P(LANG_SAVE_EXIT)); @@ -392,12 +397,15 @@ static int settings_menu(void) case 3: announce_menu(); break; - case 4: /*sep*/ + case 4: + rb->set_bool(rb->str(LANG_QUEUE_FIRST), &gAnnounce.force_enqueue); + break; + case 5: /*sep*/ continue; - case 5: /* quit the plugin */ + case 6: /* quit the plugin */ return -1; break; - case 6: + case 7: configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); return 0; break; @@ -538,7 +546,7 @@ int plugin_main(const void* parameter) } else { - rb->splash(HZ / 2, "Announce Status"); + rb->splashf(HZ / 2, "%s", "Announce Status"); if (gAnnounce.show_prompt) { if (rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_PLAYING) @@ -551,7 +559,7 @@ int plugin_main(const void* parameter) if (check_user_input() == true) { - rb->splash(100, ID2P(LANG_SETTINGS)); + rb->splashf(100, "%s", ID2P(LANG_SETTINGS)); int ret = settings_menu(); if (ret < 0) return 0; @@ -580,12 +588,25 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_USB_CONNECTED; config_set_defaults(); + + if (parameter && parameter != rb->plugin_tsr) + { + const char *param = (const char*) parameter; + if (param[0] != '\0') + { + voice_info_group(param, false); + rb->talk_force_enqueue_next(); + return PLUGIN_OK; + } + } + + if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) { /* If the loading failed, save a new config file */ config_set_defaults(); configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); - rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); + rb->splashf(HZ, "%s", ID2P(LANG_HOLD_FOR_SETTINGS)); } int ret = plugin_main(parameter); @@ -594,7 +615,7 @@ enum plugin_status plugin_start(const void* parameter) static int voice_general_info(bool testing) { - unsigned char* infotemplate = gAnnounce.wps_fmt; + const char* infotemplate = gAnnounce.wps_fmt; if (gAnnounce.index >= rb->strlen(gAnnounce.wps_fmt)) gAnnounce.index = 0; @@ -629,9 +650,9 @@ static int voice_general_info(bool testing) return 0; } -static unsigned char* voice_info_group(unsigned char* current_token, bool testing) +static const char* voice_info_group(const char* current_token, bool testing) { - unsigned char current_char; + char current_char; bool skip_next_group = false; gAnnounce.count = 0; From 17faa6abc788de5871a000dcfef6ebf28d856693 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Mon, 19 Jan 2026 21:51:54 +0000 Subject: [PATCH 03/65] stm32h743: fix 'make zip' error caused by config header Change-Id: I12521cbfdab3c768015f4eafdfe95adf81ba520a --- firmware/{target/arm/stm32 => export}/stm32h743-config.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename firmware/{target/arm/stm32 => export}/stm32h743-config.h (100%) diff --git a/firmware/target/arm/stm32/stm32h743-config.h b/firmware/export/stm32h743-config.h similarity index 100% rename from firmware/target/arm/stm32/stm32h743-config.h rename to firmware/export/stm32h743-config.h From 0474dca7c3663e1181d33395dbe71618c9ebcc5c Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 23 Jan 2026 16:16:31 +0000 Subject: [PATCH 04/65] x1000: add XT26G01CWSIGA flash chip Recently seen in Hifi Walker H2 units. Links: - https://datasheet.lcsc.com/lcsc/2108141930_XTX-XT26G01CWSIGA_C2833378.pdf - https://www.xtxtech.com/en/Products/info.aspx?productModel=XT26G01CWSIGA Change-Id: Ieef9974d2b13723047b020ff0ab2498bf18ca00d --- .../target/mips/ingenic_x1000/nand-x1000.c | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic_x1000/nand-x1000.c index af0f972eae..7c5547f947 100644 --- a/firmware/target/mips/ingenic_x1000/nand-x1000.c +++ b/firmware/target/mips/ingenic_x1000/nand-x1000.c @@ -93,6 +93,28 @@ static const struct nand_chip chip_gd5f1gq4xexx = { .cmd_program_load = NANDCMD_PROGRAM_LOAD_x4, }; +static const struct nand_chip chip_xt26g01cwsiga = { + .log2_ppb = 6, /* 64 pages */ + .page_size = 2048, + .oob_size = 128, + .nr_blocks = 1024, + .bbm_pos = 2048, + .clock_freq = 150000000, + .dev_conf = jz_orf(SFC_DEV_CONF, + CE_DL(1), HOLD_DL(1), WP_DL(1), + CPHA(0), CPOL(0), + TSH(7), TSETUP(0), THOLD(0), + STA_TYPE_V(1BYTE), CMD_TYPE_V(8BITS), + SMP_DELAY(1)), + .flags = NAND_CHIPFLAG_QUAD | NAND_CHIPFLAG_HAS_QE_BIT | + NAND_CHIPFLAG_ON_DIE_ECC, + .cmd_page_read = NANDCMD_PAGE_READ, + .cmd_program_execute = NANDCMD_PROGRAM_EXECUTE, + .cmd_block_erase = NANDCMD_BLOCK_ERASE, + .cmd_read_cache = NANDCMD_READ_CACHE_x4, + .cmd_program_load = NANDCMD_PROGRAM_LOAD_x4, +}; + #define chip_ds35x1gaxxx chip_gd5f1gq4xexx #define chip_gd5f1gq5xexxg chip_gd5f1gq4xexx @@ -105,6 +127,7 @@ const struct nand_chip_id supported_nand_chips[] = { NAND_CHIP_ID(&chip_ds35x1gaxxx, NAND_READID_ADDR, 0xe5, 0x21), /* 1.8 V */ NAND_CHIP_ID(&chip_gd5f1gq5xexxg, NAND_READID_ADDR, 0xc8, 0x51), /* 3.3 V */ NAND_CHIP_ID(&chip_gd5f1gq5xexxg, NAND_READID_ADDR, 0xc8, 0x41), /* 1.8 V */ + NAND_CHIP_ID(&chip_xt26g01cwsiga, NAND_READID_ADDR, 0x0b, 0x11), }; const size_t nr_supported_nand_chips = ARRAYLEN(supported_nand_chips); From 5442622d883c231e7ce264b1d04018d43d9be7ef Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 20 Jan 2026 22:24:40 +0000 Subject: [PATCH 05/65] arm: fix Cortex-M IRQ masking The following inline assembly in set_irq_level() turned out to have incorrect constraints: int newvalue = /* input parameter */; int oldvalue; asm volatile ("mrs %0, primask\n" "msr primask, %1\n" : "=r"(oldvalue) : "r"(newvalue)); leading to incorrect code generation for common cases like disable_irq_save(), which compiles to: mov r5, #1 mrs r5, primask msr primask, r5 ...which doesn't disable IRQs at all, since both of the operands got assigned to the same register; the write of 'oldvalue' clobbers the 'newvalue' input before it's used. Apparently GCC assumes that input operands are read before output operands are written. One way to fix this is adding the '&' constraint: "=&r"(oldvalue), but it's better to break things down into separate, simpler asm statements which GCC can figure out itself. Also add compiler memory barriers where primask is modified to ensure loads/stores aren't incorrectly moved outside of critical sections. While here, optimize disable_irq_save() a bit by using the cpsid instruction, which avoids the extra "mov" and register allocation needed by "msr primask". Change-Id: Iac94a76db5bac399a1cf028da4241a0473259a46 --- firmware/target/arm/system-arm-micro.h | 61 +++++++++++++++----------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/firmware/target/arm/system-arm-micro.h b/firmware/target/arm/system-arm-micro.h index a414a30a3e..a364a882fe 100644 --- a/firmware/target/arm/system-arm-micro.h +++ b/firmware/target/arm/system-arm-micro.h @@ -26,9 +26,6 @@ #define IRQ_STATUS 0x01 #define HIGHEST_IRQ_LEVEL IRQ_DISABLED -#define disable_irq_save() \ - set_irq_level(IRQ_DISABLED) - /* For compatibility with ARM classic */ #define CPU_MODE_THREAD_CONTEXT 0 @@ -47,39 +44,51 @@ __func__, __mproc, __massert); }) /* Core-level interrupt masking */ - -static inline int set_irq_level(int primask) -{ - int oldvalue; - - asm volatile ("mrs %0, primask\n" - "msr primask, %1\n" - : "=r"(oldvalue) : "r"(primask)); - - return oldvalue; -} - -static inline void restore_irq(int primask) -{ - asm volatile ("msr primask, %0" :: "r"(primask)); -} - static inline void enable_irq(void) { - asm volatile ("cpsie i"); + asm volatile ("cpsie i" ::: "memory"); } static inline void disable_irq(void) { - asm volatile ("cpsid i"); + asm volatile ("cpsid i" ::: "memory"); +} + +static inline void restore_irq(int primask) +{ + asm volatile ("msr primask, %0" :: "r"(primask) : "memory"); +} + +static inline int get_irq_level(void) +{ + int primask; + + asm volatile("mrs %0, primask" : "=r"(primask)); + + return primask; +} + +static inline int disable_irq_save(void) +{ + int oldlevel = get_irq_level(); + + disable_irq(); + + return oldlevel; +} + +static inline int set_irq_level(int primask) +{ + int oldvalue = get_irq_level(); + + restore_irq(primask); + + return oldvalue; } static inline bool irq_enabled(void) { - int primask; - asm volatile ("mrs %0, primask" : "=r"(primask)); - - return !(primask & 1); + return get_irq_level() == IRQ_ENABLED; } static inline unsigned long get_interrupt_number(void) From a74ee4c04fa62810976ba73fdaf492fd080f4199 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Wed, 21 Jan 2026 13:54:30 +0000 Subject: [PATCH 06/65] sdm32h7: enable sdmmc hardware flow control Hardware flow control prevents FIFO underruns/overruns by stopping the bus clock if one would occur. This can slow down transfers, but that's better than having data transfer fail due to AXI/AHB bus contention. Change-Id: I8696d3aff78c17dbbe85907160fa37fd4ee11e85 --- firmware/target/arm/stm32/sdmmc-stm32h7.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.c b/firmware/target/arm/stm32/sdmmc-stm32h7.c index 0bb0c323f9..8ec64a7d1d 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.c +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.c @@ -121,7 +121,7 @@ void stm32h7_sdmmc_set_power_enabled(void *controller, bool enabled) udelay(200); /* Automatically stop clock when bus is not in use */ - reg_writelf(ctl->regs, SDMMC_CLKCR, PWRSAV(1)); + reg_writelf(ctl->regs, SDMMC_CLKCR, PWRSAV(1), HWFC_EN(1)); } else { From 80fec463dfbdd5a365d515e75ea32275397b8bfb Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Wed, 21 Jan 2026 13:51:55 +0000 Subject: [PATCH 07/65] stm32h7: panic on sdmmc FIFO or DMA errors FIFO errors shouldn't be possible with hardware flow control enabled. DMA errors shouldn't occur unless a bad memory address was passed. Don't bother checking for ITCM/DTCM in the transfer setup and instead just wait for the IDMATE error; if the RM0433 reference manual is to be believed then SDMMC1 _only_ has access to AXI bus memories, and checking for all invalid destinations would be very verbose. Change-Id: I2b22b56009933e16c5adde4d36b7a906cee57791 --- firmware/target/arm/stm32/sdmmc-stm32h7.c | 27 +++++------------------ 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.c b/firmware/target/arm/stm32/sdmmc-stm32h7.c index 8ec64a7d1d..4147c0a95a 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.c +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.c @@ -273,27 +273,6 @@ int stm32h7_sdmmc_submit_command(void *controller, if (buff_size > MAX_DATA_LEN) panicf("%s: buffer too big", __func__); - /* - * IDMA on the SDMMC controller can't access the DTCM. - * This is only possible by bounce-buffering in one of - * the other memories accessible to IDMA, then using - * another DMA process to copy the resulting buffer to - * DTCM, which seems unnecessarily convoluted. - */ - if ((uintptr_t)buff_addr >= STM32_DTCM_BASE && - (uintptr_t)buff_addr < STM32_DTCM_BASE + STM32_DTCM_SIZE) - panicf("%s: buffer in DTCM not supported", __func__); - - /* - * Must assign to a variable to prevent GCC from whining - * about 'limited range of data type', because the ITCM - * is mapped at address 0. - */ - static const uintptr_t itcm_base = STM32_ITCM_BASE; - if ((uintptr_t)buff_addr >= itcm_base && - (uintptr_t)buff_addr < itcm_base + STM32_ITCM_SIZE) - panicf("%s: buffer in ITCM not supported", __func__); - /* Set block size */ uint32_t dctrl = 0; uint32_t dblocksize = find_first_set_bit(cmd->block_len); @@ -481,8 +460,12 @@ void stm32h7_sdmmc_irq_handler(struct stm32h7_sdmmc_controller *ctl) ctl->cmd_error = SDMMC_STATUS_TIMEOUT; else if (reg_vreadf(star, SDMMC_STAR, DCRCFAIL)) ctl->cmd_error = SDMMC_STATUS_INVALID_CRC; - else if (star & DATA_ERROR_BITS) + else if (reg_vreadf(star, SDMMC_STAR, DABORT)) ctl->cmd_error = SDMMC_STATUS_ERROR; + else if (reg_vreadf(star, SDMMC_STAR, IDMATE)) + panicf("sdmmc dma err: %08lx", reg_readl(ctl->regs, SDMMC_IDMABASE0R)); + else if (star & DATA_ERROR_BITS) + panicf("sdmmc data error: %08lx", star); } ctl->cmd_wait &= ~WAIT_DATA; From 72dd8bc4d05f41b3bc93515657c9835b4c305367 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 13 Jan 2026 16:06:34 +0000 Subject: [PATCH 08/65] sdmmc_host: implement sd_enable() This is mainly useful for bootloaders that want to safely disable the SD/MMC controller before booting. Disabling a controller will reset and power down the bus; all attempts to read or write to a disabled controller will fail. Change-Id: I4a7ec4287f2b8510a35d964cc806c74be8c86406 --- firmware/drivers/sdmmc_host.c | 34 ++++++++++++++++++++++++++++++++++ firmware/export/sdmmc_host.h | 1 + 2 files changed, 35 insertions(+) diff --git a/firmware/drivers/sdmmc_host.c b/firmware/drivers/sdmmc_host.c index e7110c64d1..c012731a7e 100644 --- a/firmware/drivers/sdmmc_host.c +++ b/firmware/drivers/sdmmc_host.c @@ -114,6 +114,7 @@ void sdmmc_host_init(struct sdmmc_host *host, host->ops = ops; host->controller = controller; host->present = !config->is_removable; + host->enabled = true; mutex_init(&host->lock); array[i] = host; @@ -188,6 +189,26 @@ static void sdmmc_host_bus_reset(struct sdmmc_host *host) memset(&host->cardinfo, 0, sizeof(host->cardinfo)); } +/* + * Call to enable/disable the host controller. If disabled then the + * controller and device are powered off and no access to the storage + * device is allowed. + */ +static void sdmmc_host_set_enabled(struct sdmmc_host *host, bool enabled) +{ + mutex_lock(&host->lock); + + if (enabled != host->enabled) + { + if (host->enabled) + sdmmc_host_bus_reset(host); + + host->enabled = enabled; + } + + mutex_unlock(&host->lock); +} + #ifdef HAVE_HOTSWAP static void sdmmc_host_hotswap_event(struct sdmmc_host *host, bool is_present) { @@ -629,6 +650,9 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, mutex_lock(&host->lock); + if (!host->enabled) + goto out; + if (!sdmmc_host_medium_present(host)) goto out; @@ -721,6 +745,16 @@ tCardInfo *card_get_info_target(int drive) return &sdmmc_sd_hosts[drive]->cardinfo; } +void sd_enable(bool enabled) +{ + for (size_t i = 0; i < ARRAYLEN(sdmmc_sd_hosts); ++i) + { + struct sdmmc_host *host = sdmmc_sd_hosts[i]; + + sdmmc_host_set_enabled(host, enabled); + } +} + long sd_last_disk_activity(void) { return sdmmc_sd_last_activity; diff --git a/firmware/export/sdmmc_host.h b/firmware/export/sdmmc_host.h index 7aa48e5f9e..1d00c94a27 100644 --- a/firmware/export/sdmmc_host.h +++ b/firmware/export/sdmmc_host.h @@ -231,6 +231,7 @@ struct sdmmc_host struct mutex lock; /* Bus & device state flags; must only be accessed with lock held */ + bool enabled : 1; bool need_reset : 1; bool powered : 1; bool initialized : 1; From d7a2aa7208b91bb2b24852f1cc9654942220db1b Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Wed, 14 Jan 2026 22:24:24 +0000 Subject: [PATCH 09/65] arm: add more Cortex-M SCB registers Fix a typo in the CCR register while here. Change-Id: I9b41ca48f466557683c4b678831f3e5eccec5587 --- firmware/reggen/cortex-m.regs | 101 +++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/firmware/reggen/cortex-m.regs b/firmware/reggen/cortex-m.regs index becd3005d1..4be406b928 100644 --- a/firmware/reggen/cortex-m.regs +++ b/firmware/reggen/cortex-m.regs @@ -32,7 +32,36 @@ CM_NVIC @ 0xe000e000 : block { // System control block CM_SCB @ 0xe000ed00 : block { + ICSR @ 0x04 : reg { + -- 31 NMIPENDSET + -- 28 PENDSVSET + -- 27 PENDSVCLR + -- 26 PENDSTSET + -- 25 PENDSTCLR + -- 23 ISRPREEMPT + -- 22 ISRPENDING + 20 12 VECTPENDING + -- 11 RETTOBASE + 08 00 VECTACTIVE + } + VTOR @ 0x08 : reg + + AIRCR @ 0x0c : reg { + 31 16 VECTKEY : { 0x05FA = KEY } + -- 15 ENDIANNESS : { 0 = LITTLE; 1 = BIG } + 10 08 PRIGROUP + -- 02 SYSRESETREQ + -- 01 VECTCLRACTIVE + -- 00 VECTRESET + } + + SCR @ 0x10 : reg { + 4 SEVONPEND + 2 SLEEPDEEP + 1 SLEEPONEXIT + } + CCR @ 0x14 : reg { 18 BP 17 IC @@ -41,9 +70,79 @@ CM_SCB @ 0xe000ed00 : block { 08 BFHFNMIGN 04 DIV_0_TRP 03 UNALIGN_TRP - 01 USERETMPEND + 01 USERSETMPEND 00 NONBASETHRDENA } + + SHPR1 @ 0x18 : reg { + 23 16 PRI_USAGEFAULT + 15 08 PRI_BUSFAULT + 07 00 PRI_MEMMANAGE + } + + SHPR2 @ 0x1c : reg { + 31 24 PRI_SVCALL + } + + SHPR3 @ 0x20 : reg { + 31 24 PRI_SYSTICK + 23 16 PRI_PENDSV + 07 00 PRI_DEBUGMONITOR + } + + SHCSR @ 0x24 : reg { + 18 USGFAULTENA + 17 BUSFAULTENA + 16 MEMFAULTENA + 15 SVCALLPENDED + 14 BUSFAULTPENDED + 13 MEMFAULTPENDED + 12 USGFAULTPENDED + 11 SYSTICKACT + 10 PENDSVACT + 08 MONITORACT + 07 SVCALLACT + 03 USGFAULTACT + 01 BUSFAULTACT + 00 MEMFAULTACT + } + + CFSR @ 0x28 : reg { + // UFSR bits + 25 DIVBYZERO + 24 UNALIGNED + 19 NOCP + 18 INVPC + 17 INVSTATE + 16 UNDEFINSTR + + // BFSR bits + 15 BFARVALID + 13 LSPERR + 12 STKERR + 11 UNSTKERR + 10 IMPRECISERR + 09 PRECISERR + 08 IBUSERR + + // MMSR bits + 07 MMARVALID + 05 MLSPERR + 04 MSTKERR + 03 MUNSTKERR + 01 DACCVIOL + 00 IACCVIOL + } + + HFSR @ 0x2c : reg { + 31 DEBUGEVT + 30 FORCED + 01 VECTTBL + } + + MMFAR @ 0x34 : reg + BFAR @ 0x38 : reg + CPACR @ 0x88 : reg } // System timer From 163c3723bc8da81eb1d28fbf0b453e71aeefb85c Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 23 Jan 2026 21:00:56 -0500 Subject: [PATCH 10/65] announce_status re-fix splashf voice prompts, add missing lang entry %s thru an ID2P() won't be voiced Change-Id: Ibce1e818a6ae35c388f75182c3a8226371b420b3 --- apps/lang/english.lang | 14 ++++++++++++++ apps/plugins/announce_status.c | 20 +++++++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 96a5cae355..52adf54617 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -15077,6 +15077,20 @@ hotkey: "Announcement format" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Announce Status" + + + *: "Announce Status" + + id: LANG_REMAIN desc: for constructs such as number of tracks remaining etc diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c index 927bac720c..203ac55a46 100644 --- a/apps/plugins/announce_status.c +++ b/apps/plugins/announce_status.c @@ -176,10 +176,10 @@ static void config_reset_voice(void) int interval = gAnnounce.interval; int announce = gAnnounce.announce_on; int grouping = gAnnounce.grouping; - - if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) + int status = configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER); + if (status < 0) { - rb->splashf(100, "%s", "ERROR!"); + rb->splashf(100, ID2P(LANG_FILE_ERROR), status); return; } @@ -302,7 +302,7 @@ static int announce_menu_cb(int action, case 8: /*Clear All*/ gAnnounce.wps_fmt[0] = '\0'; gAnnounce.bin_added = 0; - rb->splashf(HZ / 2, "%s", ID2P(LANG_RESET_DONE_CLEAR)); + rb->splash(HZ / 2, ID2P(LANG_RESET_DONE_CLEAR)); break; case 9: /* inspect it */ kbd_p = kbd_buf; @@ -333,7 +333,7 @@ static int announce_menu(void) { int selection = 0; - MENUITEM_STRINGLIST(announce_menu, "Announcements", announce_menu_cb, + MENUITEM_STRINGLIST(announce_menu, ID2P(LANG_ANNOUNCEMENT_FMT), announce_menu_cb, ID2P(LANG_TIME), ID2P(LANG_DATE), ID2P(LANG_TRACK), @@ -363,7 +363,7 @@ static int settings_menu(void) int selection = 0; //bool old_val; - MENUITEM_STRINGLIST(settings_menu, "Announce Settings", NULL, + MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_ANNOUNCE_STATUS), NULL, ID2P(LANG_TIMEOUT), ID2P(LANG_ANNOUNCE_ON), ID2P(LANG_GROUPING), @@ -546,20 +546,22 @@ int plugin_main(const void* parameter) } else { - rb->splashf(HZ / 2, "%s", "Announce Status"); + rb->splashf(HZ / 2, "%s", rb->str(LANG_ANNOUNCE_STATUS)); /* no talking */ if (gAnnounce.show_prompt) { +#if 0 /* splash should announce for us */ if (rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_PLAYING) { rb->talk_id(LANG_HOLD_FOR_SETTINGS, false); } +#endif rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); } if (check_user_input() == true) { - rb->splashf(100, "%s", ID2P(LANG_SETTINGS)); + rb->splash(100, ID2P(LANG_SETTINGS)); int ret = settings_menu(); if (ret < 0) return 0; @@ -606,7 +608,7 @@ enum plugin_status plugin_start(const void* parameter) /* If the loading failed, save a new config file */ config_set_defaults(); configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); - rb->splashf(HZ, "%s", ID2P(LANG_HOLD_FOR_SETTINGS)); + rb->splash(HZ, ID2P(LANG_HOLD_FOR_SETTINGS)); } int ret = plugin_main(parameter); From 1194a968bda4c5764d230ce89d0076ea500904b0 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 23 Jan 2026 21:52:42 -0500 Subject: [PATCH 11/65] [BUGFIX] pluginlib kbd_helper.c buffer ovfl sizeof is in bytes len counts as a ucschar should also be added to total_len yep buffer ovfl Change-Id: Ibebb8574e6dbab0ca78d1cdfeb04cbbda38f05e9 --- apps/plugins/announce_status.c | 2 +- apps/plugins/lib/kbd_helper.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c index 203ac55a46..cd0482f500 100644 --- a/apps/plugins/announce_status.c +++ b/apps/plugins/announce_status.c @@ -70,7 +70,7 @@ static const char keybd_layout[] = * - \n does not create a key, but it also consumes one element * - the final null terminator is equivalent to \n * - since sizeof includes the null terminator we don't need +1 for that. */ -static ucschar_t kbd_buf[sizeof(keybd_layout)]; +static ucschar_t kbd_buf[sizeof(keybd_layout) + 1]; /****************** prototypes ******************/ void print_scroll(char* string); /* implements a scrolling screen */ diff --git a/apps/plugins/lib/kbd_helper.c b/apps/plugins/lib/kbd_helper.c index 0f7bbff8f7..90ed165fcd 100644 --- a/apps/plugins/lib/kbd_helper.c +++ b/apps/plugins/lib/kbd_helper.c @@ -36,12 +36,11 @@ */ int kbd_create_layout(const char *layout, ucschar_t *buf, int bufsz) { - ucschar_t *pbuf; + ucschar_t *pbuf = buf; const unsigned char *p = layout; int len = 0; int total_len = 0; - pbuf = buf; - while (*p && (pbuf - buf + (ptrdiff_t) sizeof(ucschar_t)) < bufsz) + while (*p && (pbuf - buf + (ptrdiff_t) sizeof(*buf)) < bufsz) { p = rb->utf8decode(p, &pbuf[len+1]); if (pbuf[len+1] == '\n') @@ -55,13 +54,14 @@ int kbd_create_layout(const char *layout, ucschar_t *buf, int bufsz) len++; } - if (len+1 < bufsz) + if ((total_len + len + 1) * sizeof(*buf) < bufsz) { *pbuf = len; pbuf[len+1] = 0xFEFF; /* mark end of characters */ total_len += len + 1; - return total_len * sizeof(ucschar_t); + return total_len * sizeof(*buf); } + //rb->logf("%s %d %d\n", __func__, bufsz, (total_len + len + 1) * sizeof(*buf)); return 0; } From a0bd28a408e602d27a45ffe2a64f8734b1239ca9 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 23 Jan 2026 22:43:08 -0500 Subject: [PATCH 12/65] rliimg add flip image example (#FS13745) FS#13745 - Add transpose (like in python pillow module) and smooth resizing in rocklua most of this is supplied 'require image' flipping an image is built-in to the image generator function add image flip example to rliimg Change-Id: Ia53933c9ad09d2a5b92af45eb7a1140b64b6ae01 --- apps/plugins/lua_scripts/rlimg.lua | 65 ++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/apps/plugins/lua_scripts/rlimg.lua b/apps/plugins/lua_scripts/rlimg.lua index c4eefc7aa9..22784b3eb4 100755 --- a/apps/plugins/lua_scripts/rlimg.lua +++ b/apps/plugins/lua_scripts/rlimg.lua @@ -672,6 +672,45 @@ function rotate_image(img) rb.sleep(rb.HZ * 10)]] end -- rotate_image +function flip_image(img) + local blitop = _blit.BOR + local i = 1 + local d = 0 + local ximg + local x, y, w, h, xr, yr + + w = img:width() + h = img:height() + + --make a copy of original screen for restoration + local screen_img -- = _lcd:duplicate() + screen_img =_img.new(w, h) + screen_img :copy(_LCD, 1, 1, xr, yr, w, h) + --_print.f("CW") + + --[[--Profiling code + local timer = _timer.start()]] + + while d >= 0 do + -- copy our flipped image onto the background + _lcd:copy(ximg, x, y, w, 1, 1, h, false, blitop) + _lcd:update() + --restore the portion of the background we destroyed + _lcd:copy(screen_img, xr, yr, 1, 1) + + d = d + i + + if rb.get_plugin_action(0) == CANCEL_BUTTON then + break; + end + end + + _lcd:copy(ximg, x, y, 1, 1, w, h) + --[[-- Profiling code + _print.f("%d", _timer.stop(timer)) + rb.sleep(rb.HZ * 10)]] +end -- flip_image + -- shows blitting with a mask function blit_mask(dst) local timer = _timer() @@ -826,12 +865,13 @@ function main_menu() [6] = "Bouncing Ball (olive)", [7] = "The Twist", [8] = "Image Rotation", - [9] = "Long Text", - [10] = "Rainbow Image", - [11] = "Random Image", - [12] = "Clear Screen", - [13] = "Save Screen", - [14] = "Exit" + [9] = "Image Flip", + [10] = "Long Text", + [11] = "Rainbow Image", + [12] = "Random Image", + [13] = "Clear Screen", + [14] = "Save Screen", + [15] = "Exit" } local ft = { [0] = exit_now, --if user cancels do this function @@ -846,16 +886,17 @@ function main_menu() [6] = function(BOUNC) bounce_image(create_ball()) end, [7] = function(TWIST) twist(get_logo()) end, [8] = function(ROTAT) rotate_image(get_logo()) end, - [9] = long_text, - [10] = function(RAINB) + [9] = function(FLIP) flip_image(get_logo()) end, + [10] = long_text, + [11] = function(RAINB) rainbow_img(_lcd()); _lcd:update(); rb.sleep(rb.HZ) end, - [11] = function(RANDM) + [12] = function(RANDM) random_img(_lcd()); _lcd:update(); rb.sleep(rb.HZ) end, - [12] = function(CLEAR) _lcd:clear(BLACK); rock_lua() end, - [13] = function(SAVEI) _LCD:invert(); _img_save(_LCD, "/rocklua.bmp") end, - [14] = function(EXIT_) return true end + [13] = function(CLEAR) _lcd:clear(BLACK); rock_lua() end, + [14] = function(SAVEI) _LCD:invert(); _img_save(_LCD, "/rocklua.bmp") end, + [15] = function(EXIT_) return true end } if LCD_DEPTH < 2 then From b3683c84d8c37daf873d30ced3e7944777c6d456 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 23 Jan 2026 23:00:12 -0500 Subject: [PATCH 13/65] [FIX Yellow] kbd_helper.c unsigned / signed Change-Id: I10382b5ad930d350945da950a5d829c000e18f31 --- apps/plugins/lib/kbd_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/plugins/lib/kbd_helper.c b/apps/plugins/lib/kbd_helper.c index 90ed165fcd..50e0bc4e23 100644 --- a/apps/plugins/lib/kbd_helper.c +++ b/apps/plugins/lib/kbd_helper.c @@ -54,7 +54,7 @@ int kbd_create_layout(const char *layout, ucschar_t *buf, int bufsz) len++; } - if ((total_len + len + 1) * sizeof(*buf) < bufsz) + if ((total_len + len + 1) * (int)sizeof(*buf) < bufsz) { *pbuf = len; pbuf[len+1] = 0xFEFF; /* mark end of characters */ From 2b6029ae8d7aafc7281132f2980c4a0694d38761 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Sat, 24 Jan 2026 01:43:58 -0500 Subject: [PATCH 14/65] rliimg add flip image example (#FS13745) Try #2 FS#13745 - Add transpose (like in python pillow module) and smooth resizing in rocklua most of this is supplied 'require image' flipping an image is built-in to the image generator function add image flip example to rliimg pushed the wrong script Change-Id: I2c512a70ed26b0457e46f2f2a187d3365783af67 --- apps/plugins/lua_scripts/rlimg.lua | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/plugins/lua_scripts/rlimg.lua b/apps/plugins/lua_scripts/rlimg.lua index 22784b3eb4..b3fa72426c 100755 --- a/apps/plugins/lua_scripts/rlimg.lua +++ b/apps/plugins/lua_scripts/rlimg.lua @@ -674,38 +674,44 @@ end -- rotate_image function flip_image(img) local blitop = _blit.BOR - local i = 1 local d = 0 - local ximg - local x, y, w, h, xr, yr + local x, y, w, h w = img:width() h = img:height() - + x = (_lcd.W - w) / 2 + y = (_lcd.H - h) / 2 --make a copy of original screen for restoration local screen_img -- = _lcd:duplicate() screen_img =_img.new(w, h) - screen_img :copy(_LCD, 1, 1, xr, yr, w, h) - --_print.f("CW") + screen_img :copy(_LCD, 1, 1, x, y, w, h) --[[--Profiling code local timer = _timer.start()]] while d >= 0 do -- copy our flipped image onto the background - _lcd:copy(ximg, x, y, w, 1, 1, h, false, blitop) + if d == 0 then + _lcd:copy(img, x, y, 1, 1, w, h, false, blitop) + elseif d == 1 then + _lcd:copy(img, x, y, 1, 1, -w, h, false, blitop) + elseif d == 2 then + _lcd:copy(img, x, y, 1, 1, w, -h, false, blitop) + elseif d == 3 then + _lcd:copy(img, x, y, 1, 1, -w, -h, false, blitop) + d = -1 + end _lcd:update() --restore the portion of the background we destroyed - _lcd:copy(screen_img, xr, yr, 1, 1) + _lcd:copy(screen_img, x, y, 1, 1) d = d + i - if rb.get_plugin_action(0) == CANCEL_BUTTON then + if rb.get_plugin_action(rb.HZ) == CANCEL_BUTTON then break; end end - _lcd:copy(ximg, x, y, 1, 1, w, h) --[[-- Profiling code _print.f("%d", _timer.stop(timer)) rb.sleep(rb.HZ * 10)]] From 5af0a5003163b4c00ea85735b35cbc52aed94ec1 Mon Sep 17 00:00:00 2001 From: mojyack Date: Wed, 21 Jan 2026 00:16:35 +0900 Subject: [PATCH 15/65] arm: pp: usb-fw-pp502x: remove hack in usb_attach() not only is this unnecessary, but it causes a bus reset on certain iPod docks. Change-Id: I2c2cbf3e5660d0cf25fc6ff4367c23396c521a3f --- firmware/target/arm/pp/usb-fw-pp502x.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/firmware/target/arm/pp/usb-fw-pp502x.c b/firmware/target/arm/pp/usb-fw-pp502x.c index a4d8e28a06..e3fa02e26f 100644 --- a/firmware/target/arm/pp/usb-fw-pp502x.c +++ b/firmware/target/arm/pp/usb-fw-pp502x.c @@ -197,11 +197,6 @@ void usb_enable(bool on) void usb_attach(void) { -#if defined(IPOD_VIDEO) - /* FIXME: Some iPod Video's need this 2nd call of usb_drv_init() to establish - * an USB connection. */ - usb_drv_init(); -#endif } bool usb_plugged(void) From 7f0bc4bd952a3d45159c90b2b3d62e84b76f1f88 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 24 Jan 2026 07:54:24 -0500 Subject: [PATCH 16/65] bootloader: Share definition of show_logo via common.h For some reason it was locally defined everywhere. Move a single definition into common.h instead. Change-Id: Ie2fad74acccd89e40fcbb0f47258d2e14e0f8285 --- bootloader/common.h | 1 + bootloader/echoplayer.c | 2 -- bootloader/gigabeat-s.c | 3 --- bootloader/lyre_proto1.c | 3 --- bootloader/main-pp.c | 31 ++++++++++++++----------------- bootloader/ondavx747.c | 1 - bootloader/rk27xx.c | 10 ++++------ bootloader/sansaconnect.c | 2 -- bootloader/telechips.c | 15 ++++++--------- bootloader/xduoox3.c | 1 - 10 files changed, 25 insertions(+), 44 deletions(-) diff --git a/bootloader/common.h b/bootloader/common.h index 56cbbb335a..8b95c8e175 100644 --- a/bootloader/common.h +++ b/bootloader/common.h @@ -39,3 +39,4 @@ int load_raw_firmware(unsigned char* buf, char* firmware, int buffer_size); #ifdef ROCKBOX_HAS_LOGF void display_logf(void); #endif +void show_logo(void); diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index e2491aa0d5..4032279630 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -33,8 +33,6 @@ static bool is_usb_connected = false; -extern void show_logo(void); - static void demo_rtc(void) { int y = 0; diff --git a/bootloader/gigabeat-s.c b/bootloader/gigabeat-s.c index 499d9c218c..2829241b9b 100644 --- a/bootloader/gigabeat-s.c +++ b/bootloader/gigabeat-s.c @@ -43,9 +43,6 @@ #include "lcd.h" #include "version.h" -/* Show the Rockbox logo - in show_logo.c */ -extern void show_logo(void); - #define TAR_CHUNK 512 #define TAR_HEADER_SIZE 157 diff --git a/bootloader/lyre_proto1.c b/bootloader/lyre_proto1.c index b76d0fb467..6ad095170c 100644 --- a/bootloader/lyre_proto1.c +++ b/bootloader/lyre_proto1.c @@ -23,9 +23,6 @@ #include "../kernel-internal.h" #include "system.h" -/* Show the Rockbox logo - in show_logo.c */ -extern void show_logo(void); - int main(void) { /* Initialize Rockbox kernel */ diff --git a/bootloader/main-pp.c b/bootloader/main-pp.c index 15f6ad4fb7..f2bfa48157 100644 --- a/bootloader/main-pp.c +++ b/bootloader/main-pp.c @@ -11,7 +11,7 @@ * * Based on Rockbox iriver bootloader by Linus Nielsen Feltzing * and the ipodlinux bootloader by Daniel Palffy and Bernard Leach - * + * * 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 @@ -60,9 +60,6 @@ void lcd_reset(void); #endif -/* Show the Rockbox logo - in show_logo.c */ -extern void show_logo(void); - /* Button definitions */ #if CONFIG_KEYPAD == IRIVER_H10_PAD #define BOOTLOADER_BOOT_OF BUTTON_LEFT @@ -132,21 +129,21 @@ int load_mi4_part(unsigned char* buf, struct partinfo* pinfo, struct mi4header_t mi4header; struct ppmi_header_t ppmi_header; unsigned long sum; - + /* Read header to find out how long the mi4 file is. */ storage_read_sectors(IF_MD(0,) pinfo->start + PPMI_SECTOR_OFFSET, PPMI_SECTORS, &ppmi_header); - + /* The first four characters at 0x80000 (sector 1024) should be PPMI*/ if( memcmp(ppmi_header.magic, "PPMI", 4) ) return EFILE_NOT_FOUND; - + printf("BL mi4 size: %x", ppmi_header.length); - + /* Read mi4 header of the OF */ - storage_read_sectors(IF_MD(0,) pinfo->start + PPMI_SECTOR_OFFSET + PPMI_SECTORS + storage_read_sectors(IF_MD(0,) pinfo->start + PPMI_SECTOR_OFFSET + PPMI_SECTORS + (ppmi_header.length/512), MI4_HEADER_SECTORS, &mi4header); - + /* We don't support encrypted mi4 files yet */ if( (mi4header.plaintext) != (mi4header.mi4size-MI4_HEADER_SIZE)) return EINVALID_FORMAT; @@ -178,14 +175,14 @@ int load_mi4_part(unsigned char* buf, struct partinfo* pinfo, if(sum != mi4header.crc32) return EBAD_CHKSUM; - -#ifdef SANSA_E200 + +#ifdef SANSA_E200 if (disable_rebuild) { char block[512]; - + printf("Disabling database rebuild"); - + storage_read_sectors(IF_MD(0,) pinfo->start + 0x3c08, 1, block); block[0xe1] = 0; storage_write_sectors(IF_MD(0,) pinfo->start + 0x3c08, 1, block); @@ -206,7 +203,7 @@ static int handle_usb(int connect_timeout) struct queue_event ev; int usb = USB_EXTRACTED; long end_tick = 0; - + if (!usb_plugged()) return USB_EXTRACTED; @@ -394,7 +391,7 @@ void* main(void) error(EDISK,num_partitions, true); } - /* Just list the first 2 partitions since we don't have any devices yet + /* Just list the first 2 partitions since we don't have any devices yet that have more than that */ for(i=0; i Date: Sat, 24 Jan 2026 07:55:56 -0500 Subject: [PATCH 17/65] xduoox3: A couple of minor cleanups in the bootloader * When SHOW_LOGO is not defined, print the version at the same time as the logo would have been displayed * Don't re-init the display after every message in USB mode Change-Id: Ida0f5643b1d57004877ec5c42fc14028f53b1c89 --- bootloader/xduoox3.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bootloader/xduoox3.c b/bootloader/xduoox3.c index 6c37b5afcc..a8d9fc4a1d 100644 --- a/bootloader/xduoox3.c +++ b/bootloader/xduoox3.c @@ -68,7 +68,6 @@ void init_lcd(void) #ifdef HAVE_BOOTLOADER_USB_MODE static void show_splash(int timeout, const char *msg) { - init_lcd(); reset_screen(); lcd_putsxy( (LCD_WIDTH - (SYSFONT_WIDTH * strlen(msg))) / 2, (LCD_HEIGHT - SYSFONT_HEIGHT) / 2, msg); @@ -171,6 +170,9 @@ int main(void) init_lcd(); #ifdef SHOW_LOGO show_logo(); +#else + printf(MODEL_NAME" Rockbox Bootloader"); + printf("Version %s", rbversion); #endif button_init(); @@ -197,11 +199,6 @@ int main(void) } #endif /* HAVE_BOOTLOADER_USB_MODE */ -#ifndef SHOW_LOGO - printf(MODEL_NAME" Rockbox Bootloader"); - printf("Version %s", rbversion); -#endif - rc = boot_rockbox(); if(rc <= EFILE_EMPTY) { From 62332841cf87f08728730e5dfa684a19b69ca5f9 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 15 Jan 2026 01:26:45 +0000 Subject: [PATCH 18/65] stm32h7: refactor systick code Make systick setup less hardcoded, and create a public API for use by targets, in preparation for moving system_init() into target-specific code. Improve the implementation of udelay to make it more robust against timer wraparound. Change-Id: I21bb8821cfd1d7e4049fac6e6a4548d80a4276f7 --- firmware/target/arm/stm32/system-stm32h7.c | 124 +++++++++++++-------- firmware/target/arm/stm32/system-target.h | 11 +- 2 files changed, 88 insertions(+), 47 deletions(-) diff --git a/firmware/target/arm/stm32/system-stm32h7.c b/firmware/target/arm/stm32/system-stm32h7.c index b58fd76322..35c1d2e60d 100644 --- a/firmware/target/arm/stm32/system-stm32h7.c +++ b/firmware/target/arm/stm32/system-stm32h7.c @@ -27,25 +27,31 @@ #include "regs/cortex-m/cm_systick.h" #include "regs/stm32h743/dbgmcu.h" -/* EXT timer is 1/8th of CPU clock */ -#define SYSTICK_FREQ (CPU_FREQ / 8) -#define SYSTICK_PER_MS (SYSTICK_FREQ / 1000) -#define SYSTICK_PER_US (SYSTICK_FREQ / 1000000) +/* Assumed initial CPU frequency for calculating systick */ +#ifndef CPUFREQ_INITIAL +# define CPUFREQ_INITIAL CPU_FREQ +#endif -/* Max delay is limited by kernel tick interval + safety margin */ -#define SYSTICK_DELAY_MAX_US (1000000 / HZ / 2) -#define SYSTICK_DELAY_MAX_MS (SYSTICK_DELAY_MAX_US / 1000) +/* Tick interval in milliseconds */ +#ifndef SYSTICK_INTERVAL_INITIAL +# define SYSTICK_INTERVAL_INITIAL (1000 / HZ) +#endif + +/* Use EXT source which is equal to CPU frequency divided by 8 */ +#define SYSTICK_SOURCE BV_CM_SYSTICK_CSR_CLKSOURCE_EXT +#define SYSTICK_PRESCALER 8 + +/* Convert CPU frequency to number of systick ticks in 1 ms */ +#define CPUFREQ_TO_SYSTICK_PER_MS(f) \ + ((f) / (SYSTICK_PRESCALER * 1000)) + +/* SysTick related state */ +static uint32_t systick_per_ms = CPUFREQ_TO_SYSTICK_PER_MS(CPUFREQ_INITIAL); +static uint32_t systick_interval_in_ms = SYSTICK_INTERVAL_INITIAL; /* Base address of vector table */ extern char __vectors_arm[]; -static void systick_init(unsigned int interval_in_ms) -{ - reg_writef(CM_SYSTICK_RVR, VALUE(SYSTICK_PER_MS * interval_in_ms - 1)); - reg_writef(CM_SYSTICK_CVR, VALUE(0)); - reg_writef(CM_SYSTICK_CSR, CLKSOURCE_V(EXT), ENABLE(1)); -} - static void stm_enable_caches(void) { __discard_idcache(); @@ -56,6 +62,45 @@ static void stm_enable_caches(void) arm_isb(); } +static void stm32_recalc_systick_rvr(void) +{ + uint32_t ticks = systick_per_ms * systick_interval_in_ms; + + reg_writef(CM_SYSTICK_RVR, VALUE(ticks - 1)); +} + +static void stm32_set_systick_interval(uint32_t interval_in_ms) +{ + if (interval_in_ms != systick_interval_in_ms) + { + systick_interval_in_ms = interval_in_ms; + stm32_recalc_systick_rvr(); + } +} + +void stm32_systick_set_cpu_freq(uint32_t freq) +{ + uint32_t ticks_per_ms = CPUFREQ_TO_SYSTICK_PER_MS(freq); + + if (ticks_per_ms != systick_per_ms) + { + systick_per_ms = ticks_per_ms; + stm32_recalc_systick_rvr(); + } +} + +void stm32_systick_enable(void) +{ + stm32_recalc_systick_rvr(); + reg_writef(CM_SYSTICK_CVR, VALUE(0)); + reg_writef(CM_SYSTICK_CSR, CLKSOURCE(SYSTICK_SOURCE), ENABLE(1)); +} + +void stm32_systick_disable(void) +{ + reg_writef(CM_SYSTICK_CSR, ENABLE(0), TICKINT(0)); +} + void system_init(void) { #if defined(DEBUG) @@ -72,8 +117,8 @@ void system_init(void) /* Initialize system clocks */ stm_clock_init(); - /* TODO: move this */ - systick_init(1000/HZ); + /* Initialize systick */ + stm32_systick_enable(); /* Call target-specific initialization */ gpio_init(); @@ -99,7 +144,8 @@ void system_debug_enable(bool enable) void tick_start(unsigned int interval_in_ms) { - (void)interval_in_ms; + stm32_set_systick_interval(interval_in_ms); + stm32_systick_enable(); reg_writef(CM_SYSTICK_CSR, TICKINT(1)); } @@ -110,49 +156,35 @@ void systick_handler(void) } /* - * NOTE: This assumes that the CPU cannot be reclocked during an interrupt. - * If that happens, the systick interval and reload value would be modified - * to maintain the kernel tick interval and the code here will break. + * This makes two assumptions: + * + * 1. the CPU frequency must not change while udelay() is running; + * otherwise the delay time will be wrong. + * 2. interrupt handlers should not block execution for more than + * one systick interval; if this happens the delay may be much + * longer than necessary. */ -static void __udelay(uint32_t us) +void udelay(uint32_t us) { + uint32_t delay_ticks = (us * systick_per_ms / 1000); uint32_t start = reg_readf(CM_SYSTICK_CVR, VALUE); uint32_t max = reg_readf(CM_SYSTICK_RVR, VALUE); - uint32_t delay = us * SYSTICK_PER_US; - for (;;) + while (delay_ticks > 0) { uint32_t value = reg_readf(CM_SYSTICK_CVR, VALUE); uint32_t diff = start - value; if (value > start) diff += max; - if (diff >= delay) + + if (diff >= delay_ticks) break; + + delay_ticks -= diff; + start = value; } } -void udelay(uint32_t us) -{ - while (us > SYSTICK_DELAY_MAX_US) - { - __udelay(SYSTICK_DELAY_MAX_US); - us -= SYSTICK_DELAY_MAX_US; - } - - __udelay(us); -} - -void mdelay(uint32_t ms) -{ - while (ms > SYSTICK_DELAY_MAX_MS) - { - __udelay(SYSTICK_DELAY_MAX_MS * 1000); - ms -= SYSTICK_DELAY_MAX_MS; - } - - __udelay(ms * 1000); -} - void system_exception_wait(void) { #if defined(ECHO_R1) diff --git a/firmware/target/arm/stm32/system-target.h b/firmware/target/arm/stm32/system-target.h index fa3e206baa..d828b40e16 100644 --- a/firmware/target/arm/stm32/system-target.h +++ b/firmware/target/arm/stm32/system-target.h @@ -25,6 +25,15 @@ #include "cpucache-armv7m.h" #include +/* Enables the SysTick timer -- SysTick interrupt won't be enabled */ +void stm32_systick_enable(void); + +/* Disables the SysTick timer -- also disables SysTick interrupt */ +void stm32_systick_disable(void); + +/* Update the CPU frequency to recalibrate SysTick timer */ +void stm32_systick_set_cpu_freq(uint32_t freq); + /* Enable/disable debug clock domain during sleep mode. */ void system_debug_enable(bool enable); @@ -32,7 +41,7 @@ void system_debug_enable(bool enable); void gpio_init(void) INIT_ATTR; void fmc_init(void) INIT_ATTR; +/* Busy loop delay based on systick */ void udelay(uint32_t us); -void mdelay(uint32_t ms); #endif /* __STM32_SYSTEM_TARGET_H__ */ From 9471cec2ab1783722fb7e5a253d508e766ece0e5 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 15 Jan 2026 01:27:57 +0000 Subject: [PATCH 19/65] stm32h7: make cache initialization function public Change-Id: I460887977f18bfb411d33b6d4efc24ac04cb4050 --- firmware/target/arm/stm32/system-stm32h7.c | 4 ++-- firmware/target/arm/stm32/system-target.h | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/target/arm/stm32/system-stm32h7.c b/firmware/target/arm/stm32/system-stm32h7.c index 35c1d2e60d..410eab045a 100644 --- a/firmware/target/arm/stm32/system-stm32h7.c +++ b/firmware/target/arm/stm32/system-stm32h7.c @@ -52,7 +52,7 @@ static uint32_t systick_interval_in_ms = SYSTICK_INTERVAL_INITIAL; /* Base address of vector table */ extern char __vectors_arm[]; -static void stm_enable_caches(void) +void stm32_enable_caches(void) { __discard_idcache(); @@ -112,7 +112,7 @@ void system_init(void) reg_var(CM_SCB_VTOR) = (uint32_t)__vectors_arm; /* Enable CPU caches */ - stm_enable_caches(); + stm32_enable_caches(); /* Initialize system clocks */ stm_clock_init(); diff --git a/firmware/target/arm/stm32/system-target.h b/firmware/target/arm/stm32/system-target.h index d828b40e16..20a7621add 100644 --- a/firmware/target/arm/stm32/system-target.h +++ b/firmware/target/arm/stm32/system-target.h @@ -25,6 +25,9 @@ #include "cpucache-armv7m.h" #include +/* Enable CPU caches. Must be called after a reset. */ +void stm32_enable_caches(void) INIT_ATTR; + /* Enables the SysTick timer -- SysTick interrupt won't be enabled */ void stm32_systick_enable(void); From 386be9dfcc72a0c2b3786a7e1ee8ce55676c93bc Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 16 Jan 2026 14:17:02 +0000 Subject: [PATCH 20/65] stm32h7: refactor and simplify clock helper functions The clock helpers are only used for leaf clocks of single peripherals, which don't benefit from reference counting. Change-Id: Ica5685e7bc0fce621ae46f758f0ad0b1dcfb2789 --- firmware/SOURCES | 1 - firmware/target/arm/stm32/clock-stm32h7.c | 65 ------------------ firmware/target/arm/stm32/clock-stm32h7.h | 67 +++++++++---------- .../arm/stm32/echoplayer/clock-echoplayer.c | 59 ++++++---------- .../arm/stm32/echoplayer/clock-echoplayer.h | 30 +++++++++ .../arm/stm32/echoplayer/lcd-echoplayer.c | 5 +- .../arm/stm32/echoplayer/sdmmc-echoplayer.c | 3 +- firmware/target/arm/stm32/sdmmc-stm32h7.c | 8 +-- firmware/target/arm/stm32/sdmmc-stm32h7.h | 4 +- firmware/target/arm/stm32/spi-stm32h7.c | 10 +-- firmware/target/arm/stm32/spi-stm32h7.h | 4 +- 11 files changed, 101 insertions(+), 155 deletions(-) delete mode 100644 firmware/target/arm/stm32/clock-stm32h7.c create mode 100644 firmware/target/arm/stm32/echoplayer/clock-echoplayer.h diff --git a/firmware/SOURCES b/firmware/SOURCES index 6be052de85..9279307af5 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -2024,7 +2024,6 @@ target/arm/rk27xx/ihifi2/audio-ihifi800.c target/arm/stm32/crt0-stm32h7.S target/arm/stm32/vectors-stm32h7.S target/arm/stm32/adc-stm32h7.c -target/arm/stm32/clock-stm32h7.c target/arm/stm32/debug-stm32h7.c target/arm/stm32/gpio-stm32h7.c target/arm/stm32/i2c-stm32h7.c diff --git a/firmware/target/arm/stm32/clock-stm32h7.c b/firmware/target/arm/stm32/clock-stm32h7.c deleted file mode 100644 index ec1ff106b2..0000000000 --- a/firmware/target/arm/stm32/clock-stm32h7.c +++ /dev/null @@ -1,65 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2025 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 "clock-stm32h7.h" -#include "mutex.h" -#include "panic.h" -#include - -struct stm_clock_state -{ - struct mutex mutex; - uint8_t refcount[STM_NUM_CLOCKS]; -}; - -static struct stm_clock_state stm_clocks; - -void stm_clock_init(void) -{ - mutex_init(&stm_clocks.mutex); - - stm_target_clock_init(); -} - -void stm_clock_enable(enum stm_clock clock) -{ - mutex_lock(&stm_clocks.mutex); - - if (stm_clocks.refcount[clock] == UINT8_MAX) - panicf("%s: clock %d overflow", __func__, (int)clock); - - if (stm_clocks.refcount[clock]++ == 0) - stm_target_clock_enable(clock, true); - - mutex_unlock(&stm_clocks.mutex); -} - -void stm_clock_disable(enum stm_clock clock) -{ - mutex_lock(&stm_clocks.mutex); - - if (stm_clocks.refcount[clock] == 0) - panicf("%s: clock %d underflow", __func__, (int)clock); - - if (--stm_clocks.refcount[clock] == 0) - stm_target_clock_enable(clock, false); - - mutex_unlock(&stm_clocks.mutex); -} diff --git a/firmware/target/arm/stm32/clock-stm32h7.h b/firmware/target/arm/stm32/clock-stm32h7.h index 7e78d30ecf..e6dbd44c53 100644 --- a/firmware/target/arm/stm32/clock-stm32h7.h +++ b/firmware/target/arm/stm32/clock-stm32h7.h @@ -25,18 +25,15 @@ #include #include -enum stm_clock +struct stm32_clock { - STM_CLOCK_SPI1_KER, - STM_CLOCK_SPI2_KER, - STM_CLOCK_SPI3_KER, - STM_CLOCK_SPI4_KER, - STM_CLOCK_SPI5_KER, - STM_CLOCK_SPI6_KER, - STM_CLOCK_LTDC_KER, - STM_CLOCK_SDMMC1_KER, - STM_CLOCK_SDMMC2_KER, - STM_NUM_CLOCKS, + uint32_t frequency; + + uint32_t en_reg; + uint32_t en_bit; + + uint32_t lpen_reg; + uint32_t lpen_bit; }; /* @@ -47,43 +44,43 @@ enum stm_clock void stm_target_clock_init(void) INIT_ATTR; /* - * Callback to be implemented by the target when the hardware - * clock needs to be turned on or off. Clocks are internally - * reference counted so only the first / last user will change - * the hardware state. - * - * Only clocks that are actually used need to be implemented, - * and unless otherwise noted it is allowed for enable/disable - * to be a no-op if the clock is always enabled. + * Called by system_init() to setup target clocks. */ -void stm_target_clock_enable(enum stm_clock clock, bool enable); +static inline void stm_clock_init(void) +{ + stm_target_clock_init(); +} /* - * Callback to return a specific clock's frequency. For most - * peripherals the frequency must be known at initialization - * and not change afterwards; see peripheral drivers for the - * details, as their exact requirements may vary. + * Enables a clock by setting its enable bits in the RCC. */ -size_t stm_target_clock_get_frequency(enum stm_clock clock); +static inline void stm32_clock_enable(const struct stm32_clock *clk) +{ + if (clk->en_reg) + *(volatile uint32_t *)clk->en_reg |= clk->en_bit; + + if (clk->lpen_reg) + *(volatile uint32_t *)clk->lpen_reg |= clk->lpen_bit; +} /* - * Called from system_init(). Sets up internal book-keeping - * and then calls stm_target_clock_init(). + * Disables a clock in the RCC. */ -void stm_clock_init(void) INIT_ATTR; +static inline void stm32_clock_disable(const struct stm32_clock *clk) +{ + if (clk->en_reg) + *(volatile uint32_t *)clk->en_reg &= ~clk->en_bit; -/* - * Enable or disable a clock. Not safe to call from an IRQ handler. - */ -void stm_clock_enable(enum stm_clock clock); -void stm_clock_disable(enum stm_clock clock); + if (clk->lpen_reg) + *(volatile uint32_t *)clk->lpen_reg &= ~clk->lpen_bit; +} /* * Get a clock's frequency in Hz. */ -static inline size_t stm_clock_get_frequency(enum stm_clock clock) +static inline uint32_t stm32_clock_get_frequency(const struct stm32_clock *clk) { - return stm_target_clock_get_frequency(clock); + return clk->frequency; } #endif /* __CLOCK_STM32H7_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c index fcf26ae3dc..2ff829bfd8 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c @@ -180,43 +180,26 @@ void stm_target_clock_init(void) init_periph_clock(); } -void stm_target_clock_enable(enum stm_clock clock, bool enable) -{ - switch (clock) - { - case STM_CLOCK_SPI5_KER: - reg_writef(RCC_APB2ENR, SPI5EN(enable)); - reg_writef(RCC_APB2LPENR, SPI5EN(enable)); - break; +const struct stm32_clock sdmmc1_ker_clock = { + .frequency = PLL1Q_FREQ, + .en_reg = ITA_RCC_AHB3ENR, + .en_bit = BM_RCC_AHB3ENR_SDMMC1EN, + .lpen_reg = ITA_RCC_AHB3LPENR, + .lpen_bit = BM_RCC_AHB3LPENR_SDMMC1EN, +}; - case STM_CLOCK_LTDC_KER: - reg_writef(RCC_APB3ENR, LTDCEN(enable)); - reg_writef(RCC_APB3LPENR, LTDCEN(enable)); - break; +const struct stm32_clock ltdc_ker_clock = { + .frequency = LCD_DOTCLOCK_FREQ, + .en_reg = ITA_RCC_APB3ENR, + .en_bit = BM_RCC_APB3ENR_LTDCEN, + .lpen_reg = ITA_RCC_APB3LPENR, + .lpen_bit = BM_RCC_APB3ENR_LTDCEN, +}; - case STM_CLOCK_SDMMC1_KER: - reg_writef(RCC_AHB3ENR, SDMMC1EN(enable)); - reg_writef(RCC_AHB3LPENR, SDMMC1EN(enable)); - break; - - default: - panicf("%s: unsupported clock %d", __func__, (int)clock); - break; - } -} - -size_t stm_target_clock_get_frequency(enum stm_clock clock) -{ - switch (clock) - { - case STM_CLOCK_SPI5_KER: - return STM32_HSE_FREQ; - - case STM_CLOCK_SDMMC1_KER: - return PLL1Q_FREQ; - - default: - panicf("%s: unsupported clock %d", __func__, (int)clock); - return 0; - } -} +const struct stm32_clock spi5_ker_clock = { + .frequency = STM32_HSE_FREQ, + .en_reg = ITA_RCC_APB2ENR, + .en_bit = BM_RCC_APB2ENR_SPI5EN, + .lpen_reg = ITA_RCC_APB2LPENR, + .lpen_bit = BM_RCC_APB2ENR_SPI5EN, +}; diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h new file mode 100644 index 0000000000..cea54441c0 --- /dev/null +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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. + * + ****************************************************************************/ +#ifndef __CLOCK_ECHOPLAYER_H__ +#define __CLOCK_ECHOPLAYER_H__ + +#include "clock-stm32h7.h" + +extern struct stm32_clock sdmmc1_ker_clock; +extern struct stm32_clock ltdc_ker_clock; +extern struct stm32_clock spi5_ker_clock; + +#endif /* __CLOCK_ECHOPLAYER_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c index ea2febeff6..947342b1fb 100644 --- a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c @@ -22,6 +22,7 @@ #include "kernel.h" #include "lcd.h" #include "lcd-echoplayer.h" +#include "clock-echoplayer.h" #include "nvic-arm.h" #include "spi-stm32h7.h" #include "gpio-stm32h7.h" @@ -40,7 +41,7 @@ struct stm_spi_config spi_cfg = { .instance = ITA_SPI5, - .clock = STM_CLOCK_SPI5_KER, + .clock = &spi5_ker_clock, .freq = LCD_SPI_FREQ, .mode = STM_SPIMODE_HALF_DUPLEX, .proto = STM_SPIPROTO_MOTOROLA, @@ -61,7 +62,7 @@ struct stm_spi spi; static void init_ltdc(void) { /* Enable LTDC clock */ - stm_clock_enable(STM_CLOCK_LTDC_KER); + stm32_clock_enable(<dc_ker_clock); /* Set timing parameters */ const uint32_t hsw = LCD_HSW - 1; diff --git a/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c b/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c index 5120cca473..7a192bc914 100644 --- a/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c @@ -19,6 +19,7 @@ * ****************************************************************************/ #include "sdmmc_host.h" +#include "clock-echoplayer.h" #include "sdmmc-stm32h7.h" #include "gpio-stm32h7.h" #include "nvic-arm.h" @@ -95,7 +96,7 @@ static struct sdmmc_poll sdcard_poll; void sdmmc_host_target_init(void) { /* Initialize controller */ - stm32h7_sdmmc_init(&sdmmc1_ctl, ITA_SDMMC1, STM_CLOCK_SDMMC1_KER, + stm32h7_sdmmc_init(&sdmmc1_ctl, ITA_SDMMC1, &sdmmc1_ker_clock, stm32h7_reset_sdmmc1, NULL); nvic_enable_irq(NVIC_IRQN_SDMMC1); diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.c b/firmware/target/arm/stm32/sdmmc-stm32h7.c index 4147c0a95a..0fdad1156a 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.c +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.c @@ -80,7 +80,7 @@ void stm32h7_reset_sdmmc1(void) void stm32h7_sdmmc_init(struct stm32h7_sdmmc_controller *ctl, uint32_t instance, - enum stm_clock clock, + const struct stm32_clock *clock, void (*reset_sdmmc)(void), void (*vcc_enable)(bool)) { @@ -109,7 +109,7 @@ void stm32h7_sdmmc_set_power_enabled(void *controller, bool enabled) sleep(1); /* Bus clock is now needed, so enable kernel clock */ - stm_clock_enable(ctl->clock); + stm32_clock_enable(ctl->clock); /* Configure bus parameters */ stm32h7_sdmmc_set_bus_width(ctl, SDMMC_BUS_WIDTH_1BIT); @@ -136,7 +136,7 @@ void stm32h7_sdmmc_set_power_enabled(void *controller, bool enabled) * and the bus is powered down; some quick testing shows this * seems to be true. */ - stm_clock_disable(ctl->clock); + stm32_clock_disable(ctl->clock); /* Disable VCC */ if (ctl->vcc_enable) @@ -174,7 +174,7 @@ void stm32h7_sdmmc_set_bus_clock(void *controller, uint32_t clock) if (stm32h7_sdmmc_is_powered_off(ctl)) return; - size_t ker_freq = stm_clock_get_frequency(ctl->clock); + size_t ker_freq = stm32_clock_get_frequency(ctl->clock); size_t bus_freq = get_sdmmc_bus_freq(clock); if (!bus_freq) panicf("%s", __func__); diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.h b/firmware/target/arm/stm32/sdmmc-stm32h7.h index 95f3ab0cbc..7aeed251e2 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.h +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.h @@ -31,7 +31,7 @@ struct stm32h7_sdmmc_controller uint32_t regs; /* SDMMC kernel clock */ - enum stm_clock clock; + const struct stm32_clock *clock; /* Callback to reset SDMMC instance in RCC */ void (*reset_sdmmc)(void); @@ -59,7 +59,7 @@ void stm32h7_reset_sdmmc1(void); void stm32h7_sdmmc_init(struct stm32h7_sdmmc_controller *controller, uint32_t instance, - enum stm_clock clock, + const struct stm32_clock *clock, void (*reset_sdmmc)(void), void (*vcc_enable)(bool)); diff --git a/firmware/target/arm/stm32/spi-stm32h7.c b/firmware/target/arm/stm32/spi-stm32h7.c index 941310f90d..fc4d87eafc 100644 --- a/firmware/target/arm/stm32/spi-stm32h7.c +++ b/firmware/target/arm/stm32/spi-stm32h7.c @@ -37,7 +37,7 @@ static void stm_spi_enable(struct stm_spi *spi, bool hd_tx, size_t size) if (tsize > TSIZE_MAX) panicf("%s: tsize > TSIZE_MAX", __func__); - stm_clock_enable(spi->clock); + stm32_clock_enable(spi->clock); if (spi->set_cs) spi->set_cs(spi, true); @@ -59,7 +59,7 @@ static void stm_spi_disable(struct stm_spi *spi) if (spi->set_cs) spi->set_cs(spi, false); - stm_clock_disable(spi->clock); + stm32_clock_disable(spi->clock); } static uint32_t stm_spi_pack(const void **bufp, size_t *sizep) @@ -110,7 +110,7 @@ static void stm_spi_unpack(void **bufp, size_t *sizep, uint32_t data) static uint32_t stm_spi_calc_mbr(const struct stm_spi_config *config) { - size_t ker_freq = stm_clock_get_frequency(config->clock); + size_t ker_freq = stm32_clock_get_frequency(config->clock); for (uint32_t mbr = 0; mbr <= 7; mbr++) { if (ker_freq / (2 << mbr) <= config->freq) @@ -162,7 +162,7 @@ void stm_spi_init(struct stm_spi *spi, ftlevel *= 2; } - stm_clock_enable(spi->clock); + stm32_clock_enable(spi->clock); /* TODO: allow setting MBR here */ reg_writelf(spi->regs, SPI_CFG1, @@ -191,7 +191,7 @@ void stm_spi_init(struct stm_spi *spi, MIDI(0), MSSI(0)); - stm_clock_disable(spi->clock); + stm32_clock_disable(spi->clock); } int stm_spi_xfer(struct stm_spi *spi, size_t size, diff --git a/firmware/target/arm/stm32/spi-stm32h7.h b/firmware/target/arm/stm32/spi-stm32h7.h index dbc6a2fdaa..a6b0ef11d0 100644 --- a/firmware/target/arm/stm32/spi-stm32h7.h +++ b/firmware/target/arm/stm32/spi-stm32h7.h @@ -58,7 +58,7 @@ struct stm_spi_config * such the kernel clock should not be changed after * the SPI peripheral is initialized. */ - enum stm_clock clock; + const struct stm32_clock *clock; size_t freq; enum stm_spi_mode mode; @@ -77,7 +77,7 @@ struct stm_spi_config struct stm_spi { uint32_t regs; - enum stm_clock clock; + const struct stm32_clock *clock; enum stm_spi_mode mode; stm_spi_set_cs_t set_cs; uint32_t frame_size; From eea0c128f48330850002f97ab589ab843f43a083 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 16 Jan 2026 14:35:57 +0000 Subject: [PATCH 21/65] echoplayer: move system_init() out of generic stm32 code With only one target, it doesn't make sense to have a common system_init() yet. Change-Id: I0f6d37709d60bb309fb16ecb9b0870297a189cc4 --- .../arm/stm32/echoplayer/system-echoplayer.c | 38 +++++++++++++++++-- firmware/target/arm/stm32/system-stm32h7.c | 36 ------------------ firmware/target/arm/stm32/system-target.h | 4 -- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index c624569241..e2fb89e719 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -19,9 +19,12 @@ * ****************************************************************************/ #include "system.h" +#include "button.h" +#include "clock-stm32h7.h" #include "gpio-stm32h7.h" -#include "regs/stm32h743/rcc.h" #include "regs/stm32h743/fmc.h" +#include "regs/stm32h743/rcc.h" +#include "regs/cortex-m/cm_scb.h" #define F_INPUT GPIOF_INPUT(GPIO_PULL_DISABLED) #define F_INPUT_PU GPIOF_INPUT(GPIO_PULL_UP) @@ -117,7 +120,7 @@ static const struct pingroup_setting pingroups[] = { STM_DEFPINS(GPIO_I, 0x06e7, F_LCD_AF14), }; -void gpio_init(void) +INIT_ATTR static void gpio_init(void) { /* Enable clocks for all used GPIO banks */ reg_writef(RCC_AHB4ENR, @@ -135,7 +138,7 @@ void gpio_init(void) pingroups, ARRAYLEN(pingroups)); } -void fmc_init(void) +INIT_ATTR static void fmc_init(void) { /* configure clock */ reg_writef(RCC_D1CCIPR, FMCSEL_V(AHB)); @@ -181,3 +184,32 @@ void fmc_init(void) */ reg_writef(FMC_SDRTR, REIE(0), COUNT(917), CRE(0)); } + +void system_init(void) +{ + /* Set vector table address */ + extern char __vectors_arm[]; + reg_var(CM_SCB_VTOR) = (uint32_t)__vectors_arm; + +#if defined(DEBUG) + system_debug_enable(true); +#endif + + /* Enable CPU cache */ + stm32_enable_caches(); + + /* Initialize system clocks */ + stm_clock_init(); + + /* Enable systick early due to udelay() needed for FMC init */ + stm32_systick_enable(); + + /* Configure GPIOs and start FMC */ + gpio_init(); + fmc_init(); +} + +void system_exception_wait(void) +{ + while (button_read_device() != (BUTTON_POWER | BUTTON_START)); +} diff --git a/firmware/target/arm/stm32/system-stm32h7.c b/firmware/target/arm/stm32/system-stm32h7.c index 410eab045a..06639664b3 100644 --- a/firmware/target/arm/stm32/system-stm32h7.c +++ b/firmware/target/arm/stm32/system-stm32h7.c @@ -49,9 +49,6 @@ static uint32_t systick_per_ms = CPUFREQ_TO_SYSTICK_PER_MS(CPUFREQ_INITIAL); static uint32_t systick_interval_in_ms = SYSTICK_INTERVAL_INITIAL; -/* Base address of vector table */ -extern char __vectors_arm[]; - void stm32_enable_caches(void) { __discard_idcache(); @@ -101,30 +98,6 @@ void stm32_systick_disable(void) reg_writef(CM_SYSTICK_CSR, ENABLE(0), TICKINT(0)); } -void system_init(void) -{ -#if defined(DEBUG) - system_debug_enable(true); -#endif - - /* Ensure IRQs are disabled and set vector table address */ - disable_irq(); - reg_var(CM_SCB_VTOR) = (uint32_t)__vectors_arm; - - /* Enable CPU caches */ - stm32_enable_caches(); - - /* Initialize system clocks */ - stm_clock_init(); - - /* Initialize systick */ - stm32_systick_enable(); - - /* Call target-specific initialization */ - gpio_init(); - fmc_init(); -} - void system_debug_enable(bool enable) { /* @@ -185,15 +158,6 @@ void udelay(uint32_t us) } } -void system_exception_wait(void) -{ -#if defined(ECHO_R1) - while (button_read_device() != (BUTTON_POWER | BUTTON_START)); -#else - while (1); -#endif -} - int system_memory_guard(int newmode) { /* TODO -- maybe use MPU here to give some basic protection */ diff --git a/firmware/target/arm/stm32/system-target.h b/firmware/target/arm/stm32/system-target.h index 20a7621add..ac0fab226a 100644 --- a/firmware/target/arm/stm32/system-target.h +++ b/firmware/target/arm/stm32/system-target.h @@ -40,10 +40,6 @@ void stm32_systick_set_cpu_freq(uint32_t freq); /* Enable/disable debug clock domain during sleep mode. */ void system_debug_enable(bool enable); -/* Implemented by the target -- can be a no-op if not needed */ -void gpio_init(void) INIT_ATTR; -void fmc_init(void) INIT_ATTR; - /* Busy loop delay based on systick */ void udelay(uint32_t us); From 65b97917ca9c14dbc7d92db6e2bb04106a09d248 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 16 Jan 2026 15:31:18 +0000 Subject: [PATCH 22/65] echoplayer: replace stm32 clock init with target specific init Change-Id: Ib858f95b4cedba261ea669d3339ea1497e970982 --- firmware/target/arm/stm32/clock-stm32h7.h | 15 --------------- .../arm/stm32/echoplayer/clock-echoplayer.c | 14 +++++++------- .../arm/stm32/echoplayer/clock-echoplayer.h | 2 ++ .../arm/stm32/echoplayer/system-echoplayer.c | 4 ++-- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/firmware/target/arm/stm32/clock-stm32h7.h b/firmware/target/arm/stm32/clock-stm32h7.h index e6dbd44c53..a84b09e519 100644 --- a/firmware/target/arm/stm32/clock-stm32h7.h +++ b/firmware/target/arm/stm32/clock-stm32h7.h @@ -36,21 +36,6 @@ struct stm32_clock uint32_t lpen_bit; }; -/* - * Implemented by the target to initialize all oscillators, - * system, CPU, and bus clocks that need to be enabled from - * early boot. - */ -void stm_target_clock_init(void) INIT_ATTR; - -/* - * Called by system_init() to setup target clocks. - */ -static inline void stm_clock_init(void) -{ - stm_target_clock_init(); -} - /* * Enables a clock by setting its enable bits in the RCC. */ diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c index 2ff829bfd8..d42c12ae5f 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c @@ -32,14 +32,14 @@ /* Flag to use VOS0 */ #define STM32H743_USE_VOS0 (CPU_FREQ > 400000000) -static void init_hse(void) +INIT_ATTR static void init_hse(void) { reg_writef(RCC_CR, HSEON(1)); while (!reg_readf(RCC_CR, HSERDY)); } -static void init_pll(void) +INIT_ATTR static void init_pll(void) { /* For simplicity, PLL parameters are hardcoded */ _Static_assert(STM32_HSE_FREQ == 24000000, @@ -97,7 +97,7 @@ static void init_pll(void) while (!reg_readf(RCC_CR, PLL3RDY)); } -static void init_vos(void) +INIT_ATTR static void init_vos(void) { reg_writef(PWR_D3CR, VOS_V(VOS1)); while (!reg_readf(PWR_D3CR, VOSRDY)); @@ -114,7 +114,7 @@ static void init_vos(void) } } -static void init_system_clock(void) +INIT_ATTR static void init_system_clock(void) { /* Enable HCLK /2 divider (CPU is at 480 MHz, HCLK limit is 240 MHz) */ reg_writef(RCC_D1CFGR, HPRE(8)); @@ -134,7 +134,7 @@ static void init_system_clock(void) while (reg_readf(FLASH_ACR, LATENCY) != 4); } -static void init_lse(void) +INIT_ATTR static void init_lse(void) { /* * Skip if LSE and RTC are already enabled. @@ -161,7 +161,7 @@ static void init_lse(void) reg_writef(PWR_CR1, DBP(0)); } -static void init_periph_clock(void) +INIT_ATTR static void init_periph_clock(void) { reg_writef(RCC_D1CCIPR, SDMMCSEL_V(PLL1Q)); reg_writef(RCC_D2CCIP1R, SPI45SEL_V(HSE)); @@ -170,7 +170,7 @@ static void init_periph_clock(void) reg_writef(RCC_AHB3LPENR, AXISRAMEN(1)); } -void stm_target_clock_init(void) +void echoplayer_clock_init(void) { init_hse(); init_pll(); diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h index cea54441c0..e6f3624537 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h @@ -23,6 +23,8 @@ #include "clock-stm32h7.h" +void echoplayer_clock_init(void) INIT_ATTR; + extern struct stm32_clock sdmmc1_ker_clock; extern struct stm32_clock ltdc_ker_clock; extern struct stm32_clock spi5_ker_clock; diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index e2fb89e719..6a980f983e 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -20,8 +20,8 @@ ****************************************************************************/ #include "system.h" #include "button.h" -#include "clock-stm32h7.h" #include "gpio-stm32h7.h" +#include "clock-echoplayer.h" #include "regs/stm32h743/fmc.h" #include "regs/stm32h743/rcc.h" #include "regs/cortex-m/cm_scb.h" @@ -199,7 +199,7 @@ void system_init(void) stm32_enable_caches(); /* Initialize system clocks */ - stm_clock_init(); + echoplayer_clock_init(); /* Enable systick early due to udelay() needed for FMC init */ stm32_systick_enable(); From 4e4d5ac25bd7df5539962f4fd18af0a1e8c1fc48 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 24 Jan 2026 09:13:49 -0500 Subject: [PATCH 23/65] Fix yellow in echoplayer bootloader Regression introduced in 7f0bc4bd95 Change-Id: I1341319d23a2813b8473e3fceb3370bf6371ef86 --- bootloader/echoplayer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index 4032279630..862b2d2346 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -30,6 +30,7 @@ #include "disk.h" #include "file_internal.h" #include "usb.h" +#include "common.h" /* For show_logo() */ static bool is_usb_connected = false; From 6035b1fc1b4279e5dd40c58a89f1c7b14661ea9c Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Sat, 24 Jan 2026 09:13:24 -0500 Subject: [PATCH 24/65] [lua][BUGFIX] splash_scroller FS#13753, 13754 unicode + default font/fg/bg FS#13753 - Rocklua error splash don't display part or full filename when it is non-ASCII FS#13754 - Rocklua error splash don't reset gui settings (bg, fg and font) when using signed char unicode codepoints can show up as < '0' code was looking to remove control chars < ' ' which also removed unicode code points in the process Change-Id: I48074a7854f2bee195ca554e1f86505fe5567db6 --- apps/plugins/lua/rockaux.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/plugins/lua/rockaux.c b/apps/plugins/lua/rockaux.c index 7eb5ff0d28..e82471ddaf 100644 --- a/apps/plugins/lua/rockaux.c +++ b/apps/plugins/lua/rockaux.c @@ -27,7 +27,7 @@ #ifdef PLUGIN #include "plugin.h" #include "lib/pluginlib_actions.h" -#define lcd_getstringsize rb->lcd_getstringsize +#define font_getstringsize rb->font_getstringsize #define lcd_clear_display rb->lcd_clear_display #define lcd_putsxy rb->lcd_putsxy #define lcd_update rb->lcd_update @@ -66,7 +66,13 @@ int splash_scroller(int timeout, const char* str) if (!str) str = "[nil]"; int w, ch_w, ch_h; - lcd_getstringsize("W", &ch_w, &ch_h); + + struct viewport vp; + rb->viewport_set_defaults(&vp, SCREEN_MAIN); + struct viewport *last_vp = rb->lcd_set_viewport(&vp); + int fontnum = vp.font; + + font_getstringsize("W", &ch_w, &ch_h, fontnum); const int max_w = LCD_WIDTH - (ch_w * 2); const int max_lines = LCD_HEIGHT / ch_h - 1; @@ -103,13 +109,13 @@ int splash_scroller(int timeout, const char* str) line[linepos] = ' '; beep_play(1000, HZ, 1000); } - else if (ch[0] < ' ') /* Dont copy control characters */ + else if (ch[0] < ' ' && ch[0] > '\0') /* Dont copy control characters */ line[linepos] = (linepos == 0) ? '\0' : ' '; else line[linepos] = ch[0]; line[linepos + 1] = '\0'; /* terminate to check text extent */ - lcd_getstringsize(line, &w, NULL); + font_getstringsize(line, &w, NULL, fontnum); /* try to not split in middle of words */ if (w + wrap_thresh >= max_w && @@ -174,6 +180,8 @@ int splash_scroller(int timeout, const char* str) else break; } + rb->lcd_set_viewport(last_vp); + return action; } From 319fdcc506791e5c5d3a2c8d080de1f6b803ff67 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 15 Jan 2026 00:07:50 +0000 Subject: [PATCH 25/65] echoplayer: implement reboot using RTC_OUT pin Change-Id: Ibeda362030c2885abe1366fb3256e925ea27538c --- firmware/reggen/stm32h743.regs | 4 ++ .../arm/stm32/echoplayer/power-echoplayer.c | 31 ++++++++++---- .../arm/stm32/echoplayer/system-echoplayer.c | 41 +++++++++++++++++++ .../arm/stm32/echoplayer/system-echoplayer.h | 32 +++++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 firmware/target/arm/stm32/echoplayer/system-echoplayer.h diff --git a/firmware/reggen/stm32h743.regs b/firmware/reggen/stm32h743.regs index c6c25cdf4a..df8d7235ae 100644 --- a/firmware/reggen/stm32h743.regs +++ b/firmware/reggen/stm32h743.regs @@ -587,6 +587,10 @@ RTC @ 0x58004000 : block { 14 00 PREDIV_S } + WUTR @ 0x14 : reg { + 15 00 VALUE + } + WPR @ 0x24 : reg { 07 00 KEY : { 0xCA = KEY1; 0x53 = KEY2 } } diff --git a/firmware/target/arm/stm32/echoplayer/power-echoplayer.c b/firmware/target/arm/stm32/echoplayer/power-echoplayer.c index 667cd99731..63bf173d33 100644 --- a/firmware/target/arm/stm32/echoplayer/power-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/power-echoplayer.c @@ -21,6 +21,8 @@ #include "power.h" #include "mutex.h" #include "gpio-stm32h7.h" +#include "system-echoplayer.h" +#include "regs/cortex-m/cm_scb.h" static struct mutex power_1v8_lock; static int power_1v8_refcount; @@ -68,23 +70,34 @@ void power_init(void) void power_off(void) { + /* + * Disable power and reset to the bootloader immediately. + * The system can't really be powered off as long as USB + * is plugged or the power button is pressed -- it's the + * bootloader's job to monitor those inputs and decide + * when the system needs to "really" power on. + */ gpio_set_level(GPIO_CPU_POWER_ON, 0); - /* TODO: reset to bootloader if USB is plugged in */ - while (1) - core_idle(); + reg_writef(CM_SCB_AIRCR, VECTKEY_V(KEY), SYSRESETREQ(1)); + while (1); } void system_reboot(void) { /* - * TODO: support reboot - * - * For R1-Rev1 PCBs doing a CPU reset will cut power when - * running on battery (because cpu_power_on is no longer - * being driven high). The RTC alarm could be used to wake - * the system instead. + * Disable IRQs to ensure an errant panic can't disturb + * the RTC reconfig. */ + disable_irq(); + + /* + * Configure RTC_OUT pin to keep power enabled during + * reset and then do a normal power off. If this fails + * to reset back to the bootloader then the RTC_OUT + * pin will go low automatically after some timeout. + */ + echoplayer_set_rtcout_mode(ECHOPLAYER_RTCOUT_REBOOT); power_off(); } diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index 6a980f983e..3686ffb55b 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -22,8 +22,11 @@ #include "button.h" #include "gpio-stm32h7.h" #include "clock-echoplayer.h" +#include "system-echoplayer.h" #include "regs/stm32h743/fmc.h" +#include "regs/stm32h743/pwr.h" #include "regs/stm32h743/rcc.h" +#include "regs/stm32h743/rtc.h" #include "regs/cortex-m/cm_scb.h" #define F_INPUT GPIOF_INPUT(GPIO_PULL_DISABLED) @@ -207,9 +210,47 @@ void system_init(void) /* Configure GPIOs and start FMC */ gpio_init(); fmc_init(); + + /* Disable RTC_OUT pin */ + echoplayer_set_rtcout_mode(ECHOPLAYER_RTCOUT_DISABLED); } void system_exception_wait(void) { while (button_read_device() != (BUTTON_POWER | BUTTON_START)); } + +void echoplayer_set_rtcout_mode(enum echoplayer_rtcout_mode mode) +{ + reg_writef(RCC_APB4ENR, RTCAPBEN(1)); + reg_writef(PWR_CR1, DBP(1)); + + reg_writef(RTC_WPR, KEY_V(KEY1)); + reg_writef(RTC_WPR, KEY_V(KEY2)); + + reg_writef(RTC_OR, OUT_RMP(0), ALARM_TYPE_V(PUSH_PULL)); + + switch (mode) + { + case ECHOPLAYER_RTCOUT_REBOOT: + /* + * Use the inverted wakeup timer output to keep power + * enabled during reset. If, somehow, the system does + * not reset properly then the wakeup timer will drive + * the RTC_OUT pin low after 1 second and cut power. + */ + while (!reg_readf(RTC_ISR, WUTWF)); + reg_writef(RTC_ISR, WUTF(0)); + reg_writef(RTC_WUTR, VALUE(STM32_LSE_FREQ / 8)); + reg_writef(RTC_CR, OSEL_V(WAKEUP), POL(1), WUCKSEL(0), WUTE(1)); + break; + + case ECHOPLAYER_RTCOUT_DISABLED: + default: + reg_writef(RTC_CR, OSEL_V(DISABLED), POL(0), WUTE(0)); + break; + } + + reg_writef(RTC_WPR, KEY(0)); + reg_writef(PWR_CR1, DBP(0)); +} diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.h b/firmware/target/arm/stm32/echoplayer/system-echoplayer.h new file mode 100644 index 0000000000..3f93d84e30 --- /dev/null +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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. + * + ****************************************************************************/ +#ifndef __SYSTEM_ECHOPLAYER_H__ +#define __SYSTEM_ECHOPLAYER_H__ + +enum echoplayer_rtcout_mode +{ + ECHOPLAYER_RTCOUT_DISABLED, + ECHOPLAYER_RTCOUT_REBOOT, +}; + +void echoplayer_set_rtcout_mode(enum echoplayer_rtcout_mode mode); + +#endif /* __SYSTEM_ECHOPLAYER_H__ */ From 86c975ee88526c2d5e4f5f9b6935a8dc218f4034 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 16 Jan 2026 23:32:42 +0000 Subject: [PATCH 26/65] echoplayer: set cpu_power_on pin high as early as possible Change-Id: I6b7343cc81973c4c0a14396b2ecd622fc1c65d30 --- .../arm/stm32/echoplayer/system-echoplayer.c | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index 3686ffb55b..15ca8a81c9 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -69,7 +69,6 @@ static const struct gpio_setting gpios[] = { STM_DEFGPIO(GPIO_BUTTON_VOL_DOWN, F_INPUT_PU), STM_DEFGPIO(GPIO_BUTTON_POWER, F_INPUT_PD), STM_DEFGPIO(GPIO_BUTTON_HOLD, F_INPUT_PU), - STM_DEFGPIO(GPIO_CPU_POWER_ON, F_OUT_LS(1)), /* active high */ STM_DEFGPIO(GPIO_POWER_1V8, F_OUT_LS(0)), /* active high */ STM_DEFGPIO(GPIO_CODEC_AVDD_EN, F_OUT_LS(1)), /* active low */ STM_DEFGPIO(GPIO_CODEC_DVDD_EN, F_OUT_LS(1)), /* active low */ @@ -123,24 +122,6 @@ static const struct pingroup_setting pingroups[] = { STM_DEFPINS(GPIO_I, 0x06e7, F_LCD_AF14), }; -INIT_ATTR static void gpio_init(void) -{ - /* Enable clocks for all used GPIO banks */ - reg_writef(RCC_AHB4ENR, - GPIOAEN(1), GPIOBEN(1), GPIOCEN(1), GPIODEN(1), - GPIOEEN(1), GPIOFEN(1), GPIOGEN(1), GPIOHEN(1), GPIOIEN(1)); - - /* - * NOTE: I think it's possible to disable clocks for the banks which - * we don't need to access at runtime because these are only clocking - * register access. Probably a micro-optimization but it supposedly - * does save a few uA/MHz. - */ - - gpio_configure_all(gpios, ARRAYLEN(gpios), - pingroups, ARRAYLEN(pingroups)); -} - INIT_ATTR static void fmc_init(void) { /* configure clock */ @@ -190,6 +171,25 @@ INIT_ATTR static void fmc_init(void) void system_init(void) { + /* Enable clocks for all used GPIO banks */ + reg_writef(RCC_AHB4ENR, + GPIOAEN(1), GPIOBEN(1), GPIOCEN(1), GPIODEN(1), + GPIOEEN(1), GPIOFEN(1), GPIOGEN(1), GPIOHEN(1), GPIOIEN(1)); + + /* + * NOTE: I think it's possible to disable clocks for the banks which + * we don't need to access at runtime because these are only clocking + * register access. Probably a micro-optimization but it supposedly + * does save a few uA/MHz. + */ + + /* + * Set cpu_power_on high as early as possible to + * ensure we won't brown out if the power button + * isn't pressed. + */ + gpio_configure_single(GPIO_CPU_POWER_ON, F_OUT_LS(1)); + /* Set vector table address */ extern char __vectors_arm[]; reg_var(CM_SCB_VTOR) = (uint32_t)__vectors_arm; @@ -208,7 +208,8 @@ void system_init(void) stm32_systick_enable(); /* Configure GPIOs and start FMC */ - gpio_init(); + gpio_configure_all(gpios, ARRAYLEN(gpios), + pingroups, ARRAYLEN(pingroups)); fmc_init(); /* Disable RTC_OUT pin */ From 025d641d1f9eddadd8f9f382119e3c95ea91c477 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Fri, 16 Jan 2026 23:54:45 +0000 Subject: [PATCH 27/65] echoplayer: detect boot reason during system_init Change-Id: I6fdc16e7476044b43e11d3f63d971bd8de860c58 --- firmware/reggen/stm32h743.regs | 14 +++++++++++++ .../arm/stm32/echoplayer/system-echoplayer.c | 20 +++++++++++++++++++ .../arm/stm32/echoplayer/system-echoplayer.h | 9 +++++++++ 3 files changed, 43 insertions(+) diff --git a/firmware/reggen/stm32h743.regs b/firmware/reggen/stm32h743.regs index df8d7235ae..16584f0851 100644 --- a/firmware/reggen/stm32h743.regs +++ b/firmware/reggen/stm32h743.regs @@ -254,6 +254,20 @@ RCC @ 0x58024400 : block { 0 LSION } + RSR @ 0xd0 : reg { + 30 LPWRRSTF + 28 WWDG1RSTF + 26 IWDGRSTF + 24 SFTRSTF + 23 PORRSTF + 22 PINRSTF + 21 BORRSTF + 20 D2RSTF + 19 D1RSTF + 17 CPURSTF + 16 RMVF + } + AHB1ENR @ 0xd8 : reg { 28 USB2OTGHSULPIEN 27 USB2OTGHSEN diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index 15ca8a81c9..f9a2763205 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -54,6 +54,8 @@ # define F_MCO1 GPIOF_ANALOG() #endif +enum echoplayer_boot_reason echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_NORMAL; + static const struct gpio_setting gpios[] = { STM_DEFGPIO(GPIO_BUTTON_A, F_INPUT_PU), STM_DEFGPIO(GPIO_BUTTON_B, F_INPUT_PU), @@ -212,6 +214,24 @@ void system_init(void) pingroups, ARRAYLEN(pingroups)); fmc_init(); + /* Read & clear reset source */ + uint32_t rsr = reg_var(RCC_RSR); + reg_assignf(RCC_RSR, RMVF(1)); + + /* + * Determine boot reason -- SFTRST means a software reset + * occurred, which may be a reboot or a power off + */ + if (reg_vreadf(rsr, RCC_RSR, SFTRSTF)) + { + reg_writef(RCC_APB4ENR, RTCAPBEN(1)); + + if (reg_readf(RTC_CR, WUTE)) + echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_REBOOT; + else + echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_POWEROFF; + } + /* Disable RTC_OUT pin */ echoplayer_set_rtcout_mode(ECHOPLAYER_RTCOUT_DISABLED); } diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.h b/firmware/target/arm/stm32/echoplayer/system-echoplayer.h index 3f93d84e30..abfd96c145 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.h +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.h @@ -27,6 +27,15 @@ enum echoplayer_rtcout_mode ECHOPLAYER_RTCOUT_REBOOT, }; +enum echoplayer_boot_reason +{ + ECHOPLAYER_BOOT_REASON_NORMAL, + ECHOPLAYER_BOOT_REASON_SW_POWEROFF, + ECHOPLAYER_BOOT_REASON_SW_REBOOT, +}; + void echoplayer_set_rtcout_mode(enum echoplayer_rtcout_mode mode); +extern enum echoplayer_boot_reason echoplayer_boot_reason; + #endif /* __SYSTEM_ECHOPLAYER_H__ */ From 1afa2ca50d21cb4a234caece29ed3ef8ae848482 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 18 Jan 2026 15:30:03 +0000 Subject: [PATCH 28/65] echoplayer: implement LCD enable and shutdown This also gets rid of the long startup delays which were leftovers from debugging. Change-Id: Iee9fec1fb210a006a1af53bf47535d7ba2ffa904 --- firmware/export/config/echor1.h | 1 + .../arm/stm32/echoplayer/lcd-echoplayer.c | 184 +++++++++++++----- 2 files changed, 139 insertions(+), 46 deletions(-) diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 6a6bba342f..41149258c9 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -36,6 +36,7 @@ #define HAVE_LCD_COLOR #define HAVE_LCD_BITMAP #define HAVE_LCD_ENABLE +#define HAVE_LCD_SHUTDOWN /* Backlight defines */ #define HAVE_BACKLIGHT diff --git a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c index 947342b1fb..0800f3aad8 100644 --- a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c @@ -31,6 +31,9 @@ #include "regs/stm32h743/spi.h" #include "regs/stm32h743/ltdc.h" +#define MS_TO_TICKS(x) \ + (((x) + (1000 / HZ - 1)) / (1000 / HZ)) + /* * ILI9342C specifies 10 MHz max * @@ -39,6 +42,14 @@ */ #define LCD_SPI_FREQ 12000000 +#define ili_cmd(cmd, ...) \ + do { \ + uint16_t arr[] = {cmd, __VA_ARGS__}; \ + for (size_t i = 1; i < ARRAYLEN(arr); ++i) \ + arr[i] |= 0x100; \ + stm_spi_transmit(&spi, arr, sizeof(arr)); \ + } while (0) + struct stm_spi_config spi_cfg = { .instance = ITA_SPI5, .clock = &spi5_ker_clock, @@ -51,15 +62,16 @@ struct stm_spi_config spi_cfg = { struct stm_spi spi; -#define ili_cmd(cmd, ...) \ - do { \ - uint16_t arr[] = {cmd, __VA_ARGS__}; \ - for (size_t i = 1; i < ARRAYLEN(arr); ++i) \ - arr[i] |= 0x100; \ - stm_spi_transmit(&spi, arr, sizeof(arr)); \ - } while (0) +enum lcd_controller_state +{ + RESET, /* Controller in hardware reset, LTDC disabled */ + SLEEP, /* Controller in sleep-in mode, LTDC disabled */ + AWAKE, /* Controller in sleep-out mode, LTDC enabled */ +}; -static void init_ltdc(void) +static enum lcd_controller_state lcd_controller_state = RESET; + +static void enable_ltdc(void) { /* Enable LTDC clock */ stm32_clock_enable(<dc_ker_clock); @@ -101,63 +113,143 @@ static void init_ltdc(void) reg_writef(LTDC_GCR, LTDCEN(1)); } +static void disable_ltdc(void) +{ + reg_writef(LTDC_GCR, LTDCEN(0)); + + stm32_clock_disable(<dc_ker_clock); +} + +/* TODO: thread safety, is a mutex needed here? */ + +static void reset_lcd(void) +{ + if (lcd_controller_state != RESET) + { + if (lcd_controller_state == AWAKE) + disable_ltdc(); + + /* Must be >= 10 us to take effect */ + gpio_set_level(GPIO_LCD_RESET, 0); + udelay(10); + + lcd_controller_state = RESET; + } +} + +static void sleep_lcd(void) +{ + if (lcd_controller_state == AWAKE) + { + /* + * Send sleep in command -- empirically this seems to + * require a delay of 10ms before disabling the LTDC. + * The clock output appears to be necessary to fully + * blank the screen before entering sleep mode. + * + * Sometimes there is an effect where the screen is + * only partly blanked and only later fully blanked, + * which goes away with a 20ms delay. + */ + ili_cmd(0x10); + sleep(MS_TO_TICKS(20)); + + /* Disable LTDC */ + disable_ltdc(); + + lcd_controller_state = SLEEP; + } +} + +static void wake_lcd(void) +{ + if (lcd_controller_state == RESET) + { + /* Release reset line */ + gpio_set_level(GPIO_LCD_RESET, 1); + sleep(MS_TO_TICKS(5)); + + /* Memory access control (X/Y invert, BGR panel) */ + ili_cmd(0x36, 0xc8); + + /* Pixel format set (18bpp for RGB bus, 16bpp for SPI bus) */ + ili_cmd(0x3a, 0x65); + + /* Send set EXTC command to allow configuring RGB interface */ + ili_cmd(0xc8, 0xff, 0x93, 0x42); + + /* + * Enable RGB interface transferring to internal GRAM. + * + * Direct to shift register mode doesn't work; for one, the + * framebuffer doesn't get transferred properly which might + * just be timing issues. Two, the display is horizontally + * flipped and there doesn't seem to be a way to change it + * in the shift register mode. + */ + ili_cmd(0xb0, 0xc0); + ili_cmd(0xf6, 0x01, 0x00, 0x06); + + /* Display ON */ + ili_cmd(0x29); + } + + if (lcd_controller_state != AWAKE) + { + /* Sync framebuffer & enable LTDC output */ + commit_dcache(); + enable_ltdc(); + + /* Sleep out command */ + ili_cmd(0x11); + sleep(MS_TO_TICKS(5)); + + lcd_controller_state = AWAKE; + send_event(LCD_EVENT_ACTIVATION, NULL); + } +} + void lcd_init_device(void) { /* Configure SPI bus */ stm_spi_init(&spi, &spi_cfg); nvic_enable_irq(NVIC_IRQN_SPI5); - /* Enable LCD controller */ - init_ltdc(); - - /* Ensure controller is reset */ - gpio_set_level(GPIO_LCD_RESET, 0); - sleep(12); - - gpio_set_level(GPIO_LCD_RESET, 1); - sleep(12); - - /* Sleep out */ - ili_cmd(0x11); - sleep(12); - - /* memory access control (X/Y invert, BGR panel) */ - ili_cmd(0x36, 0xc8); - - /* pixel format set (18bpp for RGB bus, 16bpp for SPI bus) */ - ili_cmd(0x3a, 0x65); - - /* send set EXTC command to allow configuring RGB interface */ - ili_cmd(0xc8, 0xff, 0x93, 0x42); - - /* - * Enable RGB interface transferring to internal GRAM. - * - * Direct to shift register mode doesn't work; for one, the - * framebuffer doesn't get transferred properly which might - * just be timing issues. Two, the display is horizontally - * flipped and there doesn't seem to be a way to change it - * in the shift register mode. - */ - ili_cmd(0xb0, 0xc0); - ili_cmd(0xf6, 0x01, 0x00, 0x06); - - /* display ON */ - ili_cmd(0x29); + /* Enable LTDC and LCD controller */ + wake_lcd(); } bool lcd_active(void) { - return true; + return lcd_controller_state == AWAKE; +} + +void lcd_enable(bool enable) +{ + if (enable) + wake_lcd(); + else + sleep_lcd(); +} + +void lcd_shutdown(void) +{ + reset_lcd(); } void lcd_update(void) { + if (!lcd_active()) + return; + commit_dcache(); } void lcd_update_rect(int x, int y, int width, int height) { + if (!lcd_active()) + return; + if (x < 0) x = 0; else if (x >= LCD_WIDTH) From 721bfac475bce05fae6ea62a80bdd12598c4ab2b Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 18 Jan 2026 19:43:18 +0000 Subject: [PATCH 29/65] echoplayer: don't wake LCD automatically in bootloader Normally the bootloader won't enable the LCD, but we still want to call lcd_init() at startup to keep the code simple. Change-Id: I866ecd7c81b6c5e6acdd57f5d7680400df3f54f4 --- firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c index 0800f3aad8..bd7ead842c 100644 --- a/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/lcd-echoplayer.c @@ -215,8 +215,10 @@ void lcd_init_device(void) stm_spi_init(&spi, &spi_cfg); nvic_enable_irq(NVIC_IRQN_SPI5); +#ifndef BOOTLOADER /* Enable LTDC and LCD controller */ wake_lcd(); +#endif } bool lcd_active(void) From b056191e89df719a77f82e1d200e20a777cfcdb6 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sat, 24 Jan 2026 17:41:01 -0500 Subject: [PATCH 30/65] Translation updates: * German (Wilfried Winkler) * Korean (Hoseok Seo) * Polish (Adam Rak) * Slovak (Matej Golian) * US English (Myself) Change-Id: Ief4de3c8f6aa88aba6ffae9969d22763a1969743 --- apps/lang/deutsch.lang | 14 ++++++++++++++ apps/lang/english-us.lang | 14 ++++++++++++++ apps/lang/korean.lang | 14 ++++++++++++++ apps/lang/polski.lang | 14 ++++++++++++++ apps/lang/slovak.lang | 14 ++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang index 3823d2b6d2..572187cef9 100644 --- a/apps/lang/deutsch.lang +++ b/apps/lang/deutsch.lang @@ -16964,3 +16964,17 @@ general_purpose_led: "LED Indikatoren verwenden" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Status mitteilen" + + + *: "Status mitteilen" + + diff --git a/apps/lang/english-us.lang b/apps/lang/english-us.lang index 1d2f494a43..1063dbc539 100644 --- a/apps/lang/english-us.lang +++ b/apps/lang/english-us.lang @@ -16942,3 +16942,17 @@ general_purpose_led: "Use LED indicators" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Announce Status" + + + *: "Announce Status" + + diff --git a/apps/lang/korean.lang b/apps/lang/korean.lang index 4f7a9b06f8..77ec04eafc 100644 --- a/apps/lang/korean.lang +++ b/apps/lang/korean.lang @@ -16960,3 +16960,17 @@ general_purpose_led: "LED 표시기 사용" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "상태 발표" + + + *: "상태 발표" + + diff --git a/apps/lang/polski.lang b/apps/lang/polski.lang index 924344e9dd..7dc97aebc8 100644 --- a/apps/lang/polski.lang +++ b/apps/lang/polski.lang @@ -16952,3 +16952,17 @@ general_purpose_led: "Użyj wskaźników led" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Informuj o stanie" + + + *: "Informuj o stanie" + + diff --git a/apps/lang/slovak.lang b/apps/lang/slovak.lang index 1c6df66aa4..4e347617eb 100644 --- a/apps/lang/slovak.lang +++ b/apps/lang/slovak.lang @@ -16946,3 +16946,17 @@ general_purpose_led: "Používať LED indikátory" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Oznámiť status" + + + *: "Oznámiť status" + + From db8494d4e0eb96b9338740abb41adc7641c887c1 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 21 Jan 2026 19:57:01 +0200 Subject: [PATCH 31/65] hiby: r1_patcher: quote path variables Change-Id: I5d03b4ecec1847c404a2f2400b648469023942e2 --- tools/r1_patcher/r1_patcher.sh | 64 +++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tools/r1_patcher/r1_patcher.sh b/tools/r1_patcher/r1_patcher.sh index e0f3e68ab5..43a87c3d58 100755 --- a/tools/r1_patcher/r1_patcher.sh +++ b/tools/r1_patcher/r1_patcher.sh @@ -12,39 +12,39 @@ fi ### init ################################################################################ -currentdir=$(pwd) -updatefile="$(basename $1)" +currentdir="$(pwd)" +updatefile="$(basename "$1")" updatefile_rb="${updatefile%.*}_rb.upt" -workingdir=$(realpath ./working_dir) -workingdir_in=$workingdir"/in" -workingdir_out=$workingdir"/out" +workingdir="$(realpath ./working_dir)" +workingdir_in="$workingdir/in" +workingdir_out="$workingdir/out" -rm -rf $workingdir +rm -rf "$workingdir" -mkdir $workingdir -mkdir $workingdir_in -mkdir $workingdir_out +mkdir "$workingdir" +mkdir "$workingdir_in" +mkdir "$workingdir_out" ################################################################################ ### extract ################################################################################ # extract iso -mkdir -p $workingdir_in/image_contents -7z -o$workingdir_in/image_contents x $updatefile +mkdir -p "$workingdir_in/image_contents" +7z -o"$workingdir_in/image_contents" x "$updatefile" # create xImage (unchanged) -mkdir -p $workingdir_out/xImage -cat $workingdir_in/image_contents/ota_v0/xImage.* > $workingdir_out/xImage/xImage +mkdir -p "$workingdir_out/xImage" +cat "$workingdir_in/image_contents/ota_v0"/xImage.* > "$workingdir_out/xImage/xImage" # create rootfs -mkdir -p $workingdir_in/rootfs -mkdir -p $workingdir_in/rootfs/extracted -cat $workingdir_in/image_contents/ota_v0/rootfs.squashfs.* > $workingdir_in/rootfs/rootfs.squashfs +mkdir -p "$workingdir_in/rootfs" +mkdir -p "$workingdir_in/rootfs/extracted" +cat "$workingdir_in/image_contents/ota_v0"/rootfs.squashfs.* > "$workingdir_in/rootfs/rootfs.squashfs" # extract rootfs -unsquashfs -f -d $workingdir_in/rootfs/extracted $workingdir_in/rootfs/rootfs.squashfs +unsquashfs -f -d "$workingdir_in/rootfs/extracted" "$workingdir_in/rootfs/rootfs.squashfs" ################################################################################ ### update @@ -55,15 +55,15 @@ cp "$2" "$workingdir_in/rootfs/extracted/usr/bin/bootloader.rb" chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/bootloader.rb" # copy modified 'hibyplayer.sh' script -cp hiby_player.sh $workingdir_in/rootfs/extracted/usr/bin/ -chmod 0755 $workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh +cp hiby_player.sh "$workingdir_in/rootfs/extracted/usr/bin/" +chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" ################################################################################ ### Rockbox Hotplug Logic ################################################################################ # 1. Create hotplug helper script -cat << 'EOF' > $workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh +cat << 'EOF' > "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" #!/bin/sh MNT_SD="/data/mnt/sd_0" MNT_USB="/data/mnt/usb" @@ -84,7 +84,7 @@ case "$ACTION" in ;; esac EOF -chmod 0755 $workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh +chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" # 2. Check mdev.conf and append rules only if missing MDEV_CONF="$workingdir_in/rootfs/extracted/etc/mdev.conf" @@ -103,39 +103,39 @@ fi mkdir -p "$workingdir_out/image_contents/ota_v0" -mksquashfs $workingdir_in/rootfs/extracted $workingdir_out/rootfs.squashfs -comp lzo -all-root +mksquashfs "$workingdir_in/rootfs/extracted" "$workingdir_out/rootfs.squashfs" -comp lzo -all-root cd "$workingdir_out/image_contents/ota_v0" # rootfs.squashfs -split -b 512k $workingdir_out/rootfs.squashfs --numeric-suffixes=0 -a 4 rootfs.squashfs. +split -b 512k "$workingdir_out/rootfs.squashfs" --numeric-suffixes=0 -a 4 rootfs.squashfs. -rootfs_md5=($(md5sum $workingdir_out/rootfs.squashfs)) +rootfs_md5=($(md5sum "$workingdir_out/rootfs.squashfs")) rootfs_size=$(stat -c%s "$workingdir_out/rootfs.squashfs") md5=$rootfs_md5 ota_md5_rootfs="ota_md5_rootfs.squashfs.$md5" for part in $(ls rootfs.squashfs.[0-9]* | sort); do - md5next=($(md5sum $part)) + md5next=($(md5sum "$part")) echo $md5next >> $ota_md5_rootfs - mv $part "$part.$md5" + mv "$part" "$part.$md5" md5=$md5next done # xImage -split -b 512k $workingdir_out/xImage/xImage --numeric-suffixes=0 -a 4 xImage. +split -b 512k "$workingdir_out/xImage/xImage" --numeric-suffixes=0 -a 4 xImage. -ximage_md5=($(md5sum $workingdir_out/xImage/xImage)) +ximage_md5=($(md5sum "$workingdir_out/xImage/xImage")) ximage_size=$(stat -c%s "$workingdir_out/xImage/xImage") md5=$ximage_md5 ota_md5_xImage="ota_md5_xImage.$md5" for part in $(ls xImage.[0-9]* | sort); do - md5next=($(md5sum $part)) + md5next=($(md5sum "$part")) echo $md5next >> $ota_md5_xImage - mv $part "$part.$md5" + mv "$part" "$part.$md5" md5=$md5next done @@ -162,12 +162,12 @@ cd "$workingdir_out/image_contents" echo "current_version=0" > ota_config.in # iso -genisoimage -f -U -J -joliet-long -r -allow-lowercase -allow-multidot -o $currentdir/$updatefile_rb $workingdir_out/image_contents/ +genisoimage -f -U -J -joliet-long -r -allow-lowercase -allow-multidot -o "$currentdir/$updatefile_rb" "$workingdir_out/image_contents/" ################################################################################ ### cleanup ################################################################################ -rm -rf $workingdir +rm -rf "$workingdir" exit 0 From 58ace97a4e021da225207057aa65e9bf00685119 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Wed, 21 Jan 2026 21:15:44 +0200 Subject: [PATCH 32/65] hiby: r1_patcher: embed hiby_player.sh Make it a single-file, self-contained script. Change-Id: Idb3e375f1f9774c9328288765c7d1c7c1e01e2d3 --- tools/r1_patcher/hiby_player.sh | 10 ---------- tools/r1_patcher/r1_patcher.sh | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 14 deletions(-) delete mode 100755 tools/r1_patcher/hiby_player.sh diff --git a/tools/r1_patcher/hiby_player.sh b/tools/r1_patcher/hiby_player.sh deleted file mode 100755 index 70aaf2c33b..0000000000 --- a/tools/r1_patcher/hiby_player.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -killall hiby_player &>/dev/null -killall -9 hiby_player &>/dev/null - -killall bootloader.rb &>/dev/null -killall -9 bootloader.rb &>/dev/null - -/usr/bin/bootloader.rb -sleep 1s \ No newline at end of file diff --git a/tools/r1_patcher/r1_patcher.sh b/tools/r1_patcher/r1_patcher.sh index 43a87c3d58..7d1653310e 100755 --- a/tools/r1_patcher/r1_patcher.sh +++ b/tools/r1_patcher/r1_patcher.sh @@ -51,11 +51,23 @@ unsquashfs -f -d "$workingdir_in/rootfs/extracted" "$workingdir_in/rootfs/rootfs ################################################################################ # copy 'bootloader' -cp "$2" "$workingdir_in/rootfs/extracted/usr/bin/bootloader.rb" -chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/bootloader.rb" +bootloader_file="bootloader.rb" +cp "$2" "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" +chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" -# copy modified 'hibyplayer.sh' script -cp hiby_player.sh "$workingdir_in/rootfs/extracted/usr/bin/" +# create modified 'hiby_player.sh' script +cat << EOF > "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" +#!/bin/sh + +killall hiby_player &>/dev/null +killall -9 hiby_player &>/dev/null + +killall $bootloader_file &>/dev/null +killall -9 $bootloader_file &>/dev/null + +/usr/bin/$bootloader_file +sleep 1s +EOF chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" ################################################################################ From 0f5c42122cc7d256fd2d68185a1596ead4144273 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 13 Jan 2026 14:12:41 +0000 Subject: [PATCH 33/65] echoplayer: implement bootloader power on/off logic On the Echo R1, the main regulator is enabled primarily by the power button and USB input, and secondarily by the CPU's own output pins (cpu_power_on signal or RTC alarm output). From a user perspective, the player should appear to power up and down only if the power button is long pressed, which must be implemented in software. These logical power states are called "active" and "inactive" in the bootloader. Going from inactive to active will attempt to boot Rockbox unless a button (d-pad down) is held to enter bootloader USB mode instead. Going from active to inactive will shut down the player. The bootloader will also automatically shut down after a short timeout, if USB is not plugged in. In the inactive state, the player is supposed to enumerate over USB so it can negotiate a charging current, but should otherwise appear "off". In particular it shouldn't expose mass storage or even power up the SD card, nor power up the LCD/backight. This isn't implemented yet, because there's no way to dynamically change USB configurations (eg. going from active to inactive should trigger re-enumeration to switch to charge only mode). To avoid surprising behavior, the bootloader will just boot Rockbox immediately if USB is plugged in at boot. Change-Id: Icd1d48ef49a31eb32b54d440e9211aaf40c6b974 --- bootloader/SOURCES | 1 - bootloader/echoplayer.c | 349 +++++++++++++++++++++++++++++----------- 2 files changed, 254 insertions(+), 96 deletions(-) diff --git a/bootloader/SOURCES b/bootloader/SOURCES index ca48bfa7ba..1221e044c1 100644 --- a/bootloader/SOURCES +++ b/bootloader/SOURCES @@ -96,5 +96,4 @@ x1000/recovery.c x1000/utils.c #elif defined(ECHO_R1) echoplayer.c -show_logo.c #endif diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index 862b2d2346..ad83bed339 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -21,154 +21,313 @@ #include "kernel/kernel-internal.h" #include "system.h" #include "power.h" -#include "rtc.h" #include "lcd.h" #include "backlight.h" #include "button.h" -#include "timefuncs.h" #include "storage.h" #include "disk.h" #include "file_internal.h" #include "usb.h" -#include "common.h" /* For show_logo() */ +#include "rbversion.h" +#include "system-echoplayer.h" +#include "gpio-stm32h7.h" -static bool is_usb_connected = false; +/* Events for the monitor callback to signal the main thread */ +#define EV_POWER_PRESSED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0) +#define EV_POWER_RELEASED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1) +#define EV_USB_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 2) -static void demo_rtc(void) +/* Long press duration for power button */ +#define POWERBUTTON_LONG_PRESS_TIME (HZ) + +/* Time to remain powered with no USB cable inserted */ +#define USB_UNPLUGGED_ACTIVE_TIME (30 * HZ) +#define USB_UNPLUGGED_INACTIVE_TIME (3 * HZ) + +/* Power button monitor state */ +static bool pwr_curr_state; +static bool pwr_prev_state; +struct timeout pwr_stable_tmo; + +/* USB monitor state */ +static bool usb_curr_state; +static bool usb_prev_state; +struct timeout usb_unplugged_tmo; + +static volatile bool restart_pwr_stable_tmo; +static volatile bool restart_usb_unplugged_tmo; + +/* + * Because power is always enabled while USB is plugged in the + * bootloader decides whether to appear "active" or "inactive" + * to the user. + */ +static bool is_active; + +/* + * This flag is set if the bootloader is entered after software + * poweroff. The user may still be holding the power button and + * we don't want to boot Rockbox because of this, so we have to + * wait for the power button to be released first. + */ +static bool wait_for_power_released; + +/* Optional error message displayed on LCD */ +static const char *status_msg = NULL; + +/* Helper functions */ +static bool is_power_button_pressed(void) { - int y = 0; - struct tm *time = get_time(); - - lcd_clear_display(); - - lcd_putsf(0, y++, "time: %02d:%02d:%02d", - time->tm_hour, time->tm_min, time->tm_sec); - lcd_putsf(0, y++, "year: %d", time->tm_year + 1900); - lcd_putsf(0, y++, "month: %d", time->tm_mon); - lcd_putsf(0, y++, "day: %d", time->tm_mday); - - lcd_update(); + return button_status() & BUTTON_POWER; } -static void demo_storage(void) +static bool is_usbmode_button_pressed(void) { - int y = 0; + return button_status() & BUTTON_DOWN; +} - lcd_clear_display(); - lcd_putsf(0, y++, "tick %ld", current_tick); +static int send_event_on_tmo(struct timeout *tmo) +{ + button_queue_post(tmo->data, 0); + return 0; +} - if (is_usb_connected) +/* + * Monitors the state of the power button and USB cable + * insertion status. It will post an event to the main + * thread when (a) the power button is continously held + * or released for long enough, or (b) the USB cable is + * unplugged for long enough. + */ +static void monitor_tick(void) +{ + /* Power button state */ + pwr_prev_state = pwr_curr_state; + pwr_curr_state = is_power_button_pressed(); + + if (pwr_curr_state != pwr_prev_state || restart_pwr_stable_tmo) { - lcd_puts(0, y++, "storage disabled by USB"); - lcd_update(); + long event = pwr_curr_state ? EV_POWER_PRESSED : EV_POWER_RELEASED; + int ticks = POWERBUTTON_LONG_PRESS_TIME; + + restart_pwr_stable_tmo = false; + timeout_register(&pwr_stable_tmo, send_event_on_tmo, ticks, event); + } + + /* USB cable state */ + usb_prev_state = usb_curr_state; + usb_curr_state = usb_inserted(); + + /* Ignore cable state change in inactive state */ + if (usb_curr_state != usb_prev_state || restart_usb_unplugged_tmo) + { + long event = EV_USB_UNPLUGGED; + int ticks = is_active ? USB_UNPLUGGED_ACTIVE_TIME : USB_UNPLUGGED_INACTIVE_TIME; + + restart_usb_unplugged_tmo = false; + + if (usb_curr_state) + timeout_cancel(&usb_unplugged_tmo); + else + timeout_register(&usb_unplugged_tmo, send_event_on_tmo, ticks, event); + } +} + +static void monitor_init(void) +{ + pwr_curr_state = is_power_button_pressed(); + usb_curr_state = usb_inserted(); + + /* Make sure events fire even if inputs don't change after boot */ + restart_pwr_stable_tmo = true; + restart_usb_unplugged_tmo = true; + + tick_add_task(monitor_tick); +} + +static void go_active(void) +{ + is_active = true; + restart_usb_unplugged_tmo = true; + + gpio_set_level(GPIO_CPU_POWER_ON, 1); + storage_enable(true); +} + +static void go_inactive(void) +{ + is_active = false; + restart_usb_unplugged_tmo = true; + + storage_enable(false); + gpio_set_level(GPIO_CPU_POWER_ON, 0); +} + +static void refresh_display(void) +{ + if (!is_active) + { + lcd_shutdown(); return; } - struct partinfo pinfo; - if (storage_present(IF_MD(0,)) && disk_partinfo(0, &pinfo)) - { - lcd_putsf(0, y++, "start %d", (int)pinfo.start); - lcd_putsf(0, y++, "count %d", (int)pinfo.size); - lcd_putsf(0, y++, "type %d", (int)pinfo.type); - - DIR *d = opendir("/"); - struct dirent *ent; - while ((ent = readdir(d))) - { - lcd_putsf(0, y++, "/%s", ent->d_name); - } - - closedir(d); - } - - lcd_update(); -} - -static void demo_usb(void) -{ - static const char *phyname[] = { - [STM32H743_USBOTG_PHY_ULPI_HS] = "ULPI HS", - [STM32H743_USBOTG_PHY_ULPI_FS] = "ULPI FS", - [STM32H743_USBOTG_PHY_INT_FS] = "internal FS", - }; - int y = 0; lcd_clear_display(); - lcd_putsf(0, y++, "tick %ld", current_tick); - lcd_putsf(0, y++, "usb connected %d", (int)is_usb_connected); - lcd_putsf(0, y++, "instance = USB%d", STM32H743_USBOTG_INSTANCE + 1); - lcd_putsf(0, y++, "phy = %s", phyname[STM32H743_USBOTG_PHY]); + lcd_putsf(0, y++, "Rockbox on %s", MODEL_NAME); + y++; + + if (status_msg) + { + lcd_putsf(0, y++, "Error: %s", status_msg); + y++; + } + + if (!usb_inserted()) + { + lcd_putsf(0, y++, "Hold POWER to power off"); + lcd_putsf(0, y++, "Connect USB cable for USB mode"); + y++; + } + else + { + lcd_putsf(0, y++, "Bootloader USB mode"); + + if (charging_state()) + { + lcd_putsf(0, y++, "Battery charging (%d mA)", + usb_charging_maxcurrent()); + } + else + { + lcd_putsf(0, y++, "Battery charged"); + } + + y++; + } + + lcd_putsf(0, y++, "Version: %s", RBVERSION); lcd_update(); + lcd_enable(true); } -static void (*demo_funcs[]) (void) = { - demo_rtc, - demo_storage, - demo_usb, -}; +static void launch(void) +{ + /* No-op if USB mode was requested */ + if (is_usbmode_button_pressed()) + return; + + /* TODO: load rockbox */ + status_msg = "Can't boot RB yet!"; +} void main(void) { system_init(); kernel_init(); power_init(); - rtc_init(); - - lcd_init(); button_init(); + /* Start monitoring power button / usb state */ + monitor_init(); + + /* Prepare LCD in case we need to display something */ + lcd_init(); backlight_init(); - backlight_on(); - - show_logo(); + /* + * Prepare storage subsystem, but keep SD card unpowered + * until we actually need to access it. + */ storage_init(); + storage_enable(false); + + /* + * Initialize fs/disk internal state, the disk will not + * be mountable due to being disabled but this will not + * cause any fatal errors. + */ filesystem_init(); disk_mount_all(); + if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_REBOOT || + usb_detect() == USB_INSERTED) + { + /* + * For software reboot we want to immediately launch Rockbox. + * + * We also do so if USB is plugged in at boot, because there's + * no way to dynamically switch between charge only and mass + * storage mode depending on the active/inactive state; to avoid + * confusion, it is simpler to just boot Rockbox. + */ + go_active(); + launch(); + } + else if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_POWEROFF) + { + /* Ignore power button pressed event until button is first released */ + wait_for_power_released = true; + } + + /* + * Initialize USB so we can enumerate with the host and + * negotiate charge current (needed even in inactive mode) + */ usb_init(); usb_start_monitoring(); + usb_charging_enable(USB_CHARGING_FORCE); - int demo_page = 0; - const int num_pages = ARRAYLEN(demo_funcs); - - while (1) + for (;;) { - int btn = button_get_w_tmo(HZ); - switch (btn) + refresh_display(); + + long refresh_tmo = is_active ? HZ : TIMEOUT_BLOCK; + + switch (button_get_w_tmo(refresh_tmo)) { - case BUTTON_START: - demo_page += 1; - if (demo_page >= num_pages) - demo_page = 0; + case EV_POWER_PRESSED: + if (wait_for_power_released) + break; + + if (!is_active) + { + /* Launch Rockbox due to user pressing power button */ + go_active(); + launch(); + } + else + { + /* + * NOTE: The check for USB insertion here is only + * because we can't change to charging only mode. + */ + if (!usb_inserted()) + go_inactive(); + } + break; - case BUTTON_SELECT: - if (demo_page == 0) - demo_page = num_pages - 1; - else - demo_page -= 1; + case EV_POWER_RELEASED: + /* + * This cuts power if the power button is not + * held and we're not in the active state due + * to USB mode, etc. + */ + wait_for_power_released = false; + gpio_set_level(GPIO_CPU_POWER_ON, is_active); + break; + + case EV_USB_UNPLUGGED: + go_inactive(); break; case SYS_USB_CONNECTED: + go_active(); usb_acknowledge(SYS_USB_CONNECTED_ACK); - is_usb_connected = true; - break; - - case SYS_USB_DISCONNECTED: - is_usb_connected = false; - - case BUTTON_X: - power_off(); - break; - - default: break; } - - demo_funcs[demo_page](); } } From e3bf9210ab0f2bb6b68653dbbf89b946c7f8816b Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 13:31:31 +0000 Subject: [PATCH 34/65] echoplayer: disable boot data The current bootdata mechanism won't work well for ELF binaries so disable it. Change-Id: If6721ef88a38af76711bbdc6b561529dc3b33557 --- firmware/export/config/echor1.h | 6 +++--- firmware/target/arm/stm32/app.lds | 1 - firmware/target/arm/stm32/crt0-stm32h7.S | 7 ------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 41149258c9..8f11ab8533 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -89,9 +89,9 @@ #define BATTERY_CAPACITY_MAX 1100 #define BATTERY_CAPACITY_INC 0 -/* Multiboot */ -#define HAVE_BOOTDATA -#define BOOT_REDIR "rockbox_main.echor1" +/* TODO: Multiboot */ +//#define HAVE_BOOTDATA +//#define BOOT_REDIR "rockbox_main.echor1" /* USB support */ #ifndef SIMULATOR diff --git a/firmware/target/arm/stm32/app.lds b/firmware/target/arm/stm32/app.lds index 4e5e393d28..cd20766d2e 100644 --- a/firmware/target/arm/stm32/app.lds +++ b/firmware/target/arm/stm32/app.lds @@ -39,7 +39,6 @@ SECTIONS { loadaddress = .; /* only needed to keep ROLO happy */ - KEEP(*(.bootdata)) *(.init.text*) *(.text*) } > SRAM_AXI :sram_rx diff --git a/firmware/target/arm/stm32/crt0-stm32h7.S b/firmware/target/arm/stm32/crt0-stm32h7.S index 9130848785..6be820c390 100644 --- a/firmware/target/arm/stm32/crt0-stm32h7.S +++ b/firmware/target/arm/stm32/crt0-stm32h7.S @@ -19,18 +19,11 @@ * ****************************************************************************/ #include "config.h" -#include "bootdata.h" #include "regs/stm32h743/pwr.h" .syntax unified .text -#if defined(HAVE_BOOTDATA) && !defined(BOOTLOADER) - .section .bootdata - -put_boot_data_here -#endif - .section .init.text,"ax",%progbits .global reset_handler From bd73e0dd42d431b10fbd75ff147ede3d1949ade2 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 13:36:40 +0000 Subject: [PATCH 35/65] stm32h7: do bss/data setup in crt0 for bootloader only Change-Id: I67798a4370b01127364c40328549a6d3081545cf --- firmware/target/arm/stm32/crt0-stm32h7.S | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/firmware/target/arm/stm32/crt0-stm32h7.S b/firmware/target/arm/stm32/crt0-stm32h7.S index 6be820c390..8c8c8d082d 100644 --- a/firmware/target/arm/stm32/crt0-stm32h7.S +++ b/firmware/target/arm/stm32/crt0-stm32h7.S @@ -52,9 +52,15 @@ reset_handler: b crt0_start - .global start - .type start, function + .global crt0_start + .type crt0_start, function crt0_start: +#ifdef BOOTLOADER + /* + * Initialize data/bss for bootloader + * (not needed on app since ELF loader takes care of it) + */ + /* Zero out BSS */ ldr a1, =_bssbegin ldr a2, =_bssend @@ -66,6 +72,7 @@ crt0_start: ldr a2, =_dataend ldr a3, =_datacopy bl crt0_area_copy +#endif /* Clear the main thread stack */ ldr a1, =stackbegin @@ -96,6 +103,7 @@ crt0_start: b main +#ifdef BOOTLOADER .local crt0_area_copy .type crt0_area_copy, function /* crt0_area_copy(uint32_t *dst, uint32_t *dst_end, const void *src) */ @@ -105,7 +113,7 @@ crt0_area_copy: strhi v1, [a1], #4 bhi crt0_area_copy bx lr - +#endif .local crt0_area_clear .type crt0_area_clear, function From 174b33cc07535e67000831c0de0ad9de30a705f4 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 13:39:40 +0000 Subject: [PATCH 36/65] stm32h7: rename startup code .init.text section to .init Do this for compatibility with INIT_ATTR, which uses the .init section. Change-Id: I473c29e3d38e5d5a563c98fc910d725024072735 --- firmware/target/arm/stm32/app.lds | 2 +- firmware/target/arm/stm32/boot.lds | 2 +- firmware/target/arm/stm32/crt0-stm32h7.S | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firmware/target/arm/stm32/app.lds b/firmware/target/arm/stm32/app.lds index cd20766d2e..eb19050a07 100644 --- a/firmware/target/arm/stm32/app.lds +++ b/firmware/target/arm/stm32/app.lds @@ -39,7 +39,7 @@ SECTIONS { loadaddress = .; /* only needed to keep ROLO happy */ - *(.init.text*) + *(.init*) *(.text*) } > SRAM_AXI :sram_rx diff --git a/firmware/target/arm/stm32/boot.lds b/firmware/target/arm/stm32/boot.lds index b93a8ce7dc..018b8742d2 100644 --- a/firmware/target/arm/stm32/boot.lds +++ b/firmware/target/arm/stm32/boot.lds @@ -18,7 +18,7 @@ SECTIONS { KEEP(*(.vectors.arm)) KEEP(*(.vectors.platform)) - *(.init.text*) + *(.init*) *(.text*) *(.rodata*) } > FLASH1 diff --git a/firmware/target/arm/stm32/crt0-stm32h7.S b/firmware/target/arm/stm32/crt0-stm32h7.S index 8c8c8d082d..0548350cc9 100644 --- a/firmware/target/arm/stm32/crt0-stm32h7.S +++ b/firmware/target/arm/stm32/crt0-stm32h7.S @@ -24,7 +24,7 @@ .syntax unified .text - .section .init.text,"ax",%progbits + .section .init,"ax",%progbits .global reset_handler .type reset_handler, function From 98990df08ffe18088b32fc9b7e91cdf150f60a90 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 13:42:05 +0000 Subject: [PATCH 37/65] stm32h7: update linker scripts for ELF boot Use flash & AXI SRAM for the bootloader, ITCM/DTCM and SDRAM for Rockbox. Hardly the most optimal use of SRAMs but it's good enough to get started. Fixes the Echo R1 app build, which wasn't fitting in AXI SRAM. Change-Id: I4f7e5f618d27b553e5ff8dec1d5c4c61ac9d8eb0 --- firmware/target/arm/stm32/app.lds | 78 ++++++++++-------------------- firmware/target/arm/stm32/boot.lds | 25 +++++----- 2 files changed, 39 insertions(+), 64 deletions(-) diff --git a/firmware/target/arm/stm32/app.lds b/firmware/target/arm/stm32/app.lds index eb19050a07..1fbcd140cd 100644 --- a/firmware/target/arm/stm32/app.lds +++ b/firmware/target/arm/stm32/app.lds @@ -1,67 +1,25 @@ #include "cpu.h" -/* - * TODO: this is temporary and has not been tested - */ - -ENTRY(main) +ENTRY(crt0_start) OUTPUT_FORMAT(elf32-littlearm) OUTPUT_ARCH(arm) STARTUP(target/arm/stm32/crt0-stm32h7.o) MEMORY { - SRAM_AXI (rwx) : ORIGIN = STM32_SRAM_AXI_BASE, LENGTH = STM32_SRAM_AXI_SIZE - DTCM (rwx) : ORIGIN = STM32_DTCM_BASE, LENGTH = STM32_DTCM_SIZE - ITCM (rwx) : ORIGIN = STM32_ITCM_BASE, LENGTH = STM32_ITCM_SIZE - SDRAM (rwx) : ORIGIN = STM32_SDRAM1_BASE, LENGTH = MEMORYSIZE * 1024 * 1024 -} - -/* - * to control section alignment (only affects on-disk alignment): - * -Wl,-z,max-page-size=0x1 - */ - -PHDRS -{ - sram_rx PT_LOAD ; - sram_ro PT_LOAD ; - sram_rw PT_LOAD ; - itcm PT_LOAD ; - dtcm PT_LOAD ; - sdram_rx PT_LOAD ; - sdram_rw PT_LOAD ; + DTCM (rw) : ORIGIN = STM32_DTCM_BASE, LENGTH = STM32_DTCM_SIZE + ITCM (rx) : ORIGIN = STM32_ITCM_BASE, LENGTH = STM32_ITCM_SIZE + SDRAM (rwx) : ORIGIN = STM32_SDRAM1_BASE, LENGTH = MEMORYSIZE * 1024 * 1024 } SECTIONS { - .text : - { - loadaddress = .; /* only needed to keep ROLO happy */ - - *(.init*) - *(.text*) - } > SRAM_AXI :sram_rx - - .rodata : - { - *(.rodata*) - } > SRAM_AXI :sram_ro - - .data : - { - _databegin = .; - *(.data*) - _dataend = .; - } > SRAM_AXI :sram_rw - _datacopy = LOADADDR(.data); - .itext : { - KEEP(*(.vectors.arm)) - KEEP(*(.vectors.platform)) + KEEP(*(.vectors.arm)); + KEEP(*(.vectors.platform)); *(.icode*); - } > ITCM :itcm + } > ITCM .stack (NOLOAD) : { @@ -72,9 +30,25 @@ SECTIONS stackbegin = .; . += 0x2000; stackend = .; + } > DTCM - *(.stack); - } > DTCM :dtcm + .text : + { + loadaddress = .; /* only needed to keep ROLO happy */ + + *(.init*); + *(.text*); + } > SDRAM + + .rodata : + { + *(.rodata*); + } > SDRAM + + .data : + { + *(.data*); + } > SDRAM .bss (NOLOAD) : { @@ -82,7 +56,7 @@ SECTIONS *(.bss*); *(COMMON); _bssend = .; - } > SDRAM :sdram_rw + } > SDRAM audiobuffer = ALIGN(32); audiobufend = ORIGIN(SDRAM) + LENGTH(SDRAM) - CODEC_SIZE - PLUGIN_BUFFER_SIZE; diff --git a/firmware/target/arm/stm32/boot.lds b/firmware/target/arm/stm32/boot.lds index 018b8742d2..0994787666 100644 --- a/firmware/target/arm/stm32/boot.lds +++ b/firmware/target/arm/stm32/boot.lds @@ -7,26 +7,29 @@ STARTUP(target/arm/stm32/crt0-stm32h7.o) MEMORY { - SRAM_AXI (rwx) : ORIGIN = STM32_SRAM_AXI_BASE, LENGTH = STM32_SRAM_AXI_SIZE - DTCM (rwx) : ORIGIN = STM32_DTCM_BASE, LENGTH = STM32_DTCM_SIZE - FLASH1 (rx) : ORIGIN = STM32_FLASH_BANK1_BASE, LENGTH = STM32_FLASH_BANK1_SIZE + SRAM_AXI (rw) : ORIGIN = STM32_SRAM_AXI_BASE, LENGTH = STM32_SRAM_AXI_SIZE + FLASH1 (rx) : ORIGIN = STM32_FLASH_BANK1_BASE, LENGTH = STM32_FLASH_BANK1_SIZE } SECTIONS { .text : { - KEEP(*(.vectors.arm)) - KEEP(*(.vectors.platform)) - *(.init*) - *(.text*) - *(.rodata*) + KEEP(*(.vectors.arm)); + KEEP(*(.vectors.platform)); + *(.init*); + *(.text*); + } > FLASH1 + + .rodata : + { + *(.rodata*); } > FLASH1 .data : { _databegin = .; - *(.data*) + *(.data*); _dataend = .; } > SRAM_AXI AT> FLASH1 _datacopy = LOADADDR(.data); @@ -48,9 +51,7 @@ SECTIONS stackbegin = .; . += 0x2000; stackend = .; - - *(.stack); - } > DTCM + } > SRAM_AXI } EXTERN(__vectors_arm); From ecb8014a061ff8361dc1604bbebd2c8e7bebb012 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Sun, 25 Jan 2026 08:59:10 -0500 Subject: [PATCH 38/65] FS#13766: Updated Vietnamese Translation (Chu Khanh Hanh) Change-Id: I3cf9cf9814a549e49eecd7bed0e364c2b2b1439b --- apps/lang/vietnamese.lang | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/lang/vietnamese.lang b/apps/lang/vietnamese.lang index 6381555e83..11d20d84bb 100644 --- a/apps/lang/vietnamese.lang +++ b/apps/lang/vietnamese.lang @@ -16944,3 +16944,17 @@ general_purpose_led: "Dùng đèn hiệu LED" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Thông báo Trạng thái" + + + *: "Thông báo trạng thái" + + From f642d19c20168f4093eb0897a9666068063888bf Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Mon, 12 Jan 2026 10:44:01 +0000 Subject: [PATCH 39/65] echoplayer: initial Echo R1 keymap Change-Id: Id720378a55425070ef69ca002c2e954cc1e147cf --- apps/keymaps/keymap-echor1.c | 154 ++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/apps/keymaps/keymap-echor1.c b/apps/keymaps/keymap-echor1.c index e4b108f033..4964535b82 100644 --- a/apps/keymaps/keymap-echor1.c +++ b/apps/keymaps/keymap-echor1.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2025 Aidan MacDonald + * Copyright (C) 2026 Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,14 +29,166 @@ /* {Action Code, Button code, Prereq button code } */ static const struct button_mapping button_context_standard[] = { + {ACTION_STD_PREV, BUTTON_UP, BUTTON_NONE}, + {ACTION_STD_PREVREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_NEXT, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_STD_NEXTREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_OK, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_STD_CONTEXT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_QUICKSCREEN, BUTTON_B, BUTTON_NONE}, + {ACTION_STD_MENU, BUTTON_Y, BUTTON_NONE}, LAST_ITEM_IN_LIST }; /* button_context_standard */ +static const struct button_mapping button_context_wps[] = { + {ACTION_WPS_PLAY, BUTTON_DOWN|BUTTON_REL, BUTTON_DOWN}, + {ACTION_WPS_STOP, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_VIEW_PLAYLIST, BUTTON_UP|BUTTON_REL, BUTTON_UP}, + {ACTION_WPS_CONTEXT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_VOLUP, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_WPS_VOLUP, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_VOLDOWN, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_WPS_VOLDOWN, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_SKIPNEXT, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT}, + {ACTION_WPS_SKIPPREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT}, + {ACTION_WPS_SEEKFWD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_STOPSEEK, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT}, + {ACTION_WPS_SEEKBACK, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_WPS_STOPSEEK, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT}, + {ACTION_WPS_QUICKSCREEN, BUTTON_B, BUTTON_NONE}, + LAST_ITEM_IN_LIST +}; /* button_context_wps */ + +static const struct button_mapping button_context_tree[] = { + {ACTION_TREE_WPS, BUTTON_X, BUTTON_NONE}, + {ACTION_TREE_HOTKEY, BUTTON_Y, BUTTON_NONE}, + {ACTION_TREE_STOP, BUTTON_START|BUTTON_REPEAT, BUTTON_START}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST) +}; /* button_context_tree */ + +static const struct button_mapping button_context_list[] = { + {ACTION_LISTTREE_PGUP, BUTTON_UP|BUTTON_SELECT, BUTTON_NONE}, + {ACTION_LISTTREE_PGUP, BUTTON_UP|BUTTON_SELECT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_LISTTREE_PGDOWN, BUTTON_DOWN|BUTTON_SELECT, BUTTON_NONE}, + {ACTION_LISTTREE_PGDOWN, BUTTON_DOWN|BUTTON_SELECT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_LIST_VOLUP, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_LIST_VOLUP, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_LIST_VOLDOWN, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_LIST_VOLDOWN, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_list */ + +static const struct button_mapping button_context_settings[] = { + {ACTION_SETTINGS_INC, BUTTON_UP, BUTTON_NONE}, + {ACTION_SETTINGS_INCREPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_INCBIGSTEP, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_SETTINGS_DECREPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_DECBIGSTEP, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_STD_NEXT, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_STD_NEXTREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_PREV, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_STD_PREVREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_STD_OK, BUTTON_A, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_X, BUTTON_NONE}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_settings */ + +static const struct button_mapping button_context_settings_eq[] = { + {ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_INCBIGSTEP, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_SETTINGS_DECBIGSTEP, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_STD_OK, BUTTON_A, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_X, BUTTON_NONE}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_settings_eq */ + +static const struct button_mapping button_context_quickscreen[] = { + {ACTION_QS_TOP, BUTTON_UP, BUTTON_NONE}, + {ACTION_QS_TOP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_QS_DOWN, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_QS_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_QS_LEFT, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_QS_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_QS_RIGHT, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_QS_RIGHT, BUTTON_RIGHT|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_CONTEXT, BUTTON_B|BUTTON_REPEAT, BUTTON_B}, + {ACTION_STD_CANCEL, BUTTON_B|BUTTON_REL, BUTTON_B}, + LAST_ITEM_IN_LIST +}; /* button_context_quickscreen */ + +static const struct button_mapping button_context_yesnoscreen[] = { + {ACTION_YESNO_ACCEPT, BUTTON_A, BUTTON_NONE}, + {ACTION_YESNO_ACCEPT, BUTTON_X, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_B, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_Y, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_START, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_SELECT, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_POWER, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_STD_CANCEL, BUTTON_VOL_DOWN, BUTTON_NONE}, + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_yesnoscreen */ + +static const struct button_mapping button_context_keyboard[] = { + {ACTION_KBD_UP, BUTTON_UP, BUTTON_NONE}, + {ACTION_KBD_UP, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_DOWN, BUTTON_DOWN, BUTTON_NONE}, + {ACTION_KBD_DOWN, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_LEFT, BUTTON_LEFT, BUTTON_NONE}, + {ACTION_KBD_LEFT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_RIGHT, BUTTON_RIGHT, BUTTON_NONE}, + {ACTION_KBD_RIGHT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_SELECT, BUTTON_SELECT, BUTTON_NONE}, + {ACTION_KBD_BACKSPACE, BUTTON_X, BUTTON_NONE}, + {ACTION_KBD_BACKSPACE, BUTTON_X|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_DONE, BUTTON_A, BUTTON_NONE}, + {ACTION_KBD_ABORT, BUTTON_POWER, BUTTON_NONE}, + {ACTION_KBD_PAGE_FLIP, BUTTON_START, BUTTON_NONE}, + {ACTION_KBD_CURSOR_LEFT, BUTTON_Y, BUTTON_NONE}, + {ACTION_KBD_CURSOR_LEFT, BUTTON_Y|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_CURSOR_RIGHT, BUTTON_B, BUTTON_NONE}, + {ACTION_KBD_CURSOR_RIGHT, BUTTON_B|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_CURSOR_LEFT, BUTTON_VOL_UP, BUTTON_NONE}, + {ACTION_KBD_CURSOR_LEFT, BUTTON_VOL_UP|BUTTON_REPEAT, BUTTON_NONE}, + {ACTION_KBD_CURSOR_RIGHT, BUTTON_VOL_DOWN, BUTTON_NONE}, + {ACTION_KBD_CURSOR_RIGHT, BUTTON_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE}, + LAST_ITEM_IN_LIST +}; /* button_context_keyboard */ + const struct button_mapping* get_context_mapping(int context) { switch (context) { default: + case CONTEXT_STD: return button_context_standard; + case CONTEXT_WPS: + return button_context_wps; + case CONTEXT_TREE: + case CONTEXT_MAINMENU: + return button_context_tree; + case CONTEXT_LIST: + return button_context_list; + case CONTEXT_SETTINGS: + case CONTEXT_SETTINGS_TIME: + return button_context_settings; + case CONTEXT_SETTINGS_EQ: + case CONTEXT_SETTINGS_COLOURCHOOSER: + return button_context_settings_eq; + case CONTEXT_QUICKSCREEN: + return button_context_quickscreen; + case CONTEXT_YESNOSCREEN: + return button_context_yesnoscreen; + case CONTEXT_KEYBOARD: + return button_context_keyboard; } } From 21ba79d43182889559b5d7ba52eac861f9aa925e Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 14:59:12 +0000 Subject: [PATCH 40/65] echoplayer: disable parts of system_init() for application Most of system_init() should only be performed once after reset, and is not safe to re-run when the bootloader jumps to the application. Change-Id: I4d2e804ce4884da13b9167ddcda860ef3b5ba7d0 --- .../arm/stm32/echoplayer/system-echoplayer.c | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index f9a2763205..88f7b27720 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -29,6 +29,12 @@ #include "regs/stm32h743/rtc.h" #include "regs/cortex-m/cm_scb.h" +#ifdef BOOTLOADER +# define BOOTLOADER_INIT 1 +#else +# define BOOTLOADER_INIT 0 +#endif + #define F_INPUT GPIOF_INPUT(GPIO_PULL_DISABLED) #define F_INPUT_PU GPIOF_INPUT(GPIO_PULL_UP) #define F_INPUT_PD GPIOF_INPUT(GPIO_PULL_DOWN) @@ -173,17 +179,20 @@ INIT_ATTR static void fmc_init(void) void system_init(void) { - /* Enable clocks for all used GPIO banks */ - reg_writef(RCC_AHB4ENR, - GPIOAEN(1), GPIOBEN(1), GPIOCEN(1), GPIODEN(1), - GPIOEEN(1), GPIOFEN(1), GPIOGEN(1), GPIOHEN(1), GPIOIEN(1)); + if (BOOTLOADER_INIT) + { + /* Enable clocks for all used GPIO banks */ + reg_writef(RCC_AHB4ENR, + GPIOAEN(1), GPIOBEN(1), GPIOCEN(1), GPIODEN(1), + GPIOEEN(1), GPIOFEN(1), GPIOGEN(1), GPIOHEN(1), GPIOIEN(1)); - /* - * NOTE: I think it's possible to disable clocks for the banks which - * we don't need to access at runtime because these are only clocking - * register access. Probably a micro-optimization but it supposedly - * does save a few uA/MHz. - */ + /* + * NOTE: I think it's possible to disable clocks for the banks which + * we don't need to access at runtime because these are only clocking + * register access. Probably a micro-optimization but it supposedly + * does save a few uA/MHz. + */ + } /* * Set cpu_power_on high as early as possible to @@ -200,36 +209,42 @@ void system_init(void) system_debug_enable(true); #endif - /* Enable CPU cache */ - stm32_enable_caches(); + if (BOOTLOADER_INIT) + { + /* Enable CPU cache */ + stm32_enable_caches(); - /* Initialize system clocks */ - echoplayer_clock_init(); + /* Initialize system clocks */ + echoplayer_clock_init(); + } /* Enable systick early due to udelay() needed for FMC init */ stm32_systick_enable(); - /* Configure GPIOs and start FMC */ - gpio_configure_all(gpios, ARRAYLEN(gpios), - pingroups, ARRAYLEN(pingroups)); - fmc_init(); - - /* Read & clear reset source */ - uint32_t rsr = reg_var(RCC_RSR); - reg_assignf(RCC_RSR, RMVF(1)); - - /* - * Determine boot reason -- SFTRST means a software reset - * occurred, which may be a reboot or a power off - */ - if (reg_vreadf(rsr, RCC_RSR, SFTRSTF)) + if (BOOTLOADER_INIT) { - reg_writef(RCC_APB4ENR, RTCAPBEN(1)); + /* Configure GPIOs and start FMC */ + gpio_configure_all(gpios, ARRAYLEN(gpios), + pingroups, ARRAYLEN(pingroups)); + fmc_init(); - if (reg_readf(RTC_CR, WUTE)) - echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_REBOOT; - else - echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_POWEROFF; + /* Read & clear reset source */ + uint32_t rsr = reg_var(RCC_RSR); + reg_assignf(RCC_RSR, RMVF(1)); + + /* + * Determine boot reason -- SFTRST means a software reset + * occurred, which may be a reboot or a power off + */ + if (reg_vreadf(rsr, RCC_RSR, SFTRSTF)) + { + reg_writef(RCC_APB4ENR, RTCAPBEN(1)); + + if (reg_readf(RTC_CR, WUTE)) + echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_REBOOT; + else + echoplayer_boot_reason = ECHOPLAYER_BOOT_REASON_SW_POWEROFF; + } } /* Disable RTC_OUT pin */ From a610998ea4544bf25d2562268a866163c70b0d59 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 4 Jan 2026 00:00:05 +0000 Subject: [PATCH 41/65] firmware: add simple ELF loader for static binaries This is a small & simple ELF loader which just copies program segments from disk to memory. It only supports static binaries right now. Change-Id: I8944feb5b9dafcc5c56e12383aed25e2718ad7ea --- firmware/SOURCES | 4 + firmware/elf_loader.c | 175 ++++++++++++++++++++++++++++++++++ firmware/include/elf-target.h | 50 ++++++++++ firmware/include/elf.h | 143 +++++++++++++++++++++++++++ firmware/include/elf_loader.h | 94 ++++++++++++++++++ 5 files changed, 466 insertions(+) create mode 100644 firmware/elf_loader.c create mode 100644 firmware/include/elf-target.h create mode 100644 firmware/include/elf.h create mode 100644 firmware/include/elf_loader.h diff --git a/firmware/SOURCES b/firmware/SOURCES index 9279307af5..88927f7364 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -58,6 +58,10 @@ target/hosted/rolo.c lc-rock.c #endif +#if defined(HAVE_ELF) +elf_loader.c +#endif + #if defined(HAVE_BOOTDATA) || defined(HAVE_MULTIBOOT) common/multiboot.c #ifndef BOOTLOADER diff --git a/firmware/elf_loader.c b/firmware/elf_loader.c new file mode 100644 index 0000000000..d6fd2e71d4 --- /dev/null +++ b/firmware/elf_loader.c @@ -0,0 +1,175 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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 "elf_loader.h" +#include "elf-target.h" +#include "file.h" + +#if defined(ROCKBOX_LITTLE_ENDIAN) +# define NATIVE_ELF_DATA ELFDATA2LSB +#elif defined(ROCKBOX_BIG_ENDIAN) +# define NATIVE_ELF_DATA ELFDATA2MSB +#else +# error "Unknown endianness!" +#endif + +_Static_assert(TARGET_ELF_DATA == NATIVE_ELF_DATA, + "ELF binaries with foreign endianness not supported"); + +static int elf_validate_header(const struct elf_header *header) +{ + if (header->ei_magic[0] != ELFMAG0 || + header->ei_magic[1] != ELFMAG1 || + header->ei_magic[2] != ELFMAG2 || + header->ei_magic[3] != ELFMAG3) + return ELF_ERR_BADMAGIC; + + if (header->ei_class != TARGET_ELF_CLASS || + header->ei_data != TARGET_ELF_DATA) + return ELF_ERR_UNSUPP_ARCH; + + if (header->ei_version != EV_CURRENT || + header->e_version != EV_CURRENT) + return ELF_ERR_UNSUPP_VERSION; + + if (header->ei_osabi != TARGET_ELF_OSABI || + header->ei_abiversion != TARGET_ELF_ABIVERSION) + return ELF_ERR_UNSUPP_OS; + + if (header->e_type != ET_EXEC) + return ELF_ERR_UNSUPP_TYPE; + + if (header->e_machine != TARGET_ELF_MACHINE) + return ELF_ERR_UNSUPP_ARCH; + + if (header->e_phoff == 0) + return ELF_ERR_EMPTY; + + if (header->e_phentsize < sizeof(struct elf_phdr)) + return ELF_ERR_UNSUPP_PHSIZE; + + if (header->e_phnum == PN_XNUM) + return ELF_ERR_TOO_MANY_PHDRS; + + return ELF_OK; +} + +static int elf_validate_phdr(const struct elf_phdr *phdr, + const struct elf_load_context *ctx) +{ + if (phdr->p_type != PT_LOAD) + return ELF_ERR_UNSUPP_PHDR; + + if (phdr->p_filesz > phdr->p_memsz) + return ELF_ERR_INVALID_PHDR; + + /* Ignore headers which occupy zero memory */ + if (phdr->p_memsz == 0) + return 0; + + for (size_t i = 0; i < ctx->num_mmap; ++i) + { + const struct elf_memory_map *entry = &ctx->mmap[i]; + if (phdr->p_vaddr >= entry->addr && + phdr->p_vaddr < entry->addr + entry->size && + phdr->p_memsz <= entry->size) + { + if ((phdr->p_flags & entry->flags) != phdr->p_flags) + return ELF_ERR_MEM_INCOMPAT_FLAGS; + + return ELF_OK; + } + } + + return ELF_ERR_MEM_UNMAPPED; +} + +int elf_loadfd(int fd, + const struct elf_load_context *ctx, + void **entrypoint) +{ + struct elf_header ehdr; + struct elf_phdr phdr; + ssize_t nread; + int err; + + nread = read(fd, &ehdr, sizeof(ehdr)); + if (nread != (ssize_t)sizeof(ehdr)) + return ELF_ERR_IO; + + err = elf_validate_header(&ehdr); + if (err != ELF_OK) + return err; + + /* + * Iterate over program headers, copying segments to memory + */ + for (size_t ph_index = 0; ph_index < ehdr.e_phnum; ++ph_index) + { + off_t ph_off = ehdr.e_phoff + (ph_index * ehdr.e_phentsize); + if (lseek(fd, ph_off, SEEK_SET) == (off_t)-1) + return ELF_ERR_IO; + + nread = read(fd, &phdr, sizeof(phdr)); + if (nread != (ssize_t)sizeof(phdr)) + return ELF_ERR_IO; + + err = elf_validate_phdr(&phdr, ctx); + if (err) + return err; + + /* Load file data to memory if needed */ + if (phdr.p_filesz > 0) + { + if (lseek(fd, phdr.p_offset, SEEK_SET) == (off_t)-1) + return ELF_ERR_IO; + + nread = read(fd, (void *)phdr.p_vaddr, phdr.p_filesz); + if (nread < 0 || (uint32_t)nread != phdr.p_filesz) + return ELF_ERR_IO; + } + + /* Zero out any memory not backed by file data */ + if (phdr.p_memsz > phdr.p_filesz) + { + void *addr = (void *)phdr.p_vaddr + phdr.p_filesz; + size_t size = phdr.p_memsz - phdr.p_filesz; + + memset(addr, 0, size); + } + } + + *entrypoint = (void *)ehdr.e_entry; + return ELF_OK; +} + +int elf_loadpath(const char *filename, + const struct elf_load_context *ctx, + void **entrypoint) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + return ELF_ERR_FILE_NOT_FOUND; + + int err = elf_loadfd(fd, ctx, entrypoint); + + close(fd); + return err; +} diff --git a/firmware/include/elf-target.h b/firmware/include/elf-target.h new file mode 100644 index 0000000000..e718cf666b --- /dev/null +++ b/firmware/include/elf-target.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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. + * + ****************************************************************************/ +#ifndef __ELF_TARGET_H__ +#define __ELF_TARGET_H__ + +#include "elf.h" +#include "config.h" + +#define TARGET_ELF_CLASS ELFCLASS32 +#define TARGET_ELF_OSABI ELFOSABI_SYSV +#define TARGET_ELF_ABIVERSION 0 +#define elf_phdr elf32_phdr + +#if defined(CPU_ARM) +# define TARGET_ELF_MACHINE EM_ARM +#elif defined(CPU_MIPS) +# define TARGET_ELF_MACHINE EM_MIPS +#elif defined(CPU_COLDFIRE) +# define TARGET_ELF_MACHINE EM_68K +#else +# error "Unknown CPU architecture!" +#endif + +#if defined(ROCKBOX_LITTLE_ENDIAN) +# define TARGET_ELF_DATA ELFDATA2LSB +#elif defined(ROCKBOX_BIG_ENDIAN) +# define TARGET_ELF_DATA ELFDATA2MSB +#else +# error "Unknown endianness!" +#endif + +#endif /* __ELF_TARGET_H__ */ diff --git a/firmware/include/elf.h b/firmware/include/elf.h new file mode 100644 index 0000000000..bfdd4f2281 --- /dev/null +++ b/firmware/include/elf.h @@ -0,0 +1,143 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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. + * + ****************************************************************************/ +#ifndef __ELF_H__ +#define __ELF_H__ + +#include + +/* Field offsets in the e_ident array */ +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 +#define EI_ABIVERSION 8 +#define EI_PAD 9 +#define EI_NIDENT 16 + +/* Values for ei_magic */ +#define ELFMAG0 0x7f +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +/* Values for ei_class */ +#define ELFCLASSNONE 0 +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +/* Values for ei_data */ +#define ELFDATANONE 0 +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +/* Values for ei_version and e_version */ +#define EV_NONE 0 +#define EV_CURRENT 1 + +/* Values for ei_osabi */ +#define ELFOSABI_NONE ELFOSABI_SYSV +#define ELFOSABI_SYSV 0 + +/* Values for e_type */ +#define ET_NONE 0 +#define ET_REL 1 +#define ET_EXEC 2 +#define ET_DYN 3 +#define ET_CORE 4 + +/* Values for e_machine */ +#define EM_NONE 0 +#define EM_68K 4 +#define EM_MIPS 8 +#define EM_ARM 40 + +/* + * Special value for e_phnum when the real number of + * program headers is too large. + */ +#define PN_XNUM 0xFFFFu + +/* ELF basic types */ +typedef uint32_t elf_addr_t; +typedef uint32_t elf_off_t; + +/* ELF file header */ +struct elf_header +{ + union { + uint8_t e_ident[EI_NIDENT]; + struct { + uint8_t ei_magic[4]; + uint8_t ei_class; + uint8_t ei_data; + uint8_t ei_version; + uint8_t ei_osabi; + uint8_t ei_abiversion; + }; + }; + + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + elf_addr_t e_entry; + elf_off_t e_phoff; + elf_off_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +}; + +/* Values for p_type */ +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_SHLIB 5 +#define PT_PHDR 6 + +/* Values for p_flags */ +#define PF_X (1 << 0) /* Segment is executable */ +#define PF_W (1 << 1) /* Segment is writable */ +#define PF_R (1 << 2) /* Segment is readable */ + +/* ELF 32-bit program header */ +struct elf32_phdr +{ + uint32_t p_type; + elf_off_t p_offset; + elf_addr_t p_vaddr; + elf_addr_t p_paddr; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_flags; + uint32_t p_align; +}; + +#endif /* __ELF_H__ */ diff --git a/firmware/include/elf_loader.h b/firmware/include/elf_loader.h new file mode 100644 index 0000000000..0f30e87ab2 --- /dev/null +++ b/firmware/include/elf_loader.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 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. + * + ****************************************************************************/ +#ifndef __ELF_LOADER_H__ +#define __ELF_LOADER_H__ + +/* + * Primitive ELF loader -- allows loading an ELF binary from + * a file descriptor into RAM. Only static binaries linked at + * a fixed address are supported. + * + * The caller supplies a memory map which limits where the + * ELF file can be loaded; if a program header requests any + * memory outside of this mapping, loading will fail. There + * is no support for relocation, PIC code, or any form of + * dynamic linking. + * + * The loader can return the entry point of the ELF binary. + * It does not support arbitrary symbol lookups. + */ + +#include +#include + +enum elf_error +{ + ELF_OK, + ELF_ERR_FILE_NOT_FOUND, + ELF_ERR_IO, + ELF_ERR_EMPTY, + ELF_ERR_BADMAGIC, + ELF_ERR_UNSUPP_ARCH, + ELF_ERR_UNSUPP_OS, + ELF_ERR_UNSUPP_VERSION, + ELF_ERR_UNSUPP_TYPE, + ELF_ERR_UNSUPP_FORMAT, + ELF_ERR_UNSUPP_PHSIZE, + ELF_ERR_TOO_MANY_PHDRS, + ELF_ERR_UNSUPP_PHDR, + ELF_ERR_INVALID_PHDR, + ELF_ERR_MEM_INCOMPAT_FLAGS, + ELF_ERR_MEM_UNMAPPED, +}; + +/* + * Describes an entry in the virtual memory map specified + * when loading an ELF file. + * + * ELF segments must fit completely within a single entry + * in the memory map. Segments crossing multiple mappings + * are rejected even if the mappings are contiguous. + * + * A segment may also be rejected if it has flags (R/W/X) + * not present in the corresponding memory map entry. + */ +struct elf_memory_map +{ + uintptr_t addr; + size_t size; + uint32_t flags; +}; + +struct elf_load_context +{ + const struct elf_memory_map *mmap; + size_t num_mmap; +}; + +int elf_loadfd(int fd, + const struct elf_load_context *ctx, + void **entrypoint); + +int elf_loadpath(const char *filename, + const struct elf_load_context *ctx, + void **entrypoint); + +#endif /* __ELF_LOADER_H__ */ From 843322f8982c9194daff6e9885d769390bea6928 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 10 Jan 2026 22:00:59 +0000 Subject: [PATCH 42/65] elf_loader: add generic callback-based loader Change-Id: I1dd972a585bc7c805e32c9665d13e248663ccc73 --- firmware/elf_loader.c | 42 ++++++++++++++++++++++------------- firmware/include/elf_loader.h | 9 ++++++++ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/firmware/elf_loader.c b/firmware/elf_loader.c index d6fd2e71d4..d1f08f661c 100644 --- a/firmware/elf_loader.c +++ b/firmware/elf_loader.c @@ -101,17 +101,16 @@ static int elf_validate_phdr(const struct elf_phdr *phdr, return ELF_ERR_MEM_UNMAPPED; } -int elf_loadfd(int fd, - const struct elf_load_context *ctx, - void **entrypoint) +int elf_load(elf_read_callback_t read_cb, + intptr_t read_arg, + const struct elf_load_context *ctx, + void **entrypoint) { struct elf_header ehdr; struct elf_phdr phdr; - ssize_t nread; int err; - nread = read(fd, &ehdr, sizeof(ehdr)); - if (nread != (ssize_t)sizeof(ehdr)) + if (read_cb(read_arg, 0, &ehdr, sizeof(ehdr))) return ELF_ERR_IO; err = elf_validate_header(&ehdr); @@ -124,11 +123,7 @@ int elf_loadfd(int fd, for (size_t ph_index = 0; ph_index < ehdr.e_phnum; ++ph_index) { off_t ph_off = ehdr.e_phoff + (ph_index * ehdr.e_phentsize); - if (lseek(fd, ph_off, SEEK_SET) == (off_t)-1) - return ELF_ERR_IO; - - nread = read(fd, &phdr, sizeof(phdr)); - if (nread != (ssize_t)sizeof(phdr)) + if (read_cb(read_arg, ph_off, &phdr, sizeof(phdr))) return ELF_ERR_IO; err = elf_validate_phdr(&phdr, ctx); @@ -138,11 +133,7 @@ int elf_loadfd(int fd, /* Load file data to memory if needed */ if (phdr.p_filesz > 0) { - if (lseek(fd, phdr.p_offset, SEEK_SET) == (off_t)-1) - return ELF_ERR_IO; - - nread = read(fd, (void *)phdr.p_vaddr, phdr.p_filesz); - if (nread < 0 || (uint32_t)nread != phdr.p_filesz) + if (read_cb(read_arg, phdr.p_offset, (void *)phdr.p_vaddr, phdr.p_filesz)) return ELF_ERR_IO; } @@ -160,6 +151,25 @@ int elf_loadfd(int fd, return ELF_OK; } +int elf_loadfd(int fd, + const struct elf_load_context *ctx, + void **entrypoint) +{ + return elf_load(elf_read_fd_callback, fd, ctx, entrypoint); +} + +int elf_read_fd_callback(intptr_t fd, off_t pos, void *buf, size_t size) +{ + if (lseek(fd, pos, SEEK_SET) == (off_t)-1) + return -1; + + ssize_t nread = read(fd, buf, size); + if (nread < 0 || (size_t)nread != size) + return -1; + + return 0; +} + int elf_loadpath(const char *filename, const struct elf_load_context *ctx, void **entrypoint) diff --git a/firmware/include/elf_loader.h b/firmware/include/elf_loader.h index 0f30e87ab2..344a4eb4e1 100644 --- a/firmware/include/elf_loader.h +++ b/firmware/include/elf_loader.h @@ -38,6 +38,7 @@ #include #include +#include enum elf_error { @@ -83,9 +84,17 @@ struct elf_load_context size_t num_mmap; }; +typedef int (*elf_read_callback_t) (intptr_t arg, off_t pos, void *buf, size_t size); + +int elf_load(elf_read_callback_t read_cb, + intptr_t read_arg, + const struct elf_load_context *ctx, + void **entrypoint); + int elf_loadfd(int fd, const struct elf_load_context *ctx, void **entrypoint); +int elf_read_fd_callback(intptr_t fd, off_t pos, void *buf, size_t size); int elf_loadpath(const char *filename, const struct elf_load_context *ctx, From 7868e45edbfca111bd8f45595b2f04ba445d8199 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sat, 10 Jan 2026 22:05:21 +0000 Subject: [PATCH 43/65] elf_loader: add elf_loadmem() to load file from memory Change-Id: Ib68b0e8b610d45713a2d5480da015cc4295f2676 --- firmware/elf_loader.c | 26 ++++++++++++++++++++++++++ firmware/include/elf_loader.h | 12 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/firmware/elf_loader.c b/firmware/elf_loader.c index d1f08f661c..637dadb886 100644 --- a/firmware/elf_loader.c +++ b/firmware/elf_loader.c @@ -183,3 +183,29 @@ int elf_loadpath(const char *filename, close(fd); return err; } + +int elf_loadmem(const void *elf_buffer, + size_t elf_size, + const struct elf_load_context *ctx, + void **entrypoint) +{ + struct elf_loadmem_state state = { + .buffer = elf_buffer, + .size = elf_size, + }; + + return elf_load(elf_read_mem_callback, (intptr_t)&state, ctx, entrypoint); +} + +int elf_read_mem_callback(intptr_t loadmem_state, off_t pos, void *buf, size_t size) +{ + struct elf_loadmem_state *state = (void *)loadmem_state; + + if (pos < 0 || (size_t)pos >= state->size) + return -1; + if (state->size - (size_t)pos < size) + return -1; + + memcpy(buf, state->buffer + pos, size); + return 0; +} diff --git a/firmware/include/elf_loader.h b/firmware/include/elf_loader.h index 344a4eb4e1..9b7550bc62 100644 --- a/firmware/include/elf_loader.h +++ b/firmware/include/elf_loader.h @@ -100,4 +100,16 @@ int elf_loadpath(const char *filename, const struct elf_load_context *ctx, void **entrypoint); +struct elf_loadmem_state +{ + const void *buffer; + size_t size; +}; + +int elf_loadmem(const void *elf_buffer, + size_t elf_size, + const struct elf_load_context *ctx, + void **entrypoint); +int elf_read_mem_callback(intptr_t loadmem_state, off_t pos, void *buf, size_t size); + #endif /* __ELF_LOADER_H__ */ From 603c5bb2bea8988d7245dd1f7518abf483903e4b Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 15:04:38 +0000 Subject: [PATCH 44/65] echoplayer: load Rockbox from SD card in bootloader To avoid problems with SDMMC DMA not being able to access all SRAMs equally, the ELF binary is loaded at the top of SDRAM and then copied into place. Change-Id: Icf16d02bc15605539cbe781dd27709225abca8f9 --- bootloader/echoplayer.c | 101 +++++++++++++++++++++++++++++++- firmware/export/config/echor1.h | 1 + 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index ad83bed339..15e4d743b4 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -26,12 +26,21 @@ #include "button.h" #include "storage.h" #include "disk.h" +#include "file.h" #include "file_internal.h" #include "usb.h" +#include "elf.h" +#include "elf_loader.h" #include "rbversion.h" #include "system-echoplayer.h" #include "gpio-stm32h7.h" +#define SDRAM_SIZE (MEMORYSIZE * 1024 * 1024) + +/* Address where Rockbox .elf binary will be cached in RAM */ +#define LOAD_SIZE (2 * 1024 * 1024) +#define LOAD_BUFFER_ADDR (STM32_SDRAM1_BASE + SDRAM_SIZE - LOAD_SIZE) + /* Events for the monitor callback to signal the main thread */ #define EV_POWER_PRESSED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0) #define EV_POWER_RELEASED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1) @@ -44,6 +53,29 @@ #define USB_UNPLUGGED_ACTIVE_TIME (30 * HZ) #define USB_UNPLUGGED_INACTIVE_TIME (3 * HZ) +static const struct elf_memory_map rb_elf_mmap[] = { + { + .addr = STM32_ITCM_BASE, + .size = STM32_ITCM_SIZE, + .flags = PF_R | PF_X, + }, + { + .addr = STM32_DTCM_BASE, + .size = STM32_DTCM_SIZE, + .flags = PF_R | PF_W, + }, + { + .addr = STM32_SDRAM1_BASE, + .size = SDRAM_SIZE - LOAD_SIZE, + .flags = PF_R | PF_W | PF_X, + }, +}; + +static const struct elf_load_context rb_elf_ctx = { + .mmap = rb_elf_mmap, + .num_mmap = ARRAYLEN(rb_elf_mmap), +}; + /* Power button monitor state */ static bool pwr_curr_state; static bool pwr_prev_state; @@ -75,6 +107,10 @@ static bool wait_for_power_released; /* Optional error message displayed on LCD */ static const char *status_msg = NULL; +/* Location of Rockbox ELF binary in memory */ +static void *elf_load_addr = NULL; +static size_t elf_load_size = 0; + /* Helper functions */ static bool is_power_button_pressed(void) { @@ -152,6 +188,7 @@ static void go_active(void) gpio_set_level(GPIO_CPU_POWER_ON, 1); storage_enable(true); + disk_mount_all(); } static void go_inactive(void) @@ -213,14 +250,74 @@ static void refresh_display(void) lcd_enable(true); } +static bool load_rockbox(void) +{ + int fd = open(BOOTDIR "/" BOOTFILE, O_RDONLY); + if (fd < 0) + { + status_msg = "Rockbox not found"; + return false; + } + + void *tmp_buf = (void *)LOAD_BUFFER_ADDR; + size_t tmp_size = LOAD_SIZE; + + ssize_t ret = read(fd, tmp_buf, tmp_size); + if (ret < 0) + { + status_msg = "I/O error loading Rockbox"; + goto out; + } + + if ((size_t)ret == tmp_size) + { + status_msg = "Rockbox binary too large"; + goto out; + } + + elf_load_addr = tmp_buf; + elf_load_size = tmp_size; + +out: + close(fd); + return elf_load_addr != NULL; +} + +static void launch_elf(void) +{ + void *entrypoint = NULL; + void (*entry_fn) (void) = NULL; + + if (elf_load_addr == NULL || elf_load_size == 0) + return; + + int err = elf_loadmem(elf_load_addr, elf_load_size, &rb_elf_ctx, &entrypoint); + if (err) + { + status_msg = "Failed to execute Rockbox"; + return; + } + + disk_unmount_all(); + storage_enable(false); + lcd_shutdown(); + + entry_fn = entrypoint; + commit_discard_idcache(); + disable_irq(); + stm32_systick_disable(); + + entry_fn(); +} + static void launch(void) { /* No-op if USB mode was requested */ if (is_usbmode_button_pressed()) return; - /* TODO: load rockbox */ - status_msg = "Can't boot RB yet!"; + load_rockbox(); + launch_elf(); } void main(void) diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 8f11ab8533..3674419c17 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -124,3 +124,4 @@ #define HAVE_HOTKEY #define AB_REPEAT_ENABLE #define HAVE_BOOTLOADER_SCREENDUMP +#define HAVE_ELF From d5506dfa2258c644a0863a14c8fd51165ffa7f54 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 15:06:38 +0000 Subject: [PATCH 45/65] echoplayer: implement boot from debugger Add a 'make start' target which starts Rockbox using a debugger. This only works to load the main binary, but makes it much faster to test changes that don't affect plugins/codecs. Because SDRAM isn't accessible at reset and the main binary is usually too big to fit in SRAM, the bootloader must be flashed first before Rockbox can be loaded in this way. The boot protocol involves GDB writing a check pattern to SRAM while the CPU is held in reset. The bootloader detects the pattern and takes a breakpoint, by which time SDRAM is accessible; GDB can then upload a binary ELF image (copied as a raw file, since the ELF will be loaded using RB's ELF loader) and leave the breakpoint to continue booting. From there the bootloader can load the ELF binary from memory, exactly like a normal SD card boot. Change-Id: I4eb971b4162ea422e38660455cfa0958cefaa18d --- bootloader/echoplayer.c | 47 +++++++++++++++++++++++++++++++++++ tools/echoplayer/openocd.make | 46 +++++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index 15e4d743b4..113c457f5f 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -41,6 +41,20 @@ #define LOAD_SIZE (2 * 1024 * 1024) #define LOAD_BUFFER_ADDR (STM32_SDRAM1_BASE + SDRAM_SIZE - LOAD_SIZE) +/* Values at GDB_MAGICx */ +#define GDB_MAGICVAL1 0x726f636b +#define GDB_MAGICVAL2 0x424f4f54 +#define GDB_MAGICVAL3 0x6764626c +#define GDB_MAGICVAL4 0x6f616455 + +/* Addresses used by GDB boot protocol */ +#define GDB_MAGIC1 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x00)) +#define GDB_MAGIC2 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x04)) +#define GDB_MAGIC3 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x08)) +#define GDB_MAGIC4 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x0c)) +#define GDB_ELFADDR (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x10)) +#define GDB_ELFSIZE (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x14)) + /* Events for the monitor callback to signal the main thread */ #define EV_POWER_PRESSED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0) #define EV_POWER_RELEASED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1) @@ -320,6 +334,35 @@ static void launch(void) launch_elf(); } +static bool handle_gdb_boot(void) +{ + /* Look for magic values that signal the GDB boot protocol */ + bool gdb_boot = (GDB_MAGIC1 == GDB_MAGICVAL1 && + GDB_MAGIC2 == GDB_MAGICVAL2 && + GDB_MAGIC3 == GDB_MAGICVAL3 && + GDB_MAGIC4 == GDB_MAGICVAL4); + + /* Clear them so they won't hang around on a system reset */ + GDB_MAGIC1 = 0; + GDB_MAGIC2 = 0; + GDB_MAGIC3 = 0; + GDB_MAGIC4 = 0; + + if (!gdb_boot) + return false; + + /* "Call" GDB by entering breakpoint */ + GDB_ELFADDR = 0; + GDB_ELFSIZE = 0; + asm volatile("bkpt"); + + /* Read location of the loaded binary */ + elf_load_addr = (void *)GDB_ELFADDR; + elf_load_size = (size_t)GDB_ELFSIZE; + + return true; +} + void main(void) { system_init(); @@ -349,6 +392,10 @@ void main(void) filesystem_init(); disk_mount_all(); + /* GDB assisted boot takes precedence */ + if (handle_gdb_boot()) + launch_elf(); + if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_REBOOT || usb_detect() == USB_INSERTED) { diff --git a/tools/echoplayer/openocd.make b/tools/echoplayer/openocd.make index f7664d128e..de9d557314 100644 --- a/tools/echoplayer/openocd.make +++ b/tools/echoplayer/openocd.make @@ -2,16 +2,56 @@ GDB := gdb OPENOCD := openocd OPENOCD_CFG := $(ROOTDIR)/tools/echoplayer/openocd.cfg +# GDB boot protocol 'registers' used for communicating with the bootloader +GDBBOOT_REG_BASE := 0x38000000 +GDBBOOT_REG_MAGIC1 := ($(GDBBOOT_REG_BASE) + 0x00) +GDBBOOT_REG_MAGIC2 := ($(GDBBOOT_REG_BASE) + 0x04) +GDBBOOT_REG_MAGIC3 := ($(GDBBOOT_REG_BASE) + 0x08) +GDBBOOT_REG_MAGIC4 := ($(GDBBOOT_REG_BASE) + 0x0c) +GDBBOOT_REG_ELFADDR := ($(GDBBOOT_REG_BASE) + 0x10) +GDBBOOT_REG_ELFSIZE := ($(GDBBOOT_REG_BASE) + 0x14) + +# Address where the RB binary will be loaded +GDBBOOT_LOAD_ADDR := 0x71e00000 +GDBBOOT_LOAD_SIZE := 0x200000 + ifneq (,$(findstring bootloader,$(APPSDIR))) TARGET_ELF := $(BUILDDIR)/bootloader.elf else TARGET_ELF := $(BUILDDIR)/rockbox.elf + TARGET_BIN := $(BUILDDIR)/rockbox.echo endif +# Attach to running process debug: - $(GDB) $(TARGET_ELF) -ex "target extended-remote | openocd -c \"gdb_port pipe\" -f $(OPENOCD_CFG)" + $(GDB) $(TARGET_ELF) \ + -ex "target extended-remote | openocd -c \"gdb_port pipe\" -f $(OPENOCD_CFG)" +.PHONY: debug +ifneq (,$(findstring bootloader,$(APPSDIR))) + +# Flash bootloader flash: $(TARGET_ELF) - $(OPENOCD) -f $(OPENOCD_CFG) -c "program $(TARGET_ELF) verify reset exit" + $(OPENOCD) -f $(OPENOCD_CFG) -c "program $< verify reset exit" +.PHONY: flash + +else + +# Start Rockbox via debugger +start: $(TARGET_BIN) + $(GDB) $(TARGET_ELF) \ + -ex "target extended-remote | openocd -c \"gdb_port pipe\" -f $(OPENOCD_CFG)" \ + -ex "monitor reset halt" \ + -ex "set *$(GDBBOOT_REG_MAGIC1) = 0x726f636b" \ + -ex "set *$(GDBBOOT_REG_MAGIC2) = 0x424f4f54" \ + -ex "set *$(GDBBOOT_REG_MAGIC3) = 0x6764626c" \ + -ex "set *$(GDBBOOT_REG_MAGIC4) = 0x6f616455" \ + -ex "continue" \ + -ex "restore $(TARGET_BIN) binary $(GDBBOOT_LOAD_ADDR)" \ + -ex "set *$(GDBBOOT_REG_ELFADDR) = $(GDBBOOT_LOAD_ADDR)" \ + -ex "set *$(GDBBOOT_REG_ELFSIZE) = $(GDBBOOT_LOAD_SIZE)" \ + -ex "continue" +.PHONY: start + +endif -.PHONY: debug flash From 1951c17e0bc0822960f536587c1272ed5d9b64bf Mon Sep 17 00:00:00 2001 From: mojyack Date: Thu, 15 Jan 2026 16:46:03 +0900 Subject: [PATCH 46/65] pass event data to usb_acknowledge add second argument to usb_acknowledge. it can be used for more appropriate connection tracking that does not rely on timeout in the future. Change-Id: I8a44366b7c7a1f944524c4ba8ecd6d9673746a65 --- apps/audio_thread.c | 2 +- apps/gui/usb_screen.c | 4 ++-- apps/gui/usb_screen.h | 6 ++++-- apps/iap/iap-core.c | 2 +- apps/main.c | 4 ++-- apps/misc.c | 15 ++++++++------- apps/playlist.c | 2 +- apps/plugin.h | 2 +- apps/plugins/announce_status.c | 2 +- apps/plugins/battery_bench.c | 2 +- apps/plugins/tagcache/tagcache.c | 2 +- apps/plugins/test_usb.c | 2 +- apps/tagcache.c | 2 +- bootloader/echoplayer.c | 2 +- bootloader/gigabeat-s.c | 2 +- bootloader/imx233.c | 2 +- bootloader/ipod-s5l87xx.c | 2 +- bootloader/main-pp.c | 2 +- bootloader/ondavx747.c | 2 +- bootloader/sansaconnect.c | 2 +- bootloader/x1000/gui.c | 2 ++ bootloader/x1000/utils.c | 3 ++- bootloader/xduoox3.c | 2 +- docs/PLUGIN_API | 3 ++- firmware/backlight.c | 2 +- firmware/export/usb.h | 6 +++--- firmware/scroll_engine.c | 2 +- firmware/storage.c | 2 +- firmware/target/arm/ipod/piezo.c | 2 +- .../tms320dm320/sansa-connect/avr-sansaconnect.c | 2 +- firmware/target/arm/usb-tcc.c | 2 +- firmware/usb.c | 7 ++++--- uisimulator/common/sim_tasks.c | 4 ++-- 33 files changed, 54 insertions(+), 46 deletions(-) diff --git a/apps/audio_thread.c b/apps/audio_thread.c index 74f18454cc..c58d509875 100644 --- a/apps/audio_thread.c +++ b/apps/audio_thread.c @@ -101,7 +101,7 @@ static void NORETURN_ATTR audio_thread(void) case SYS_USB_CONNECTED: LOGFQUEUE("audio < SYS_USB_CONNECTED"); voice_stop(); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); usb_wait_for_disconnect(&audio_queue); break; } diff --git a/apps/gui/usb_screen.c b/apps/gui/usb_screen.c index e16428e1e5..6d57a09b08 100644 --- a/apps/gui/usb_screen.c +++ b/apps/gui/usb_screen.c @@ -248,7 +248,7 @@ static void usb_screens_draw(struct usb_screen_vps_t *usb_screen_vps_ar) } } -void gui_usb_screen_run(bool early_usb) +void gui_usb_screen_run(bool early_usb, intptr_t seqnum) { #ifdef SIMULATOR /* the sim allows toggling USB fast enough to overflow viewportmanagers stack */ static bool in_usb_screen = false; @@ -297,7 +297,7 @@ void gui_usb_screen_run(bool early_usb) font_disable_all(); } - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, seqnum); while (1) { diff --git a/apps/gui/usb_screen.h b/apps/gui/usb_screen.h index 321c396fc9..6ee467988d 100644 --- a/apps/gui/usb_screen.h +++ b/apps/gui/usb_screen.h @@ -21,10 +21,12 @@ #ifndef _USB_SCREEN_H_ #define _USB_SCREEN_H_ +#include + #ifdef USB_NONE -#define gui_usb_screen_run(early_usb) do {} while(0) +#define gui_usb_screen_run(early_usb, seqnum) do {} while(0) #else -extern void gui_usb_screen_run(bool early_usb); +extern void gui_usb_screen_run(bool early_usb, intptr_t seqnum); #endif #endif diff --git a/apps/iap/iap-core.c b/apps/iap/iap-core.c index da58d49ff8..a9ed8c6c4b 100644 --- a/apps/iap/iap-core.c +++ b/apps/iap/iap-core.c @@ -404,7 +404,7 @@ static void iap_thread(void) /* Ack USB thread */ case SYS_USB_CONNECTED: { - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); break; } } diff --git a/apps/main.c b/apps/main.c index 70f4cb5b04..c25847430f 100644 --- a/apps/main.c +++ b/apps/main.c @@ -618,7 +618,7 @@ static void init(void) (mmc_remove_request() == SYS_HOTSWAP_EXTRACTED)) #endif { - gui_usb_screen_run(true); + gui_usb_screen_run(true, button_get_data()); mounted = true; /* mounting done @ end of USB mode */ } #ifdef HAVE_USB_POWER @@ -675,7 +675,7 @@ static void init(void) #ifndef USB_NONE usb_start_monitoring(); while(button_get(true) != SYS_USB_CONNECTED) {}; - gui_usb_screen_run(true); + gui_usb_screen_run(true, button_get_data()); #elif !defined(DEBUG) && !(CONFIG_STORAGE & STORAGE_RAMDISK) sleep(HZ*5); #endif diff --git a/apps/misc.c b/apps/misc.c index 778e524b91..39936f2b36 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -656,24 +656,25 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame } break; case SYS_USB_CONNECTED: + { + intptr_t seqnum = button_get_data(); if (callback != NULL) callback(parameter); - { - system_flush(); + system_flush(); #ifdef BOOTFILE #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) - check_bootfile(false); /* gets initial size */ + check_bootfile(false); /* gets initial size */ #endif #endif - gui_usb_screen_run(false); + gui_usb_screen_run(false, seqnum); #ifdef BOOTFILE #if !defined(USB_NONE) && !defined(USB_HANDLED_BY_OF) - check_bootfile(true); + check_bootfile(true); #endif #endif - system_restore(); - } + system_restore(); return SYS_USB_CONNECTED; + } case SYS_POWEROFF: case SYS_REBOOT: diff --git a/apps/playlist.c b/apps/playlist.c index e06d36e605..1543fad262 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -1882,7 +1882,7 @@ static void dc_thread_playlist(void) } case SYS_USB_CONNECTED: - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); usb_wait_for_disconnect(&playlist_queue); break; } diff --git a/apps/plugin.h b/apps/plugin.h index 0f63dbe839..20190846c5 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -948,7 +948,7 @@ struct plugin_api { /* usb */ bool (*usb_inserted)(void); - void (*usb_acknowledge)(long id); + void (*usb_acknowledge)(long id, intptr_t seqnum); #ifdef USB_ENABLE_HID void (*usb_hid_send)(usage_page_t usage_page, int id); #endif diff --git a/apps/plugins/announce_status.c b/apps/plugins/announce_status.c index cd0482f500..f71d25af6f 100644 --- a/apps/plugins/announce_status.c +++ b/apps/plugins/announce_status.c @@ -435,7 +435,7 @@ void thread(void) switch (ev.id) { case SYS_USB_CONNECTED: - rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); in_usb = true; break; case SYS_USB_DISCONNECTED: diff --git a/apps/plugins/battery_bench.c b/apps/plugins/battery_bench.c index cc242c169f..fc67b306b0 100644 --- a/apps/plugins/battery_bench.c +++ b/apps/plugins/battery_bench.c @@ -491,7 +491,7 @@ static void thread(void) { case SYS_USB_CONNECTED: in_usb_mode = true; - rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); break; case SYS_USB_DISCONNECTED: in_usb_mode = false; diff --git a/apps/plugins/tagcache/tagcache.c b/apps/plugins/tagcache/tagcache.c index 483d2fbaf9..c5ee21e870 100644 --- a/apps/plugins/tagcache/tagcache.c +++ b/apps/plugins/tagcache/tagcache.c @@ -979,7 +979,7 @@ static void thread(void) switch (ev.id) { case SYS_USB_CONNECTED: - rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); logenabled = false; break; case SYS_USB_DISCONNECTED: diff --git a/apps/plugins/test_usb.c b/apps/plugins/test_usb.c index 28ef8f7e5f..11c058757a 100644 --- a/apps/plugins/test_usb.c +++ b/apps/plugins/test_usb.c @@ -58,7 +58,7 @@ static void main_loop(void) state = "connected"; logf("test_usb: connect ack %ld", *rb->current_tick); DEBUGF("test_usb: connect ack %ld\n", *rb->current_tick); - rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); break; case SYS_USB_DISCONNECTED: diff --git a/apps/tagcache.c b/apps/tagcache.c index 443f18b7a4..8b6a46d937 100644 --- a/apps/tagcache.c +++ b/apps/tagcache.c @@ -5357,7 +5357,7 @@ static void tagcache_thread(void) case SYS_USB_CONNECTED: logf("USB: TagCache"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); usb_wait_for_disconnect(&tagcache_queue); break ; } diff --git a/bootloader/echoplayer.c b/bootloader/echoplayer.c index 113c457f5f..6f742aec50 100644 --- a/bootloader/echoplayer.c +++ b/bootloader/echoplayer.c @@ -470,7 +470,7 @@ void main(void) case SYS_USB_CONNECTED: go_active(); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); break; } } diff --git a/bootloader/gigabeat-s.c b/bootloader/gigabeat-s.c index 2829241b9b..831f854dc8 100644 --- a/bootloader/gigabeat-s.c +++ b/bootloader/gigabeat-s.c @@ -123,7 +123,7 @@ static void handle_usb(int connect_timeout) /* Got the message - wait for disconnect */ printf("Bootloader USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while (1) { diff --git a/bootloader/imx233.c b/bootloader/imx233.c index efe19bf0c2..bb8189cf6e 100644 --- a/bootloader/imx233.c +++ b/bootloader/imx233.c @@ -96,7 +96,7 @@ static void usb_mode(int connect_timeout) adc_init(); /* ack the SYS_USB_CONNECTED polled from the button queue */ - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while(1) { diff --git a/bootloader/ipod-s5l87xx.c b/bootloader/ipod-s5l87xx.c index 3a53f612b0..0558d8da5b 100644 --- a/bootloader/ipod-s5l87xx.c +++ b/bootloader/ipod-s5l87xx.c @@ -129,7 +129,7 @@ static void usb_mode(void) printf("Bootloader USB mode"); /* Ack the SYS_USB_CONNECTED polled from the button queue */ - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while(1) { diff --git a/bootloader/main-pp.c b/bootloader/main-pp.c index f2bfa48157..0bd4dcbca0 100644 --- a/bootloader/main-pp.c +++ b/bootloader/main-pp.c @@ -230,7 +230,7 @@ static int handle_usb(int connect_timeout) printf("Bootloader USB mode"); usb = USB_HANDLED; - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); #if defined(SANSA_E200) && defined(HAVE_BOOTLOADER_USB_MODE) /* E200 misses unplug randomly probably fine for other targets too but needs tested */ diff --git a/bootloader/ondavx747.c b/bootloader/ondavx747.c index 9309b78f88..b32a2ab151 100644 --- a/bootloader/ondavx747.c +++ b/bootloader/ondavx747.c @@ -75,7 +75,7 @@ static void usb_mode(void) /* Got the message - wait for disconnect */ show_splash(0, "Bootloader USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while (1) { diff --git a/bootloader/sansaconnect.c b/bootloader/sansaconnect.c index 65ca9ff3d7..c8cfecbc61 100644 --- a/bootloader/sansaconnect.c +++ b/bootloader/sansaconnect.c @@ -196,7 +196,7 @@ static void handle_usb(int connect_timeout) if (button_get_w_tmo(HZ/2) == SYS_USB_CONNECTED) { printf("Bootloader USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while (button_get_w_tmo(HZ/2) != SYS_USB_DISCONNECTED) { storage_spin(); diff --git a/bootloader/x1000/gui.c b/bootloader/x1000/gui.c index a15f315a44..a235618e70 100644 --- a/bootloader/x1000/gui.c +++ b/bootloader/x1000/gui.c @@ -34,6 +34,7 @@ static bool lcd_inited = false; extern bool is_usb_connected; +extern intptr_t usb_connection_seqnum; void clearscreen(void) { @@ -134,6 +135,7 @@ int get_button(int timeout) case SYS_USB_CONNECTED: case SYS_USB_DISCONNECTED: is_usb_connected = (btn == SYS_USB_CONNECTED); + usb_connection_seqnum = button_get_data(); break; #ifdef HAVE_SCREENDUMP case BL_SCREENSHOT: diff --git a/bootloader/x1000/utils.c b/bootloader/x1000/utils.c index 8d296b234b..fecf7908d1 100644 --- a/bootloader/x1000/utils.c +++ b/bootloader/x1000/utils.c @@ -38,6 +38,7 @@ * Handled by the gui code since that's how events are delivered * TODO: this is an ugly kludge */ bool is_usb_connected = false; +intptr_t usb_connection_seqnum = 0; static bool screenshot_enabled = false; @@ -71,7 +72,7 @@ void usb_mode(void) return; splashf(0, "USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, usb_connection_seqnum); while(is_usb_connected) get_button(TIMEOUT_BLOCK); diff --git a/bootloader/xduoox3.c b/bootloader/xduoox3.c index a8d9fc4a1d..4a680664e0 100644 --- a/bootloader/xduoox3.c +++ b/bootloader/xduoox3.c @@ -105,7 +105,7 @@ static void usb_mode(void) /* Got the message - wait for disconnect */ show_splash(0, "Bootloader USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data()); while(1) { button = button_get_w_tmo(HZ/2); diff --git a/docs/PLUGIN_API b/docs/PLUGIN_API index 7c43225970..f8bf383896 100644 --- a/docs/PLUGIN_API +++ b/docs/PLUGIN_API @@ -2996,9 +2996,10 @@ void unregister_storage_idle_func(void (*function)(void), bool run) \param run \description -void usb_acknowledge(long id) +void usb_acknowledge(long id, intptr_t seqnum) \group usb \param id + \param seqnum \description void usb_hid_send(usage_page_t usage_page, int id) diff --git a/firmware/backlight.c b/firmware/backlight.c index f3d82d7050..e28793253b 100644 --- a/firmware/backlight.c +++ b/firmware/backlight.c @@ -605,7 +605,7 @@ void backlight_thread(void) #endif /* HAVE_REMOTE_LCD/ HAVE_REMOTE_LCD_AS_MAIN */ #endif /* !SIMULATOR */ case SYS_USB_CONNECTED: - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); break; #ifdef BACKLIGHT_DRIVER_CLOSE diff --git a/firmware/export/usb.h b/firmware/export/usb.h index 13271652f9..2770c0d07b 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -70,8 +70,8 @@ * mass storage mode, it will require exclusive access to the disk and ask all * threads to release any file handle and stop using the disks. It does so by * broadcasting a SYS_USB_CONNECTED message, which threads must acknowledge using - * usb_acknowledge(SYS_USB_CONNECTED_ACK). They must not access the disk until - * SYS_USB_DISCONNECTED is broadcast. To ease waiting, threads can call + * usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data). They must not access the disk + * until SYS_USB_DISCONNECTED is broadcast. To ease waiting, threads can call * usb_wait_for_disconnect() or usb_wait_for_disconnect_w_tmo() on their waiting * queue. * @@ -198,7 +198,7 @@ void usb_attach(void); void usb_start_monitoring(void) INIT_ATTR; void usb_close(void); /* acknowledge usb connection, typically with SYS_USB_CONNECTED_ACK */ -void usb_acknowledge(long id); +void usb_acknowledge(long id, intptr_t seqnum); /* block the current thread until SYS_USB_DISCONNECTED has been broadcast */ void usb_wait_for_disconnect(struct event_queue *q); /* same as usb_wait_for_disconnect() but with a timeout, returns 1 on timeout */ diff --git a/firmware/scroll_engine.c b/firmware/scroll_engine.c index c39e9f0be9..79609e5b63 100644 --- a/firmware/scroll_engine.c +++ b/firmware/scroll_engine.c @@ -89,7 +89,7 @@ static bool scroll_process_message(int delay) case SYS_TIMEOUT: return false; case SYS_USB_CONNECTED: - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); usb_wait_for_disconnect(&scroll_queue); sync_display_ticks(); return true; diff --git a/firmware/storage.c b/firmware/storage.c index 341c007f7e..5134e0f0e3 100644 --- a/firmware/storage.c +++ b/firmware/storage.c @@ -236,7 +236,7 @@ static void NORETURN_ATTR storage_thread(void) storage_event_send(CONFIG_STORAGE, ev.id, (intptr_t)&bdcast); usb_mode = ev.id == SYS_USB_CONNECTED; if (usb_mode) { - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); } else { bdcast = CONFIG_STORAGE; diff --git a/firmware/target/arm/ipod/piezo.c b/firmware/target/arm/ipod/piezo.c index 898f12dd24..40a685c489 100644 --- a/firmware/target/arm/ipod/piezo.c +++ b/firmware/target/arm/ipod/piezo.c @@ -108,7 +108,7 @@ static void piezo_thread(void) /*logf("USB: Piezo core");*/ piezo_hw_stop(); queue_clear(&piezo_queue); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); usb_wait_for_disconnect(&piezo_queue); break ; #endif diff --git a/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c b/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c index 0d9e9722c8..a7b982b645 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/avr-sansaconnect.c @@ -838,7 +838,7 @@ void avr_thread(void) if (ev.id == SYS_USB_CONNECTED) { /* Allow USB to gain exclusive storage access */ - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, ev.data); disk_access_available = false; } else if (ev.id == SYS_USB_DISCONNECTED) diff --git a/firmware/target/arm/usb-tcc.c b/firmware/target/arm/usb-tcc.c index 0083655434..ba055b4cb7 100644 --- a/firmware/target/arm/usb-tcc.c +++ b/firmware/target/arm/usb-tcc.c @@ -747,7 +747,7 @@ void usb_test(void) usb_init(); usb_start_monitoring(); - usb_acknowledge(SYS_USB_CONNECTED_ACK); + usb_acknowledge(SYS_USB_CONNECTED_ACK, 0); while (1) { sleep(HZ); diff --git a/firmware/usb.c b/firmware/usb.c index 4c0505daca..514284320a 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -765,9 +765,9 @@ void usb_start_monitoring(void) #endif /* USB_STATUS_BY_EVENT */ #endif /* USB_FULL_INIT */ -void usb_acknowledge(long id) +void usb_acknowledge(long id, intptr_t seqnum) { - queue_post(&usb_queue, id, 0); + queue_post(&usb_queue, id, seqnum); } void usb_init(void) @@ -901,9 +901,10 @@ bool usb_inserted(void) return false; } -void usb_acknowledge(long id) +void usb_acknowledge(long id, intptr_t seqnum) { (void)id; + (void)seqnum; } void usb_init(void) diff --git a/uisimulator/common/sim_tasks.c b/uisimulator/common/sim_tasks.c index 6ac2376316..8a02aa5c80 100644 --- a/uisimulator/common/sim_tasks.c +++ b/uisimulator/common/sim_tasks.c @@ -203,9 +203,9 @@ void usb_start_monitoring(void) { } -void usb_acknowledge(long id) +void usb_acknowledge(long id, intptr_t seqnum) { - queue_post(&sim_queue, id, 0); + queue_post(&sim_queue, id, seqnum); } void usb_wait_for_disconnect(struct event_queue *q) From 33d0a3efa3fc1178bcf2211f8dd1ed33672de15c Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 16 Jan 2026 17:08:12 +0900 Subject: [PATCH 47/65] usb: enter to exclusive disk mode only when required by usb config currently, exclusive disk mode was enabled at the same time as the usb insertion. when multiple usb configurations exist, this is not appropriate. manage it in the core and enable it only when necessary. Change-Id: Iadbec05fad1d1319471233227ae0e72c12079295 --- firmware/export/usb.h | 9 +- firmware/export/usb_core.h | 1 - firmware/usb.c | 210 ++++++++++++++++++----------------- firmware/usbstack/usb_core.c | 22 ++-- 4 files changed, 129 insertions(+), 113 deletions(-) diff --git a/firmware/export/usb.h b/firmware/export/usb.h index 2770c0d07b..c16fecc69f 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -189,9 +189,7 @@ struct usb_transfer_completion_event_data void usb_init(void) INIT_ATTR; /* target must implement this to enable/disable the usb transceiver/core */ void usb_enable(bool on); -/* when one or more driver requires exclusive mode, this is called after all threads have acknowledged - * exclusive mode and disk have been umounted; otherwise it is called immediately after host has - * been detected */ +/* called after host has been detected */ void usb_attach(void); /* enable usb detection monitoring; before this function is called, all usb * detection changes are ignored */ @@ -252,6 +250,11 @@ bool usb_driver_enabled(int driver); bool usb_exclusive_storage(void); #endif /* HAVE_USBSTACK */ +/* broadcast usb insertion event to enable exclusive storage */ +void usb_request_exclusive_storage(void); +/* finish exclusive storage access if enabled and mount volumes */ +void usb_release_exclusive_storage(void); + #ifdef USB_FIREWIRE_HANDLING bool firewire_detect(void); void usb_firewire_connect_event(void); diff --git a/firmware/export/usb_core.h b/firmware/export/usb_core.h index 6cee077c0d..1e940dc8e3 100644 --- a/firmware/export/usb_core.h +++ b/firmware/export/usb_core.h @@ -56,7 +56,6 @@ void usb_core_control_complete(int status); void usb_core_legacy_control_request(struct usb_ctrlrequest* req); void usb_core_transfer_complete(int endpoint,int dir,int status,int length); void usb_core_bus_reset(void); -bool usb_core_any_exclusive_storage(void); void usb_core_enable_driver(int driver,bool enabled); bool usb_core_driver_enabled(int driver); void usb_core_handle_transfer_completion( diff --git a/firmware/usb.c b/firmware/usb.c index 514284320a..4727e1b5ff 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -102,7 +102,8 @@ static unsigned int usb_thread_entry = 0; static bool usb_monitor_enabled = false; #endif /* USB_FULL_INIT */ static struct event_queue usb_queue SHAREDBSS_ATTR; -static bool exclusive_storage_access = false; +static bool exclusive_storage_requested = false; +static bool exclusive_storage_enabled = false; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif @@ -113,14 +114,12 @@ static int usb_audio = 0; #ifdef USB_FULL_INIT static bool usb_host_present = false; static int usb_num_acks_to_expect = 0; -static long usb_last_broadcast_tick = 0; +static uint32_t usb_broadcast_seqnum = 0x80000000; #ifdef HAVE_USB_POWER static int usb_mode = USBMODE_DEFAULT; -static int new_usbmode = USBMODE_DEFAULT; +static bool usb_power_only = false; #endif -static int usb_release_exclusive_storage(void); - #if defined(USB_FIREWIRE_HANDLING) static void try_reboot(void) { @@ -206,7 +205,7 @@ static inline void usb_handle_hotswap(long id) } #endif /* HAVE_HOTSWAP */ -static inline bool usb_configure_drivers(int for_state) +static inline void usb_configure_drivers(int for_state) { #ifdef USB_ENABLE_AUDIO // FIXME: doesn't seem to get set when loaded at boot... @@ -232,9 +231,8 @@ static inline bool usb_configure_drivers(int for_state) #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, true); #endif - exclusive_storage_access = false; - usb_attach(); /* Powered only: attach now. */ + usb_attach(); break; /* USB_POWERED: */ @@ -251,25 +249,17 @@ static inline bool usb_configure_drivers(int for_state) #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, false); #endif - /* Check any drivers enabled at this point for exclusive storage - * access requirements. */ - exclusive_storage_access = usb_core_any_exclusive_storage(); - if(exclusive_storage_access) - return true; - - usb_attach(); /* Not exclusive: attach now. */ + usb_attach(); break; /* USB_INSERTED: */ case USB_EXTRACTED: - if(exclusive_storage_access) - usb_release_exclusive_storage(); + /* do not call usb_release_exclusive_storage. + * usb core handles it */ break; /* USB_EXTRACTED: */ } - - return false; } static inline void usb_slave_mode(bool on) @@ -283,9 +273,8 @@ static inline void usb_slave_mode(bool on) thread_set_priority(thread_self(), PRIORITY_REALTIME); #endif disk_unmount_all(); - usb_attach(); } - else /* usb_state == USB_INSERTED (only!) */ + else { #ifdef HAVE_PRIORITY_SCHEDULING thread_set_priority(thread_self(), PRIORITY_SYSTEM); @@ -350,23 +339,20 @@ static inline void usb_handle_hotswap(long id) } #endif /* HAVE_HOTSWAP */ -static inline bool usb_configure_drivers(int for_state) +static inline void usb_configure_drivers(int for_state) { switch(for_state) { case USB_POWERED: - exclusive_storage_access = false; + exclusive_storage_requested = false; break; case USB_INSERTED: - exclusive_storage_access = true; - return true; + usb_request_exclusive_storage(); + break; case USB_EXTRACTED: - if(exclusive_storage_access) - usb_release_exclusive_storage(); + usb_release_exclusive_storage(); break; } - - return false; } static inline void usb_slave_mode(bool on) @@ -420,7 +406,7 @@ static void usb_set_host_present(bool present) } #ifdef HAVE_USB_POWER - if (new_usbmode == USB_MODE_CHARGE || new_usbmode == USB_MODE_ADB) + if (usb_power_only) { /* Only charging is desired */ usb_configure_drivers(USB_POWERED); @@ -428,44 +414,7 @@ static void usb_set_host_present(bool present) } #endif - if(!usb_configure_drivers(USB_INSERTED)) - return; /* Exclusive storage access not required */ - - /* Tell all threads that they have to back off the storage. - We subtract one for our own thread. Expect an ACK for every - listener for each broadcast they received. If it has been too - long, the user might have entered a screen that didn't ACK - when inserting the cable, such as a debugging screen. In that - case, reset the count or else USB would be locked out until - rebooting because it most likely won't ever come. Simply - resetting to the most recent broadcast count is racy. */ - if(TIME_AFTER(current_tick, usb_last_broadcast_tick + HZ*5)) - { - usb_num_acks_to_expect = 0; - usb_last_broadcast_tick = current_tick; - } - - usb_num_acks_to_expect += queue_broadcast(SYS_USB_CONNECTED, 0) - 1; - DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); -} - -static bool usb_handle_connected_ack(void) -{ - if(usb_num_acks_to_expect > 0 && --usb_num_acks_to_expect == 0) - { - DEBUGF("usb: all threads have acknowledged the connect.\n"); - if(usb_host_present) - { - usb_slave_mode(true); - return true; - } - } - else - { - DEBUGF("usb: got ack, %d to go...\n", usb_num_acks_to_expect); - } - - return false; + usb_configure_drivers(USB_INSERTED); } /*--- General driver code ---*/ @@ -496,6 +445,7 @@ static void NORETURN_ATTR usb_thread(void) break; #ifdef USB_DETECT_BY_REQUEST + usb_state = USB_INSERTED; usb_set_host_present(true); #endif @@ -523,32 +473,40 @@ static void NORETURN_ATTR usb_thread(void) #endif send_event(SYS_EVENT_USB_INSERTED, &usb_mode); #endif - /* Power (charging-only) button */ #ifdef HAVE_USB_POWER - new_usbmode = usb_mode; - switch (usb_mode) { - case USB_MODE_CHARGE: - case USB_MODE_ADB: - if (button_status() & ~USBPOWER_BTN_IGNORE) - new_usbmode = USB_MODE_MASS_STORAGE; - break; - default: - case USB_MODE_MASS_STORAGE: - if (button_status() & ~USBPOWER_BTN_IGNORE) - new_usbmode = USB_MODE_CHARGE; - break; - } + /* Power (charging-only) button */ + usb_power_only = usb_mode != USB_MODE_MASS_STORAGE; + if(button_status() & ~USBPOWER_BTN_IGNORE) { + usb_power_only = !usb_power_only; + } #endif #ifndef USB_DETECT_BY_REQUEST + usb_state = USB_INSERTED; usb_set_host_present(true); #endif break; /* USB_INSERTED */ case SYS_USB_CONNECTED_ACK: - if(usb_handle_connected_ack()) - usb_state = USB_INSERTED; + if((uint32_t)ev.data != usb_broadcast_seqnum) { + DEBUGF("usb: late ack %lX < %lX", ev.data, usb_broadcast_seqnum); + break; + } + if(usb_num_acks_to_expect == 0) { + DEBUGF("usb: unexpected ack"); + break; + } + if(--usb_num_acks_to_expect > 0) { + DEBUGF("usb: got ack, %d to go...\n", usb_num_acks_to_expect); + break; + } + + DEBUGF("usb: all threads have acknowledged the connect.\n"); + if(usb_host_present && exclusive_storage_requested) { + usb_slave_mode(true); + exclusive_storage_enabled = true; + } break; /* SYS_USB_CONNECTED_ACK */ @@ -563,15 +521,7 @@ static void NORETURN_ATTR usb_thread(void) iap_reset_state(IF_IAP_MP(0)); #endif - /* Only disable the USB slave mode if we really have enabled - it. Some expected acks may not have been received. */ - if(usb_state == USB_INSERTED) - usb_slave_mode(false); - usb_state = USB_EXTRACTED; -#ifdef HAVE_USB_POWER - new_usbmode = usb_mode; -#endif #ifndef BOOTLOADER send_event(SYS_EVENT_USB_EXTRACTED, NULL); #endif @@ -857,18 +807,80 @@ bool usb_inserted(void) bool usb_exclusive_storage(void) { /* Storage isn't actually exclusive until slave mode has been entered */ - return exclusive_storage_access && usb_state == USB_INSERTED; + return exclusive_storage_enabled; } #endif /* HAVE_USBSTACK */ -int usb_release_exclusive_storage(void) +/* exclusive storage mode transision + * HAVE_USBSTACK: + * (inserted) + * usb_set_host_present(true) + * usb_configure_drivers(USB_INSERTED) + * ... + * (SET_CONFIG(n) which requires exclusive storage) + * usb_core_do_set_config(n) + * usb_request_exclusive_storage() + * exclusive_storage_requested = true + * ... + * (all threads acked) + * usb_slave_mode(true) + * disk_unmount_all() + * exclusive_storage_enabled = true + * (exclusive mode done) + * ... + * (extracted, or SET_CONFIG(m) which does not require exclusive storage) + * usb_core_do_set_config(m) + * usb_release_exclusive_storage() + * exclusive_storage_requested = false + * exclusive_storage_enabled = false + * usb_slave_mode(false) + * disk_mount_all() + * + * !HAVE_USBSTACK: + * (inserted) + * usb_set_host_present(true) + * usb_configure_drivers(USB_INSERTED) + * usb_request_exclusive_storage() + * exclusive_storage_requested = true + * ... + * (all threads acked) + * usb_slave_mode(true) + * disk_unmount_all() + * exclusive_storage_enabled = true + * ... + * (extracted) + * usb_set_host_present(false) + * usb_configure_drivers(USB_EXTRACTED) + * usb_release_exclusive_storage() + * .. + * */ + +void usb_request_exclusive_storage(void) { - int bccount; - exclusive_storage_access = false; + exclusive_storage_requested = true; + usb_broadcast_seqnum += 1; + usb_num_acks_to_expect = queue_broadcast(SYS_USB_CONNECTED, usb_broadcast_seqnum) - 1; + DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); +} + +void usb_release_exclusive_storage(void) +{ + if(!exclusive_storage_requested) { + return; + } + exclusive_storage_requested = false; + + if(exclusive_storage_enabled) { + usb_slave_mode(false); + } + exclusive_storage_enabled = false; + +#ifdef DEBUG /* Tell all threads that we are back in business */ - bccount = queue_broadcast(SYS_USB_DISCONNECTED, 0) - 1; + int bccount = queue_broadcast(SYS_USB_DISCONNECTED, 0) - 1; DEBUGF("USB extracted. Broadcast to %d threads...\n", bccount); - return bccount; +#endif + return; } #ifdef USB_ENABLE_HID @@ -889,7 +901,7 @@ void usb_set_audio(int value) #ifdef HAVE_USB_POWER bool usb_powered_only(void) { - return usb_state == USB_POWERED; + return usb_power_only; } #endif /* HAVE_USB_POWER */ diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 3f2aff4310..da02d256a3 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -544,16 +544,6 @@ bool usb_core_driver_enabled(int driver) return drivers[driver].enabled; } -bool usb_core_any_exclusive_storage(void) -{ - int i; - for(i = 0; i < USB_NUM_DRIVERS; i++) - if(drivers[i].enabled && drivers[i].needs_exclusive_storage) - return true; - - return false; -} - #ifdef HAVE_HOTSWAP void usb_core_hotswap_event(int volume, bool inserted) { @@ -874,16 +864,28 @@ static int usb_core_do_set_config(uint8_t new_config) usb_config = new_config; usb_state = usb_config == 0 ? ADDRESS : CONFIGURED; + bool require_exclusive = false; + /* activate 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(require_exclusive) { + if(!usb_exclusive_storage()) { + usb_release_exclusive_storage(); + usb_request_exclusive_storage(); + } + } else { + usb_release_exclusive_storage(); + } + #ifdef HAVE_USB_CHARGING_ENABLE usb_charging_maxcurrent_change(usb_charging_maxcurrent()); #endif From 89cf5b57e6158ca9900c124f779b257e532fa92e Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Mon, 26 Jan 2026 20:34:49 -0500 Subject: [PATCH 48/65] Fix yellow from 1951c17e0bc on targets with USB_NONE gui_usb_screen_run() is a do{} while(0) macro, resulting in an unused variable warning in the "caller" Change-Id: I4b4b00ef38decfb5cc9db0da3d81ad0c9a4207d1 --- apps/gui/usb_screen.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gui/usb_screen.h b/apps/gui/usb_screen.h index 6ee467988d..aaf5d58a3f 100644 --- a/apps/gui/usb_screen.h +++ b/apps/gui/usb_screen.h @@ -24,7 +24,7 @@ #include #ifdef USB_NONE -#define gui_usb_screen_run(early_usb, seqnum) do {} while(0) +#define gui_usb_screen_run(early_usb, seqnum) do {(void)seqnum;} while(0) #else extern void gui_usb_screen_run(bool early_usb, intptr_t seqnum); #endif From b562c9d58f044b1bb471a42ad070daf1bc0ec1c6 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Mon, 26 Jan 2026 22:51:06 -0500 Subject: [PATCH 49/65] Fix red introduced in 33d0a3ef Many bootloaders broke because they have only partial USB implementations -- HAVE_USBSTACK but without HAVE_BOOTLOADER_USB_MODE... and a pile of exceptions. Those exceptions need to be cleaned up properly, but for now, get the build going again by wrapping the new ack-tracking bits with the USB_FULL_INIT define created by the above conditions. Change-Id: I936d4989b8c8195ee30d53808f61104a4986942a --- firmware/usb.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/firmware/usb.c b/firmware/usb.c index 4727e1b5ff..9d9b221e0a 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -100,10 +100,10 @@ static long usb_stack[(DEFAULT_STACK_SIZE*4 + DUMP_BMP_LINESIZE + USB_EXTRA_STAC static const char usb_thread_name[] = "usb"; static unsigned int usb_thread_entry = 0; static bool usb_monitor_enabled = false; +static bool exclusive_storage_enabled = false; #endif /* USB_FULL_INIT */ static struct event_queue usb_queue SHAREDBSS_ATTR; static bool exclusive_storage_requested = false; -static bool exclusive_storage_enabled = false; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif @@ -111,13 +111,16 @@ static bool usb_hid = true; static int usb_audio = 0; #endif +#ifdef HAVE_USB_POWER +static bool usb_power_only = false; +#endif + #ifdef USB_FULL_INIT static bool usb_host_present = false; static int usb_num_acks_to_expect = 0; static uint32_t usb_broadcast_seqnum = 0x80000000; #ifdef HAVE_USB_POWER static int usb_mode = USBMODE_DEFAULT; -static bool usb_power_only = false; #endif #if defined(USB_FIREWIRE_HANDLING) @@ -489,6 +492,7 @@ static void NORETURN_ATTR usb_thread(void) /* USB_INSERTED */ case SYS_USB_CONNECTED_ACK: +#ifdef USB_FULL_INIT if((uint32_t)ev.data != usb_broadcast_seqnum) { DEBUGF("usb: late ack %lX < %lX", ev.data, usb_broadcast_seqnum); break; @@ -503,6 +507,7 @@ static void NORETURN_ATTR usb_thread(void) } DEBUGF("usb: all threads have acknowledged the connect.\n"); +#endif if(usb_host_present && exclusive_storage_requested) { usb_slave_mode(true); exclusive_storage_enabled = true; @@ -803,7 +808,7 @@ bool usb_inserted(void) return usb_state == USB_INSERTED || usb_state == USB_POWERED; } -#ifdef HAVE_USBSTACK +#if defined(HAVE_USBSTACK) && defined(USB_FULL_INIT) bool usb_exclusive_storage(void) { /* Storage isn't actually exclusive until slave mode has been entered */ @@ -858,11 +863,14 @@ bool usb_exclusive_storage(void) void usb_request_exclusive_storage(void) { exclusive_storage_requested = true; +#ifdef USB_FULL_INIT usb_broadcast_seqnum += 1; usb_num_acks_to_expect = queue_broadcast(SYS_USB_CONNECTED, usb_broadcast_seqnum) - 1; DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); +#endif } +#ifdef USB_FULL_INIT void usb_release_exclusive_storage(void) { if(!exclusive_storage_requested) { @@ -882,6 +890,7 @@ void usb_release_exclusive_storage(void) #endif return; } +#endif #ifdef USB_ENABLE_HID void usb_set_hid(bool enable) From 33678c6c3caeda406c9272807aeeabf447ebcef6 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 07:10:44 -0500 Subject: [PATCH 50/65] s3c2440: Fix some compilation failures in the mini2440 bootloader This whole platform has bitrotten, may be prudent to just nuke it. Change-Id: Ia12e7ebc160484e57a74dd689d6095d22f3dbfd9 --- firmware/target/arm/s3c2440/dma-s3c2440.c | 44 +++++++++---------- firmware/target/arm/s3c2440/dma-target.h | 4 +- .../s3c2440/mini2440/touchscreen-mini2440.c | 35 +++++++-------- firmware/target/arm/s3c2440/sd-s3c2440.c | 4 +- 4 files changed, 43 insertions(+), 44 deletions(-) diff --git a/firmware/target/arm/s3c2440/dma-s3c2440.c b/firmware/target/arm/s3c2440/dma-s3c2440.c index 32be66d0a8..312a9bff68 100644 --- a/firmware/target/arm/s3c2440/dma-s3c2440.c +++ b/firmware/target/arm/s3c2440/dma-s3c2440.c @@ -34,13 +34,13 @@ static int dma_used = 0; /* Status flags */ #define STATUS_CHANNEL_ACTIVE (1<<0) -static struct dma_channel_state +static struct dma_channel_state { volatile unsigned status; void (*callback)(void); } dma_state [NUM_CHANNELS]; -struct dma_channel_regs +struct dma_channel_regs { volatile unsigned long disrc; volatile unsigned long disrcc; @@ -51,7 +51,7 @@ struct dma_channel_regs volatile unsigned long dcsrc; volatile unsigned long dcdst; volatile unsigned long dmasktrig; - volatile unsigned long reserved [7]; /* pad to 0x40 bytes */ + volatile unsigned long reserved [7]; /* pad to 0x40 bytes */ }; @@ -61,7 +61,7 @@ struct dma_channel_regs *dma_regs [4] = (struct dma_channel_regs *) &DISRC1, (struct dma_channel_regs *) &DISRC2, (struct dma_channel_regs *) &DISRC3 - } + } ; @@ -73,7 +73,7 @@ void dma_init(void) /* Clear pending source */ SRCPND = DMA0_MASK | DMA1_MASK | DMA2_MASK | DMA3_MASK; INTPND = DMA0_MASK | DMA1_MASK | DMA2_MASK | DMA3_MASK; - + /* Enable interrupt in controller */ bitclr32(&INTMOD, DMA0_MASK | DMA1_MASK | DMA2_MASK | DMA3_MASK); bitclr32(&INTMSK, DMA0_MASK | DMA1_MASK | DMA2_MASK | DMA3_MASK); @@ -101,15 +101,15 @@ void dma_release(void) } -inline void dma_disable_channel(int channel) +void dma_disable_channel(int channel) { - struct dma_channel_regs *regs = dma_regs [channel]; - + struct dma_channel_regs *regs = dma_regs [channel]; + /* disable the specified channel */ - + /* Reset the channel */ regs->dmasktrig |= DMASKTRIG_STOP; - + /* Wait for DMA controller to be ready */ while(regs->dmasktrig & DMASKTRIG_ON) ; @@ -120,35 +120,35 @@ inline void dma_disable_channel(int channel) void dma_enable_channel(int channel, struct dma_request *request) { struct dma_channel_regs *regs = dma_regs [channel]; - + /* TODO - transfer sizes (assumes word) */ - + if (DMA_GET_SRC(request->source_map, channel) == DMA_INVALID) panicf ("DMA: invalid channel"); - + /* setup a transfer on specified channel */ - dma_disable_channel (channel); - + dma_disable_channel(channel); + if((unsigned long)request->source_addr < UNCACHED_BASE_ADDR) regs->disrc = (unsigned long)request->source_addr + UNCACHED_BASE_ADDR; else regs->disrc = (unsigned long)request->source_addr; regs->disrcc = request->source_control; - + if((unsigned long)request->dest_addr < UNCACHED_BASE_ADDR) regs->didst = (unsigned long)request->dest_addr + UNCACHED_BASE_ADDR; else regs->didst = (unsigned long)request->dest_addr; regs->didstc = request->dest_control; - - regs->dcon = request->control | request->count | + + regs->dcon = request->control | request->count | DMA_GET_SRC(request->source_map, channel) * DCON_HWSRCSEL; dma_state [channel].callback = request->callback; - + /* Activate the channel */ commit_discard_dcache_range((void *)request->dest_addr, request->count * 4); - + dma_state [channel].status |= STATUS_CHANNEL_ACTIVE; regs->dmasktrig = DMASKTRIG_ON; @@ -157,7 +157,7 @@ void dma_enable_channel(int channel, struct dma_request *request) /* Start DMA */ regs->dmasktrig |= DMASKTRIG_SW_TRIG; } - + } /* ISRs */ @@ -168,7 +168,7 @@ static inline void generic_isr (unsigned channel) if (dma_state [channel].callback) /* call callback for relevant channel */ dma_state [channel].callback(); - + dma_state [channel].status &= ~STATUS_CHANNEL_ACTIVE; } } diff --git a/firmware/target/arm/s3c2440/dma-target.h b/firmware/target/arm/s3c2440/dma-target.h index bec1eadd0b..350a4a2d23 100644 --- a/firmware/target/arm/s3c2440/dma-target.h +++ b/firmware/target/arm/s3c2440/dma-target.h @@ -36,7 +36,7 @@ #error Unsupported target #endif -struct dma_request +struct dma_request { volatile void *source_addr; volatile void *dest_addr; @@ -51,7 +51,7 @@ struct dma_request void dma_init(void); void dma_enable_channel(int channel, struct dma_request *request); -inline void dma_disable_channel(int channel); +void dma_disable_channel(int channel); void dma_retain(void); void dma_release(void); diff --git a/firmware/target/arm/s3c2440/mini2440/touchscreen-mini2440.c b/firmware/target/arm/s3c2440/mini2440/touchscreen-mini2440.c index ce7c8d5dd7..4ab4a46351 100644 --- a/firmware/target/arm/s3c2440/mini2440/touchscreen-mini2440.c +++ b/firmware/target/arm/s3c2440/mini2440/touchscreen-mini2440.c @@ -1,6 +1,6 @@ /*************************************************************************** * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___void + * Open \______ \ ____ ____ | | _\_ |__ _______ ___void * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ @@ -25,6 +25,7 @@ #include "system.h" #include "stdlib.h" #include "button.h" +#include "tick.h" #include "touchscreen.h" #define NO_OF_TOUCH_DATA 5 @@ -99,9 +100,9 @@ void touchscreen_scan_device() static int touch_data_index = 0; int saveADCDLY; - - /* check touch state */ - if(ADCDAT1 & (1<<15)) + + /* check touch state */ + if(ADCDAT1 & (1<<15)) { return; } @@ -111,23 +112,23 @@ void touchscreen_scan_device() /* resets the index if the last touch could not be read 5 times */ touch_data_index = 0; } - - /* read touch data */ + + /* read touch data */ saveADCDLY = ADCDLY; - ADCDLY = 40000; /*delay ~0.8ms (1/50M)*4000 */ + ADCDLY = 40000; /*delay ~0.8ms (1/50M)*4000 */ ADCTSC = (1<<3)|(1<<2); /* pullup disable, seq x,y pos measure */ /* start adc */ - ADCCON|= 0x1; + ADCCON|= 0x1; /* wait for start and end */ while(ADCCON & 0x1); while(!(ADCCON & 0x8000)); x[touch_data_index] = ADCDAT0&0x3ff; y[touch_data_index] = ADCDAT1&0x3ff; - + ADCTSC = 0xd3; /* back to interrupt mode */ ADCDLY = saveADCDLY; - + touch_data_index++; if (touch_data_index > NO_OF_TOUCH_DATA - 1) @@ -161,18 +162,18 @@ int touchscreen_read_device(int *data, int *old_data) /* sort the 5 data taken and use the median value */ qsort(x, NO_OF_TOUCH_DATA, sizeof(short), short_cmp); qsort(y, NO_OF_TOUCH_DATA, sizeof(short), short_cmp); - + x_touch = last_x = x[(NO_OF_TOUCH_DATA - 1)/2]; y_touch = last_y = y[(NO_OF_TOUCH_DATA - 1)/2]; - + last_touch = current_tick; - + touch_hold = true; touch_available = false; } - + *old_data = *data = touch_to_pixels(x_touch, y_touch); - + btn |= touchscreen_to_pixels((*data&0xffff0000) >> 16, (*data&0x0000ffff), data); @@ -183,8 +184,6 @@ int touchscreen_read_device(int *data, int *old_data) /* put the touchscreen back into interrupt mode */ touch_hold = false; } - + return btn; } - - diff --git a/firmware/target/arm/s3c2440/sd-s3c2440.c b/firmware/target/arm/s3c2440/sd-s3c2440.c index 82fd60c711..5737366a88 100644 --- a/firmware/target/arm/s3c2440/sd-s3c2440.c +++ b/firmware/target/arm/s3c2440/sd-s3c2440.c @@ -799,7 +799,7 @@ int sd_read_sectors(IF_MD(int card_no,) sector_t start, int incount, ret = 0; /* assume success */ else #endif - ret = sd_transfer_sectors(card_no, start, incount, inbuf, false); + ret = sd_transfer_sectors(IF_MD_DRV(card_no), start, incount, inbuf, false); dbgprintf ("sd_read, ret=%d\n", ret); return ret; } @@ -827,7 +827,7 @@ int sd_write_sectors(IF_MD(int drive,) sector_t start, int count, return 0; /* assume success */ else #endif - return sd_transfer_sectors(drive, start, count, (void*)outbuf, true); + return sd_transfer_sectors(IF_MD_DRV(drive), start, count, (void*)outbuf, true); #endif } /*****************************************************************************/ From e1d10c938a9b4bbc0480e3967060c6268da83dbb Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 07:21:37 -0500 Subject: [PATCH 51/65] config: Targets must opt-in to bootloader USB storage support ie by only using HAVE_BOOTLOADER_USB_MODE, instead of blanket-enabling USB_ENABLE_STORAGE for numerous SoC families Change-Id: Ief433a1d693876072779e714883438c0012ba2e0 --- bootloader/creativezvm.c | 16 ++++++++-------- firmware/export/config.h | 10 +++------- firmware/export/config/ipodnano2g.h | 1 + firmware/export/config/zenvision.h | 3 ++- firmware/export/config/zenvisionm30gb.h | 3 ++- firmware/export/config/zenvisionm60gb.h | 3 ++- firmware/target/arm/system-arm-classic.h | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bootloader/creativezvm.c b/bootloader/creativezvm.c index 415cab1ea3..db7a8c7459 100644 --- a/bootloader/creativezvm.c +++ b/bootloader/creativezvm.c @@ -47,13 +47,13 @@ void main(void) int buffer_size; int(*kernel_entry)(void); int ret; - + /* Make sure interrupts are disabled */ set_irq_level(IRQ_DISABLED); set_fiq_status(FIQ_DISABLED); system_init(); kernel_init(); - + /* Now enable interrupts */ set_irq_level(IRQ_ENABLED); set_fiq_status(FIQ_ENABLED); @@ -70,18 +70,18 @@ void main(void) reset_screen(); printf("Rockbox boot loader"); printf("Version %s", rbversion); - + ret = storage_init(); if(ret) printf("ATA error: %d", ret); filesystem_init(); - + /* If no button is held, start the OF */ if(button_read_device() == 0) { printf("Loading Creative firmware..."); - + loadbuffer = (unsigned char*)0x00A00000; ret = load_minifs_file("creativeos.jrm", loadbuffer); if(ret != -1) @@ -99,7 +99,7 @@ void main(void) ret = disk_mount_all(); if (ret <= 0) error(EDISK, ret, true); - + printf("Loading Rockbox firmware..."); loadbuffer = (unsigned char*)0x00900000; @@ -113,8 +113,8 @@ void main(void) ret = kernel_entry(); printf("FAILED!"); } - + storage_sleepnow(); - + while(1); } diff --git a/firmware/export/config.h b/firmware/export/config.h index b89cab6bbd..b38732d554 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -1025,12 +1025,12 @@ Lyre prototype 1 */ #define USB_DETECT_BY_REQUEST #endif -#if defined(HAVE_USBSTACK) && CONFIG_USBOTG == USBOTG_ARC +#if CONFIG_USBOTG == USBOTG_ARC #define INCLUDE_TIMEOUT_API #define USB_DRIVER_CLOSE #endif -#if defined(HAVE_USBSTACK) && CONFIG_USBOTG == USBOTG_TNETV105 +#if CONFIG_USBOTG == USBOTG_TNETV105 #define INCLUDE_TIMEOUT_API #define USB_DRIVER_CLOSE #endif @@ -1384,11 +1384,7 @@ Lyre prototype 1 */ #ifdef BOOTLOADER /* enable usb storage for targets that do bootloader usb */ -#if defined(HAVE_BOOTLOADER_USB_MODE) || \ - defined(CREATIVE_ZVx) || defined(CPU_TCC780X) || \ - CONFIG_USBOTG == USBOTG_JZ4740 || CONFIG_USBOTG == USBOTG_AS3525 || \ - CONFIG_USBOTG == USBOTG_S3C6400X || CONFIG_USBOTG == USBOTG_DESIGNWARE || \ - CONFIG_USBOTG == USBOTG_JZ4760 +#if defined(HAVE_BOOTLOADER_USB_MODE) #define USB_ENABLE_STORAGE #endif diff --git a/firmware/export/config/ipodnano2g.h b/firmware/export/config/ipodnano2g.h index 81054e0141..621f4e27a5 100644 --- a/firmware/export/config/ipodnano2g.h +++ b/firmware/export/config/ipodnano2g.h @@ -222,6 +222,7 @@ /* logf() over USB serial (http://www.rockbox.org/wiki/PortalPlayerUsb) */ //#define USB_ENABLE_SERIAL #define HAVE_USBSTACK +#define HAVE_BOOTLOADER_USB_MODE #define HAVE_USB_HID_MOUSE #define USB_VENDOR_ID 0x05AC #define USB_PRODUCT_ID 0x1260 diff --git a/firmware/export/config/zenvision.h b/firmware/export/config/zenvision.h index b832f3b744..d8b75876ae 100644 --- a/firmware/export/config/zenvision.h +++ b/firmware/export/config/zenvision.h @@ -157,7 +157,8 @@ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define USB_VENDOR_ID 0x041e +#define HAVE_BOOTLOADER_USB_MODE +#define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/export/config/zenvisionm30gb.h b/firmware/export/config/zenvisionm30gb.h index bede49d3d0..974783540a 100644 --- a/firmware/export/config/zenvisionm30gb.h +++ b/firmware/export/config/zenvisionm30gb.h @@ -158,7 +158,8 @@ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define USB_VENDOR_ID 0x041e +#define HAVE_BOOTLOADER_USB_MODE +#define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/export/config/zenvisionm60gb.h b/firmware/export/config/zenvisionm60gb.h index dfdc4c7210..0209d8ed6e 100644 --- a/firmware/export/config/zenvisionm60gb.h +++ b/firmware/export/config/zenvisionm60gb.h @@ -158,7 +158,8 @@ /* #define CONFIG_USBOTG USBOTG_ISP1761 */ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define USB_VENDOR_ID 0x041e +#define HAVE_BOOTLOADER_USB_MODE +#define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/target/arm/system-arm-classic.h b/firmware/target/arm/system-arm-classic.h index 66f6467dc1..bc648e43ed 100644 --- a/firmware/target/arm/system-arm-classic.h +++ b/firmware/target/arm/system-arm-classic.h @@ -104,7 +104,7 @@ static inline int set_interrupt_status(int status, int mask) unsigned long cpsr; int oldstatus; /* Read the old levels and set the new ones */ -#if (defined(CREATIVE_ZVM) || defined(CREATIVE_ZV)) && defined(BOOTLOADER) +#if defined(CREATIVE_ZVx) && defined(BOOTLOADER) // FIXME: This workaround is for a problem with inlining; // for some reason 'mask' gets treated as a variable/non-immediate constant // but only on this build. All others (including the nearly-identical mrobe500boot) are fine From 2b09078b928b8f309d3638ca7b3dc47c9718c34a Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 25 Jan 2026 22:26:53 +0000 Subject: [PATCH 52/65] stm32h7: set 8-byte alignment on main and irq stack Explicitly set 8-byte alignment as per the AAPCS, which says the stack should be 8-byte aligned at a public ABI boundary. Change-Id: Ie60b664718119ea576e7c6b5efaac011eb907531 --- firmware/target/arm/stm32/app.lds | 2 +- firmware/target/arm/stm32/boot.lds | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/target/arm/stm32/app.lds b/firmware/target/arm/stm32/app.lds index 1fbcd140cd..fc8bd381f7 100644 --- a/firmware/target/arm/stm32/app.lds +++ b/firmware/target/arm/stm32/app.lds @@ -21,7 +21,7 @@ SECTIONS *(.icode*); } > ITCM - .stack (NOLOAD) : + .stack (NOLOAD) : ALIGN(8) { irqstackbegin = .; . += 0x400; diff --git a/firmware/target/arm/stm32/boot.lds b/firmware/target/arm/stm32/boot.lds index 0994787666..06d3471303 100644 --- a/firmware/target/arm/stm32/boot.lds +++ b/firmware/target/arm/stm32/boot.lds @@ -42,7 +42,7 @@ SECTIONS _bssend = .; } > SRAM_AXI - .stack (NOLOAD) : ALIGN(4) + .stack (NOLOAD) : ALIGN(8) { irqstackbegin = .; . += 0x400; From 77f30202d05bdfd051b53f8f966df1e473a0010e Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 27 Jan 2026 13:23:21 +0000 Subject: [PATCH 53/65] tools: detect Git revision correctly in non-default worktrees In a non-default Git worktree, the .git directory is replaced by a file containing the path to the real .git directory. This breaks the version detection logic because it expects .git to be a directory. Passing the root of the source tree via "git -C", letting Git figure out if we're in a repo or not, solves this problem. Change-Id: I595f1a694258cad490b1a4964f8ae9d51ae76de1 --- tools/version.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tools/version.sh b/tools/version.sh index 1863789917..dce5354387 100755 --- a/tools/version.sh +++ b/tools/version.sh @@ -19,16 +19,14 @@ # This logic is pulled from the Linux's scripts/setlocalversion (also GPL) # and tweaked for rockbox. gitversion() { - export GIT_DIR="$1/.git" - # This verifies we are in a git directory - if head=`git rev-parse --verify --short=10 HEAD 2>/dev/null`; then + if head=`git -C "$1" rev-parse --verify --short=10 HEAD 2>/dev/null`; then # Are there uncommitted changes? export GIT_WORK_TREE="$1" - if git diff --name-only HEAD | read dummy; then + if git -C "$1" diff --name-only HEAD | read dummy; then mod="M" - elif git diff --name-only --cached HEAD | read dummy; then + elif git -C "$1" diff --name-only --cached HEAD | read dummy; then mod="M" fi @@ -51,9 +49,7 @@ if [ -z $VERSION ]; then VER=`cat $TOP/$VERSIONFILE`; else # Ok, we need to derive it from the Version Control system - if [ -d "$TOP/.git" ]; then VER=`gitversion $TOP` - fi fi VERSION=$VER-`date -u +%y%m%d` fi From d15bb5f8488715901809cf8373f9994fcb24a2a8 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Thu, 22 Jan 2026 14:20:36 +0000 Subject: [PATCH 54/65] stm32h7: move LCD framebuffer to AXI SRAM Framebuffer access consumes a lot of SDRAM bandwidth. Moving it to AXI SRAM should be a big improvement as it's around 8x faster (2x clock speed, 4x bus width). Also take advantage of explicitly assigning sections to the special :NONE segment, which means they will not appear in any ELF program header and thus aren't visible to the bootloader. They can then overlap areas used by the bootloader -- by the time they're written, the bootloader will be long gone -- making it easier to make efficient use of SRAM. Change-Id: I2392fd23b17472cc08dc5fe4556f6def3cc186ed --- firmware/export/config/echor1.h | 1 + firmware/target/arm/stm32/app.lds | 30 +++++++++++++++++++++--------- firmware/target/arm/stm32/boot.lds | 5 +++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 3674419c17..8d70ebe081 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -37,6 +37,7 @@ #define HAVE_LCD_BITMAP #define HAVE_LCD_ENABLE #define HAVE_LCD_SHUTDOWN +#define IRAM_LCDFRAMEBUFFER __attribute__((section(".framebuffer"))) /* Backlight defines */ #define HAVE_BACKLIGHT diff --git a/firmware/target/arm/stm32/app.lds b/firmware/target/arm/stm32/app.lds index fc8bd381f7..d51ed57858 100644 --- a/firmware/target/arm/stm32/app.lds +++ b/firmware/target/arm/stm32/app.lds @@ -7,9 +7,16 @@ STARTUP(target/arm/stm32/crt0-stm32h7.o) MEMORY { - DTCM (rw) : ORIGIN = STM32_DTCM_BASE, LENGTH = STM32_DTCM_SIZE - ITCM (rx) : ORIGIN = STM32_ITCM_BASE, LENGTH = STM32_ITCM_SIZE - SDRAM (rwx) : ORIGIN = STM32_SDRAM1_BASE, LENGTH = MEMORYSIZE * 1024 * 1024 + DTCM (rw) : ORIGIN = STM32_DTCM_BASE, LENGTH = STM32_DTCM_SIZE + ITCM (rx) : ORIGIN = STM32_ITCM_BASE, LENGTH = STM32_ITCM_SIZE + SRAM_AXI (rw) : ORIGIN = STM32_SRAM_AXI_BASE, LENGTH = STM32_SRAM_AXI_SIZE + SDRAM (rwx) : ORIGIN = STM32_SDRAM1_BASE, LENGTH = MEMORYSIZE * 1024 * 1024 +} + +PHDRS +{ + itcm PT_LOAD; + sdram PT_LOAD; } SECTIONS @@ -19,7 +26,7 @@ SECTIONS KEEP(*(.vectors.arm)); KEEP(*(.vectors.platform)); *(.icode*); - } > ITCM + } > ITCM :itcm .stack (NOLOAD) : ALIGN(8) { @@ -30,7 +37,12 @@ SECTIONS stackbegin = .; . += 0x2000; stackend = .; - } > DTCM + } > DTCM :NONE + + .framebuffer (NOLOAD) : + { + *(.framebuffer); + } > SRAM_AXI :NONE .text : { @@ -38,17 +50,17 @@ SECTIONS *(.init*); *(.text*); - } > SDRAM + } > SDRAM :sdram .rodata : { *(.rodata*); - } > SDRAM + } > SDRAM :sdram .data : { *(.data*); - } > SDRAM + } > SDRAM :sdram .bss (NOLOAD) : { @@ -56,7 +68,7 @@ SECTIONS *(.bss*); *(COMMON); _bssend = .; - } > SDRAM + } > SDRAM :sdram audiobuffer = ALIGN(32); audiobufend = ORIGIN(SDRAM) + LENGTH(SDRAM) - CODEC_SIZE - PLUGIN_BUFFER_SIZE; diff --git a/firmware/target/arm/stm32/boot.lds b/firmware/target/arm/stm32/boot.lds index 06d3471303..01fc514286 100644 --- a/firmware/target/arm/stm32/boot.lds +++ b/firmware/target/arm/stm32/boot.lds @@ -52,6 +52,11 @@ SECTIONS . += 0x2000; stackend = .; } > SRAM_AXI + + .framebuffer (NOLOAD) : + { + *(.framebuffer); + } > SRAM_AXI } EXTERN(__vectors_arm); From 7b77752aff4849bfcb457f9ba6a9c93ee38394af Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 25 Jan 2026 19:29:51 +0000 Subject: [PATCH 55/65] echoplayer: reduce USB storage buffer size for bootloader This saves 64k of BSS, freeing up space in AXI SRAM. Change-Id: I37079c5fa5ed2b969d70622491dbf0f4dca2c759 --- firmware/export/config/echor1.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index 8d70ebe081..c2027d975d 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -114,6 +114,11 @@ #define HAVE_BOOTLOADER_USB_MODE #endif +#ifdef BOOTLOADER +# define USB_READ_BUFFER_SIZE (32 * 1024) +# define USB_WRITE_BUFFER_SIZE (32 * 1024) +#endif + /* Rockbox capabilities */ #define HAVE_FAT16SUPPORT #define HAVE_ALBUMART From 41f9285def95ebab1ef4953f6110e70f46befcc9 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 07:50:53 -0500 Subject: [PATCH 56/65] usb: Clean up the pile of USB_FULL_INIT exceptions The intent here is that when HAVE_USBSTACK is not defined, or we are in a bootloader wthout HAVE_BOOTLOADER_USB_MODE, a device may still some of USB subsystem initialized. For example, this may be needed to enable USB-based charging functionality. So, get rid of the blanket enables of USB_FULL_INIT based on target SoC, enabling HAVE_BOOTLOADER_USB_MODE on targets that need it, and clean up the initial mess. Most of this mess is because usb_core.c has no sense of USB_FULL_INIT or not, and is always included when HAVE_USBSTACK is set (even in bootloaders without BOOTLOADER_USB_MODE), but dealing with that latter case will come later. Change-Id: I7f805b89dded39aeea2db9038209780069e3b600 --- firmware/SOURCES | 3 +- firmware/export/config/ipodnano2g.h | 2 +- firmware/export/config/zenvision.h | 2 +- firmware/export/config/zenvisionm30gb.h | 2 +- firmware/export/config/zenvisionm60gb.h | 2 +- firmware/export/usb_core.h | 3 +- firmware/usb.c | 63 +++++++++++++------------ 7 files changed, 42 insertions(+), 35 deletions(-) diff --git a/firmware/SOURCES b/firmware/SOURCES index 88927f7364..d62b8d254c 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -216,7 +216,7 @@ target/hosted/xduoo/xduoolinux_codec.c target/hosted/button-devinput.c #endif -#if defined(HIBY_LINUX) && !defined(HIBY_R3PROII) && !defined(HIBY_R1) && !defined(SIMULATOR) +#if defined(HIBY_LINUX) && !defined(HIBY_R3PROII) && !defined(HIBY_R1) && !defined(SIMULATOR) target/hosted/usb-hiby.c #endif @@ -949,6 +949,7 @@ target/arm/s5l8702/lcd-asm-s5l8702.S #endif /* USB Stack */ +// TODO: This needs to be HAVE_USBSTACK && (!BOOTLOADER || HAVE_USB_BOOTLOADER_MODE) #ifdef HAVE_USBSTACK usbstack/usb_core.c #ifdef USB_ENABLE_STORAGE diff --git a/firmware/export/config/ipodnano2g.h b/firmware/export/config/ipodnano2g.h index 621f4e27a5..34e041912b 100644 --- a/firmware/export/config/ipodnano2g.h +++ b/firmware/export/config/ipodnano2g.h @@ -222,7 +222,7 @@ /* logf() over USB serial (http://www.rockbox.org/wiki/PortalPlayerUsb) */ //#define USB_ENABLE_SERIAL #define HAVE_USBSTACK -#define HAVE_BOOTLOADER_USB_MODE +//#define HAVE_BOOTLOADER_USB_MODE #define HAVE_USB_HID_MOUSE #define USB_VENDOR_ID 0x05AC #define USB_PRODUCT_ID 0x1260 diff --git a/firmware/export/config/zenvision.h b/firmware/export/config/zenvision.h index d8b75876ae..eb94c84ca7 100644 --- a/firmware/export/config/zenvision.h +++ b/firmware/export/config/zenvision.h @@ -157,7 +157,7 @@ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define HAVE_BOOTLOADER_USB_MODE +//#define HAVE_BOOTLOADER_USB_MODE #define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/export/config/zenvisionm30gb.h b/firmware/export/config/zenvisionm30gb.h index 974783540a..236a153a3c 100644 --- a/firmware/export/config/zenvisionm30gb.h +++ b/firmware/export/config/zenvisionm30gb.h @@ -158,7 +158,7 @@ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define HAVE_BOOTLOADER_USB_MODE +//#define HAVE_BOOTLOADER_USB_MODE #define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/export/config/zenvisionm60gb.h b/firmware/export/config/zenvisionm60gb.h index 0209d8ed6e..64f6925328 100644 --- a/firmware/export/config/zenvisionm60gb.h +++ b/firmware/export/config/zenvisionm60gb.h @@ -158,7 +158,7 @@ /* #define CONFIG_USBOTG USBOTG_ISP1761 */ #define CONFIG_USBOTG USBOTG_ISP1583 #define HAVE_USBSTACK -#define HAVE_BOOTLOADER_USB_MODE +//#define HAVE_BOOTLOADER_USB_MODE #define USB_VENDOR_ID 0x041e #define USB_PRODUCT_ID 0x4133 #define USB_NUM_ENDPOINTS 7 diff --git a/firmware/export/usb_core.h b/firmware/export/usb_core.h index 1e940dc8e3..5aa6e4f33a 100644 --- a/firmware/export/usb_core.h +++ b/firmware/export/usb_core.h @@ -58,8 +58,10 @@ void usb_core_transfer_complete(int endpoint,int dir,int status,int length); void usb_core_bus_reset(void); void usb_core_enable_driver(int driver,bool enabled); bool usb_core_driver_enabled(int driver); +#ifdef HAVE_USBSTACK void usb_core_handle_transfer_completion( struct usb_transfer_completion_event_data* event); +#endif void usb_core_handle_notify(long id, intptr_t data); /* For controllers which handle SET ADDR and/or SET CONFIG in hardware */ void usb_core_notify_set_address(uint8_t addr); @@ -70,4 +72,3 @@ void usb_core_hotswap_event(int volume,bool inserted); #endif #endif - diff --git a/firmware/usb.c b/firmware/usb.c index 9d9b221e0a..318c3f07ac 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -59,17 +59,7 @@ #include "iap.h" #endif -/* Conditions under which we want the entire driver */ -#if !defined(BOOTLOADER) || \ - (defined(HAVE_USBSTACK) && defined(HAVE_BOOTLOADER_USB_MODE)) || \ - (defined(HAVE_USBSTACK) && defined(IPOD_NANO2G)) || \ - (defined(HAVE_USBSTACK) && (defined(CREATIVE_ZVx))) || \ - (defined(HAVE_USBSTACK) && (defined(OLYMPUS_MROBE_500))) || \ - defined(CPU_TCC780X) || \ - (CONFIG_USBOTG == USBOTG_JZ4740) || \ - (CONFIG_USBOTG == USBOTG_JZ4760) -/* TODO: condition should be reset to be only the original - (defined(HAVE_USBSTACK) && defined(HAVE_BOOTLOADER_USB_MODE)) */ +#if defined(HAVE_USBSTACK) && (!defined(BOOTLOADER) || defined(HAVE_BOOTLOADER_USB_MODE)) #define USB_FULL_INIT #endif @@ -91,8 +81,8 @@ static int usb_state = USB_EXTRACTED; static int usb_mmc_countdown = 0; #endif -/* Make sure there's enough stack space for screendump */ #ifdef USB_FULL_INIT +/* Make sure there's enough stack space for screendump */ #ifndef USB_EXTRA_STACK # define USB_EXTRA_STACK 0x0 /*Define in firmware/export/config/[target].h*/ #endif @@ -101,26 +91,20 @@ static const char usb_thread_name[] = "usb"; static unsigned int usb_thread_entry = 0; static bool usb_monitor_enabled = false; static bool exclusive_storage_enabled = false; -#endif /* USB_FULL_INIT */ -static struct event_queue usb_queue SHAREDBSS_ATTR; static bool exclusive_storage_requested = false; +static struct event_queue usb_queue SHAREDBSS_ATTR; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif #ifdef USB_ENABLE_AUDIO static int usb_audio = 0; #endif - -#ifdef HAVE_USB_POWER -static bool usb_power_only = false; -#endif - -#ifdef USB_FULL_INIT static bool usb_host_present = false; static int usb_num_acks_to_expect = 0; static uint32_t usb_broadcast_seqnum = 0x80000000; #ifdef HAVE_USB_POWER static int usb_mode = USBMODE_DEFAULT; +static bool usb_power_only = false; #endif #if defined(USB_FIREWIRE_HANDLING) @@ -492,7 +476,6 @@ static void NORETURN_ATTR usb_thread(void) /* USB_INSERTED */ case SYS_USB_CONNECTED_ACK: -#ifdef USB_FULL_INIT if((uint32_t)ev.data != usb_broadcast_seqnum) { DEBUGF("usb: late ack %lX < %lX", ev.data, usb_broadcast_seqnum); break; @@ -507,7 +490,6 @@ static void NORETURN_ATTR usb_thread(void) } DEBUGF("usb: all threads have acknowledged the connect.\n"); -#endif if(usb_host_present && exclusive_storage_requested) { usb_slave_mode(true); exclusive_storage_enabled = true; @@ -712,19 +694,45 @@ static void usb_tick(void) } #endif } - void usb_start_monitoring(void) { usb_monitor_enabled = true; } #endif /* USB_STATUS_BY_EVENT */ -#endif /* USB_FULL_INIT */ void usb_acknowledge(long id, intptr_t seqnum) { queue_post(&usb_queue, id, seqnum); } +#else /* !USB_FULL_INIT */ +/* TODO: All of this can go away once usb_core.c is no longer built + with BOOTLOADER && !HAVE_USB_BOOTLOADER_MODE */ +#ifdef HAVE_USBSTACK +void usb_signal_transfer_completion( + struct usb_transfer_completion_event_data* event_data) +{ + (void)event_data; +} +#endif +void usb_clear_pending_transfer_completion_events(void) +{ +} +void usb_release_exclusive_storage(void) +{ +} +void usb_signal_notify(long id, intptr_t data) +{ + (void)id; + (void)data; +} +void usb_acknowledge(long id, intptr_t seqnum) +{ + (void)id; + (void)seqnum; +} +#endif /* !USB_FULL_INIT */ + void usb_init(void) { /* Do required hardware inits first. For software USB the driver has @@ -814,7 +822,6 @@ bool usb_exclusive_storage(void) /* Storage isn't actually exclusive until slave mode has been entered */ return exclusive_storage_enabled; } -#endif /* HAVE_USBSTACK */ /* exclusive storage mode transision * HAVE_USBSTACK: @@ -863,14 +870,11 @@ bool usb_exclusive_storage(void) void usb_request_exclusive_storage(void) { exclusive_storage_requested = true; -#ifdef USB_FULL_INIT usb_broadcast_seqnum += 1; usb_num_acks_to_expect = queue_broadcast(SYS_USB_CONNECTED, usb_broadcast_seqnum) - 1; DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); -#endif } -#ifdef USB_FULL_INIT void usb_release_exclusive_storage(void) { if(!exclusive_storage_requested) { @@ -890,7 +894,6 @@ void usb_release_exclusive_storage(void) #endif return; } -#endif #ifdef USB_ENABLE_HID void usb_set_hid(bool enable) @@ -914,6 +917,8 @@ bool usb_powered_only(void) } #endif /* HAVE_USB_POWER */ +#endif /* HAVE_USBSTACK && defined(USB_FULL_INIT) */ + #elif defined(USB_NONE) /* Dummy functions for USB_NONE */ From a5b589cf5bbd9fcbf30579e26134c43ecb07ce14 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 12:21:06 -0500 Subject: [PATCH 57/65] vx747: Target needs HAVE_BOOTLOADER_USB_MODE Change-Id: I735cbf5575b0368741f2b8147eb80a71e0a67c98 --- firmware/export/config/ondavx747.h | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/export/config/ondavx747.h b/firmware/export/config/ondavx747.h index e91dcb84fa..8966b0dc09 100644 --- a/firmware/export/config/ondavx747.h +++ b/firmware/export/config/ondavx747.h @@ -201,6 +201,7 @@ No access to the NAND yet.. #define CONFIG_USBOTG USBOTG_JZ4740 #define HAVE_USBSTACK +#define HAVE_BOOTLOADER_USB_MODE /* Connect by events, not by tick polling */ #define USB_STATUS_BY_EVENT From fce8248267751f5d99ac066bf8e206394e52a4c2 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 13:05:33 -0500 Subject: [PATCH 58/65] usb: Hopefully resolve the red in 41f9285def First, leave USB_FULL_INIT on for all non-bootloader builds; this is needed for devices that don't have a USBSTACK (such as most hosted targets and ones that provide USB<>ATA in hardware) Then unwind another hack that is no longer needed now that USB_FULL_INIT is not set in most bootloaders. Change-Id: I00881ac76b2469e5cd7700bad2203c58ef1e09e7 --- firmware/usb.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/firmware/usb.c b/firmware/usb.c index 318c3f07ac..49b4e70b3e 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -59,7 +59,7 @@ #include "iap.h" #endif -#if defined(HAVE_USBSTACK) && (!defined(BOOTLOADER) || defined(HAVE_BOOTLOADER_USB_MODE)) +#if (!defined(BOOTLOADER) || defined(HAVE_BOOTLOADER_USB_MODE)) #define USB_FULL_INIT #endif @@ -704,7 +704,6 @@ void usb_acknowledge(long id, intptr_t seqnum) { queue_post(&usb_queue, id, seqnum); } - #else /* !USB_FULL_INIT */ /* TODO: All of this can go away once usb_core.c is no longer built with BOOTLOADER && !HAVE_USB_BOOTLOADER_MODE */ @@ -816,7 +815,7 @@ bool usb_inserted(void) return usb_state == USB_INSERTED || usb_state == USB_POWERED; } -#if defined(HAVE_USBSTACK) && defined(USB_FULL_INIT) +#if defined(USB_FULL_INIT) bool usb_exclusive_storage(void) { /* Storage isn't actually exclusive until slave mode has been entered */ From eb692117912524d556fe23f85d09e0e037d21585 Mon Sep 17 00:00:00 2001 From: mojyack Date: Fri, 19 Dec 2025 15:22:25 +0900 Subject: [PATCH 59/65] usb: introduce USB_NOTIFY_CLASS_DRIVER this invokes specified class driver's notify_event method. initial purpose is to trigger a callback from isr, like a timer event. Change-Id: Id600e9f0d8840a12da779d5a15783edf14bd76b5 --- firmware/export/usb.h | 14 +++++++++++++- firmware/usb.c | 1 + firmware/usbstack/usb_class_driver.h | 4 ++++ firmware/usbstack/usb_core.c | 10 ++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/firmware/export/usb.h b/firmware/export/usb.h index c16fecc69f..6d9784f93e 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -119,6 +119,7 @@ enum USB_NOTIFY_SET_ADDR, /* Event */ USB_NOTIFY_SET_CONFIG, /* Event */ USB_NOTIFY_BUS_RESET, /* Event */ + USB_NOTIFY_CLASS_DRIVER, /* Event - notify_event() of specified class driver */ #endif #ifdef USB_FIREWIRE_HANDLING USB_REQUEST_REBOOT, /* Event */ @@ -238,12 +239,23 @@ void usb_set_mode(int mode); /* USB driver call this function to notify that a transfer has completed */ void usb_signal_transfer_completion( struct usb_transfer_completion_event_data *event_data); + /* Clear all signaled transfer completion events from event queue */ void usb_clear_pending_transfer_completion_events(void); + /* notify the USB code that some important event has occurred which influences the * USB state (like USB_NOTIFY_SET_ADDR). USB drivers should call usb_core_notify_* - * functions and not this function. */ + * functions and not this function. + * for USB_NOTIFY_CLASS_DRIVER, use usb_signal_class_notify() instead */ void usb_signal_notify(long id, intptr_t data); + +/* wrapper for usb_signal_notify(USB_NOTIFY_CLASS_DRIVER) + * class_num: target driver. USB_DRIVER_* + * data: optional data. note that its upper 8 bits will be masked */ +static inline void usb_signal_class_notify(int8_t class_num, uint32_t data) { + usb_signal_notify(USB_NOTIFY_CLASS_DRIVER, class_num << 24 | (data & 0x00ffffff)); +} + /* returns whether a USB_DRIVER_* is enabled (like HID, mass storage, ...) */ bool usb_driver_enabled(int driver); /* returns whether exclusive storage is available for USB */ diff --git a/firmware/usb.c b/firmware/usb.c index 49b4e70b3e..c088db4ef1 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -423,6 +423,7 @@ static void NORETURN_ATTR usb_thread(void) case USB_NOTIFY_SET_ADDR: case USB_NOTIFY_SET_CONFIG: case USB_NOTIFY_BUS_RESET: + case USB_NOTIFY_CLASS_DRIVER: if(usb_state <= USB_EXTRACTED) break; usb_core_handle_notify(ev.id, ev.data); diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index 0dab7ea0b0..f26324fe20 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -118,6 +118,10 @@ struct usb_class_driver { * Returns value on success and -1 on error. * Mandatory function if alternate interface support is needed */ int (*get_interface)(int interface); + + /* Invoked by USB_NOTIFY_CLASS_DRIVER + Optional function */ + void (*notify_event)(intptr_t data); }; #define PACK_DATA(dest, data) pack_data(dest, &(data), sizeof(data)) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index da02d256a3..60d07879ef 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -1181,6 +1181,16 @@ void usb_core_handle_notify(long id, intptr_t data) usb_charging_maxcurrent_change(usb_charging_maxcurrent()); #endif break; + case USB_NOTIFY_CLASS_DRIVER: { + uint8_t index = data >> 24; + if(index >= USB_NUM_DRIVERS) { + 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); + } + } break; default: break; } From b349eea2c9d710a00e4f609be692e75542d84d51 Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 15:19:55 -0500 Subject: [PATCH 60/65] usb: fix yellow in eb69211791 Bootloaders with HAVE_USBSTACK but without HAVE_BOOTLOADER_USB_MODE end up with USB_NUM_DRIVERS of 0 which leads to a warning due to a signed number being checked to see if it's >= 0. Work around this temporarily; the proper fix is to not build usb_core and its class drivers when BOOTLOADER & !HAVE_BOOTLOADER_USB_MODE Change-Id: I1b41140d31ba9df6b4c760478c4265d4e5584963 --- firmware/usbstack/usb_core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 60d07879ef..9d5fa04d13 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -1182,8 +1182,12 @@ void usb_core_handle_notify(long id, intptr_t data) #endif break; case USB_NOTIFY_CLASS_DRIVER: { - uint8_t index = data >> 24; - if(index >= USB_NUM_DRIVERS) { + /* HACK: index is uint8 but promoted to int to avoid a compiler + warning when USB_NUM_DRIVERS is 0, mainly in bootloaders. + This hack can be removed once usb_core is no longer built + for BOOTLOADER && !HAVE_BOOTLOADER_USB_MODE */ + int index = data >> 24; + if(index < 0 || index >= USB_NUM_DRIVERS) { logf("usb_core: invalid notification destination index=%u", index); return; } From 30ff611529db89fa3e3b7261bc01d8619c822bfc Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 17:15:03 -0500 Subject: [PATCH 61/65] voice: Switch default en_GB TTS voice to Piper's 'alba'. Does better for short phrases/words than 'semaine'. "No/Yes" and numbers in were pretty bad in particular. Change-Id: I795ad57b7ca8c5b8a3fe0c2b721ef167d0dd9f6d --- tools/builds.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/builds.pm b/tools/builds.pm index ce8df15db4..e2af574df4 100644 --- a/tools/builds.pm +++ b/tools/builds.pm @@ -613,7 +613,7 @@ sub simbuilds { 'festival' => '--language english', 'espeak' => '-ven-gb -k 5', 'gtts' => '-l en -t co.uk', - 'piper' => 'en_GB-semaine-medium.onnx', + 'piper' => 'en_GB-alba-medium.onnx', }, 'enabled' => 1, }, From 83420e0a95fcf0d78ea3c1b9b168005ecc267a3b Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 17:36:14 -0500 Subject: [PATCH 62/65] voice: Switch default en_GB TTS voice to Piper's 'alba' #2 The previous commit just switched the voice used for the nightlies; this changes the default used when using the cmdline voice.pl tool without the user overriding it with something else. Change-Id: I5144fe66e355f3c41677ca37226a743667d291bf --- tools/voice.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/voice.pl b/tools/voice.pl index 4d947fccbc..c9450a41c0 100755 --- a/tools/voice.pl +++ b/tools/voice.pl @@ -158,7 +158,7 @@ my %espeak_lang_map = ( ); my %piper_lang_map = ( - 'english' => 'en_GB-semaine-medium.onnx', # Always first, it's the golden master + 'english' => 'en_GB-alba-medium.onnx', # Always first, it's the golden master 'bulgarian' => 'bg_BG-dimitar-medium.onnx', 'chinese-simp' => 'zh_CN-huayan-medium.onnx', 'czech' => 'cs_CZ-jirka-medium.onnx', From 62200ff4d79dadf750ba1e0f78e8c7f784d5c1ad Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Tue, 27 Jan 2026 19:48:37 -0500 Subject: [PATCH 63/65] FS#13768: Updated Serbian Translation (Ivan Pesic) Change-Id: Iae502c6757823ae00ec22a65d83c3e151d0c4c55 --- apps/lang/srpski.lang | 87 +++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/apps/lang/srpski.lang b/apps/lang/srpski.lang index 2e8d5794aa..e043cf8fb0 100644 --- a/apps/lang/srpski.lang +++ b/apps/lang/srpski.lang @@ -12615,20 +12615,6 @@ *: "Просечни битски проток" - - id: LANG_PLAYTIME_ERROR - desc: playing time screen - user: core - - *: "Error while gathering info" - - - *: "Грешка током прикупљања инфо" - - - *: "Грешка током прикупљања инфо" - - id: LANG_PLAYING_TIME desc: onplay menu @@ -15940,20 +15926,6 @@ *: "Подраз. прегледач" - - id: LANG_AMAZE_MENU - desc: Amaze game - user: core - - *: "Amaze Main Menu" - - - *: "Amaze главни мени" - - - *: "Амејз главни мени" - - id: LANG_SET_MAZE_SIZE desc: Maze size in Amaze game @@ -16206,34 +16178,6 @@ *: "Мик мод подешавања" - - id: LANG_MIKMOD_MENU - desc: mikmod plugin - user: core - - *: "Mikmod Menu" - - - *: "Mikmod мени" - - - *: "Мик мод мени" - - - - id: LANG_CHESSBOX_MENU - desc: chessbox plugin - user: core - - *: "Chessbox Menu" - - - *: "Chessbox мени" - - - *: "Чес бокс мени" - - id: VOICE_INVALID_VOICE_FILE desc: played if the voice file fails to load @@ -16983,3 +16927,34 @@ usbdac: "У Ес Бе - ДАК активан" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Објави статус" + + + *: "Објави статус" + + + + id: LANG_USE_LED_INDICATORS + desc: LED indicators setting + user: core + + *: none + general_purpose_led: "Use LED indicators" + + + *: none + general_purpose_led: "Користи LED индикаторе" + + + *: none + general_purpose_led: "Користи LED индикаторе" + + From 2a4cb29b27a8a95caa2a2a161555531c0305bc3f Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Wed, 28 Jan 2026 15:54:28 -0500 Subject: [PATCH 64/65] synopsis-dwmac: Make ep0_buffer (and friends) static. Looks like an oversight Change-Id: Ia730fd1f9e19ca31add581400372dfacead7b452 --- firmware/drivers/usb-designware.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index 7233f873ed..d1274f5e41 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -186,8 +186,8 @@ static const char* const dw_resp_str[3] = static struct usb_dw_ep usb_dw_ep_list[USB_NUM_ENDPOINTS][USB_DW_NUM_DIRS]; static struct usb_dw_ep0 ep0; -uint8_t _ep0_buffer[64] USB_DEVBSS_ATTR __attribute__((aligned(32))); -uint8_t* ep0_buffer; /* Uncached, unless NO_UNCACHED_ADDR is defined */ +static uint8_t _ep0_buffer[64] USB_DEVBSS_ATTR __attribute__((aligned(32))); +static uint8_t* ep0_buffer; /* Uncached, unless NO_UNCACHED_ADDR is defined */ static uint32_t usb_endpoints; /* available EPs mask */ From 1f30d9efe3d3d915d6c63ac95ac64e30f76373ad Mon Sep 17 00:00:00 2001 From: Solomon Peachy Date: Thu, 29 Jan 2026 17:16:20 -0500 Subject: [PATCH 65/65] FS#13769 - Updated Italian translation (Alessio Lenzi) Change-Id: Ieeefb31f70f1924c8de25ea640ad75e17cac40f5 --- apps/lang/italiano.lang | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/lang/italiano.lang b/apps/lang/italiano.lang index 66dc981301..22f37d489e 100644 --- a/apps/lang/italiano.lang +++ b/apps/lang/italiano.lang @@ -16946,3 +16946,17 @@ general_purpose_led: "Usa indicatori led" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Annuncia Lo Stato" + + + *: "Annuncia Lo Stato" + +