/* * Copyright (c) 2018 naehrwert * * Copyright (c) 2018-2023 CTCaer * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include "config.h" #include "gfx/logos.h" #include "gfx/tui.h" #include "hos/hos.h" #include "hos/secmon_exo.h" #include "l4t/l4t.h" #include #include #include #include "storage/emummc.h" #include "frontend/fe_tools.h" #include "frontend/fe_info.h" hekate_config h_cfg; boot_cfg_t __attribute__((section ("._boot_cfg"))) b_cfg; const volatile ipl_ver_meta_t __attribute__((section ("._ipl_version"))) ipl_ver = { .magic = BL_MAGIC, .version = (BL_VER_MJ + '0') | ((BL_VER_MN + '0') << 8) | ((BL_VER_HF + '0') << 16), .rsvd0 = 0, .rsvd1 = 0 }; volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR; void emmcsn_path_impl(char *path, char *sub_dir, char *filename, sdmmc_storage_t *storage) { static char emmc_sn[9] = {0}; // Check if not valid S/N and get actual eMMC S/N. if (!storage && !emmc_sn[0]) { if (!emmc_initialize(false)) strcpy(emmc_sn, "00000000"); else { itoa(emmc_storage.cid.serial, emmc_sn, 16); emmc_end(); } } else itoa(storage->cid.serial, emmc_sn, 16); // Create main folder. strcpy(path, "backup"); f_mkdir(path); // Create eMMC S/N folder. strcat(path, "/"); strcat(path, emmc_sn); f_mkdir(path); // Create sub folder if defined. Dir slash must be included. strcat(path, sub_dir); // Can be a null-terminator. if (strlen(sub_dir)) f_mkdir(path); // Add filename. strcat(path, "/"); strcat(path, filename); // Can be a null-terminator. } void check_power_off_from_hos() { // Power off on alarm wakeup from HOS shutdown. For modchips/dongles. u8 hos_wakeup = i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_IRQTOP); // Clear RTC interrupts. (void)i2c_recv_byte(I2C_5, MAX77620_RTC_I2C_ADDR, MAX77620_RTC_RTCINT_REG); // Stop the alarm, in case we injected and powered off too fast. max77620_rtc_stop_alarm(); // Handle RTC wake up. if (hos_wakeup & MAX77620_IRQ_TOP_RTC_MASK) { if (h_cfg.autohosoff == 1) { render_static_bootlogo(); if (display_get_decoded_panel_id() != PANEL_SAM_AMS699VC01) { // Slow fading for LCD panels. display_backlight_brightness(10, 5000); display_backlight_brightness(100, 25000); msleep(600); display_backlight_brightness(0, 20000); } else { // Blink 3 times for OLED panel. for (u32 i = 0; i < 3; i++) { msleep(150); display_backlight_brightness(100, 0); msleep(150); display_backlight_brightness(0, 0); } } } power_set_state(POWER_OFF_RESET); } } // This is a safe and unused DRAM region for our payloads. #define RELOC_META_OFF 0x7C #define PATCHED_RELOC_SZ 0x94 #define PATCHED_RELOC_STACK 0x40007000 #define PATCHED_RELOC_ENTRY 0x40010000 #define EXT_PAYLOAD_ADDR 0xC0000000 #define RCM_PAYLOAD_ADDR (EXT_PAYLOAD_ADDR + ALIGN(PATCHED_RELOC_SZ, 0x10)) #define COREBOOT_END_ADDR 0xD0000000 #define COREBOOT_VER_OFF 0x41 #define CBFS_DRAM_EN_ADDR 0x4003E000 #define CBFS_DRAM_MAGIC 0x4452414D // "DRAM" static void *coreboot_addr; static void _reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) { memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ); reloc_meta_t *relocator = (reloc_meta_t *)(payload_src + RELOC_META_OFF); relocator->start = payload_dst - ALIGN(PATCHED_RELOC_SZ, 0x10); relocator->stack = PATCHED_RELOC_STACK; relocator->end = payload_dst + payload_size; relocator->ep = payload_dst; if (payload_size == 0x7000) { memcpy((u8 *)(payload_src + ALIGN(PATCHED_RELOC_SZ, 0x10)), coreboot_addr, 0x7000); // Bootblock. *(vu32 *)CBFS_DRAM_EN_ADDR = CBFS_DRAM_MAGIC; } } bool is_ipl_updated(void *buf, char *path, bool force) { ipl_ver_meta_t *update_ft = (ipl_ver_meta_t *)(buf + PATCHED_RELOC_SZ + sizeof(boot_cfg_t)); bool magic_valid = update_ft->magic == ipl_ver.magic; bool force_update = force && !magic_valid; bool is_valid_old = magic_valid && (byte_swap_32(update_ft->version) < byte_swap_32(ipl_ver.version)); // Check if newer version. if (!force && magic_valid) if (byte_swap_32(update_ft->version) > byte_swap_32(ipl_ver.version)) return false; // Update if old or broken. if (force_update || is_valid_old) { FIL fp; reloc_meta_t *reloc = (reloc_meta_t *)(IPL_LOAD_ADDR + RELOC_META_OFF); boot_cfg_t *tmp_cfg = malloc(sizeof(boot_cfg_t)); memset(tmp_cfg, 0, sizeof(boot_cfg_t)); f_open(&fp, path, FA_WRITE | FA_CREATE_ALWAYS); f_write(&fp, (u8 *)reloc->start, reloc->end - reloc->start, NULL); // Write needed tag in case injected ipl uses old versioning. f_write(&fp, "ICTC49", 6, NULL); // Reset boot storage configuration. f_lseek(&fp, PATCHED_RELOC_SZ); f_write(&fp, tmp_cfg, sizeof(boot_cfg_t), NULL); f_close(&fp); free(tmp_cfg); } return true; } static void _launch_payload(char *path, bool update, bool clear_screen) { if (clear_screen) gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); FIL fp; if (f_open(&fp, path, FA_READ)) { gfx_con.mute = false; EPRINTFARGS("Payload file is missing!\n(%s)", path); goto out; } // Read and copy the payload to our chosen address void *buf; u32 size = f_size(&fp); if (size < 0x30000) buf = (void *)RCM_PAYLOAD_ADDR; else { coreboot_addr = (void *)(COREBOOT_END_ADDR - size); buf = coreboot_addr; if (h_cfg.t210b01) { f_close(&fp); gfx_con.mute = false; EPRINTF("Coreboot not allowed on Mariko!"); goto out; } } if (f_read(&fp, buf, size, NULL)) { f_close(&fp); goto out; } f_close(&fp); if (update && is_ipl_updated(buf, path, false)) goto out; sd_end(); if (size < 0x30000) { if (update) memcpy((u8 *)(RCM_PAYLOAD_ADDR + PATCHED_RELOC_SZ), &b_cfg, sizeof(boot_cfg_t)); // Transfer boot cfg. else _reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, ALIGN(size, 0x10)); hw_reinit_workaround(false, byte_swap_32(*(u32 *)(buf + size - sizeof(u32)))); } else { _reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, 0x7000); // Get coreboot seamless display magic. u32 magic = 0; char *magic_ptr = buf + COREBOOT_VER_OFF; memcpy(&magic, magic_ptr + strlen(magic_ptr) - 4, 4); hw_reinit_workaround(true, magic); } // Some cards (Sandisk U1), do not like a fast power cycle. Wait min 100ms. sdmmc_storage_init_wait_sd(); void (*update_ptr)() = (void *)RCM_PAYLOAD_ADDR; void (*ext_payload_ptr)() = (void *)EXT_PAYLOAD_ADDR; // Launch our payload. if (!update) (*ext_payload_ptr)(); else { // Set updated flag to skip check on launch. EMC(EMC_SCRATCH0) |= EMC_HEKA_UPD; (*update_ptr)(); } out: if (!update) { gfx_con.mute = false; EPRINTF("Failed to launch payload!"); } } static void _launch_payloads() { u8 max_entries = 61; char *filelist = NULL; char *file_sec = NULL; char *dir = NULL; ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 3)); gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); if (!sd_mount()) { free(ments); goto failed_sd_mount; } dir = (char *)malloc(256); memcpy(dir, "bootloader/payloads", 20); filelist = dirlist(dir, NULL, false, false); u32 i = 0; if (filelist) { // Build configuration menu. ments[0].type = MENT_BACK; ments[0].caption = "Back"; ments[1].type = MENT_CHGLINE; while (true) { if (i > max_entries || !filelist[i * 256]) break; ments[i + 2].type = INI_CHOICE; ments[i + 2].caption = &filelist[i * 256]; ments[i + 2].data = &filelist[i * 256]; i++; } } if (i > 0) { memset(&ments[i + 2], 0, sizeof(ment_t)); menu_t menu = { ments, "Choose a payload", 0, 0 }; file_sec = (char *)tui_do_menu(&menu); if (!file_sec) { free(ments); free(dir); free(filelist); sd_end(); return; } } else EPRINTF("No payloads found."); free(ments); free(filelist); if (file_sec) { memcpy(dir + strlen(dir), "/", 2); memcpy(dir + strlen(dir), file_sec, strlen(file_sec) + 1); _launch_payload(dir, false, true); } failed_sd_mount: sd_end(); free(dir); btn_wait(); } static void _launch_ini_list() { u8 max_entries = 61; char *special_path = NULL; char *emummc_path = NULL; ini_sec_t *cfg_sec = NULL; LIST_INIT(ini_list_sections); gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); if (!sd_mount()) goto parse_failed; // Check that ini files exist and parse them. if (!ini_parse(&ini_list_sections, "bootloader/ini", true)) { EPRINTF("No .ini files in bootloader/ini!"); goto parse_failed; } // Build configuration menu. ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 3)); ments[0].type = MENT_BACK; ments[0].caption = "Back"; ments[1].type = MENT_CHGLINE; u32 sec_idx = 2; LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_list_sections, link) { if (ini_sec->type == INI_COMMENT || ini_sec->type == INI_NEWLINE || !strcmp(ini_sec->name, "config")) continue; ments[sec_idx].type = ini_sec->type; ments[sec_idx].caption = ini_sec->name; ments[sec_idx].data = ini_sec; if (ini_sec->type == MENT_CAPTION) ments[sec_idx].color = ini_sec->color; sec_idx++; if ((sec_idx - 1) > max_entries) break; } if (sec_idx > 2) { memset(&ments[sec_idx], 0, sizeof(ment_t)); menu_t menu = { ments, "Launch ini entries", 0, 0 }; cfg_sec = (ini_sec_t *)tui_do_menu(&menu); special_path = ini_check_special_section(cfg_sec); if (cfg_sec && !special_path) { LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg_sec->kvs, link) { if (!strcmp("emummc_force_disable", kv->key)) h_cfg.emummc_force_disable = atoi(kv->val); else if (!strcmp("emupath", kv->key)) emummc_path = kv->val; } if (emummc_path && !emummc_set_path(emummc_path)) { EPRINTF("emupath is wrong!"); goto wrong_emupath; } } if (!cfg_sec) { free(ments); return; } } else EPRINTF("No extra configs found."); free(ments); parse_failed: if (!cfg_sec) goto out; if (special_path) { // Try to launch Payload or L4T. if (special_path != (char *)-1) _launch_payload(special_path, false, true); else { u32 entry_idx = 0; for (u32 i = 0; i < sec_idx; i++) { if (ments[i].data == cfg_sec) { entry_idx = i; break; } } launch_l4t(cfg_sec, entry_idx, 1, h_cfg.t210b01); } } else if (!hos_launch(cfg_sec)) { wrong_emupath: if (emummc_path) { sd_mount(); emummc_load_cfg(); // Reload emuMMC config in case of emupath. } } out: btn_wait(); } static void _launch_config() { u8 max_entries = 61; char *special_path = NULL; char *emummc_path = NULL; ini_sec_t *cfg_sec = NULL; LIST_INIT(ini_sections); gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); if (!sd_mount()) goto parse_failed; // Load emuMMC configuration. emummc_load_cfg(); // Parse main configuration. ini_parse(&ini_sections, "bootloader/hekate_ipl.ini", false); // Build configuration menu. ment_t *ments = (ment_t *)malloc(sizeof(ment_t) * (max_entries + 6)); ments[0].type = MENT_BACK; ments[0].caption = "Back"; ments[1].type = MENT_CHGLINE; ments[2].type = MENT_HANDLER; ments[2].caption = "Payloads..."; ments[2].handler = _launch_payloads; ments[3].type = MENT_HANDLER; ments[3].caption = "More configs..."; ments[3].handler = _launch_ini_list; ments[4].type = MENT_CHGLINE; u32 sec_idx = 5; LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_sections, link) { if (ini_sec->type == INI_COMMENT || ini_sec->type == INI_NEWLINE || !strcmp(ini_sec->name, "config")) continue; ments[sec_idx].type = ini_sec->type; ments[sec_idx].caption = ini_sec->name; ments[sec_idx].data = ini_sec; if (ini_sec->type == MENT_CAPTION) ments[sec_idx].color = ini_sec->color; sec_idx++; if ((sec_idx - 4) > max_entries) break; } if (sec_idx < 6) { ments[sec_idx].type = MENT_CAPTION; ments[sec_idx].caption = "No main configs found..."; ments[sec_idx].color = TXT_CLR_WARNING; sec_idx++; } memset(&ments[sec_idx], 0, sizeof(ment_t)); menu_t menu = { ments, "Launch configurations", 0, 0 }; cfg_sec = (ini_sec_t *)tui_do_menu(&menu); special_path = ini_check_special_section(cfg_sec); if (cfg_sec && !special_path) { LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg_sec->kvs, link) { if (!strcmp("emummc_force_disable", kv->key)) h_cfg.emummc_force_disable = atoi(kv->val); if (!strcmp("emupath", kv->key)) emummc_path = kv->val; } if (emummc_path && !emummc_set_path(emummc_path)) { EPRINTF("emupath is wrong!"); goto wrong_emupath; } } if (!cfg_sec) { free(ments); sd_end(); return; } free(ments); parse_failed: if (!cfg_sec) { gfx_printf("\nPress any key...\n"); goto out; } if (special_path) { // Try to launch Payload or L4T. if (special_path != (char *)-1) _launch_payload(special_path, false, true); else { u32 entry_idx = 0; for (u32 i = 0; i < sec_idx; i++) { if (ments[i].data == cfg_sec) { entry_idx = i; break; } } launch_l4t(cfg_sec, entry_idx, 0, h_cfg.t210b01); } } else if (!hos_launch(cfg_sec)) { wrong_emupath: if (emummc_path) { sd_mount(); emummc_load_cfg(); // Reload emuMMC config in case of emupath. } } out: sd_end(); h_cfg.emummc_force_disable = false; btn_wait(); } #define NYX_VER_OFF 0x9C static void _nyx_load_run() { u8 *nyx = sd_file_read("bootloader/sys/nyx.bin", NULL); if (!nyx) return; sd_end(); render_static_bootlogo(); display_backlight_brightness(h_cfg.backlight, 1000); // Check if Nyx version is old. u32 expected_nyx_ver = ((NYX_VER_MJ + '0') << 24) | ((NYX_VER_MN + '0') << 16) | ((NYX_VER_HF + '0') << 8); u32 nyx_ver = byte_swap_32(*(u32 *)(nyx + NYX_VER_OFF)); if (nyx_ver < expected_nyx_ver) { h_cfg.errors |= ERR_SYSOLD_NYX; gfx_con_setpos(0, 0); WPRINTF("Old Nyx GUI found! There will be dragons!\n"); WPRINTF("\nUpdate bootloader folder!\n\n"); WPRINTF("Press any key..."); msleep(1000); btn_wait(); } // Set hekate errors. nyx_str->info.errors = h_cfg.errors; // Set Nyx mode. nyx_str->cfg = 0; if (b_cfg.extra_cfg & EXTRA_CFG_NYX_UMS) { b_cfg.extra_cfg &= ~(EXTRA_CFG_NYX_UMS); nyx_str->cfg |= NYX_CFG_UMS; nyx_str->cfg |= b_cfg.ums << 24; } // Set hekate version used to boot Nyx. nyx_str->version = ipl_ver.version - 0x303030; // Convert ASCII to numbers. // Set SD card initialization info. nyx_str->info.magic = NYX_NEW_INFO; nyx_str->info.sd_init = sd_get_mode(); // Set SD card error info. u16 *sd_errors = sd_get_error_count(); for (u32 i = 0; i < 3; i++) nyx_str->info.sd_errors[i] = sd_errors[i]; reloc_meta_t *reloc = (reloc_meta_t *)(IPL_LOAD_ADDR + RELOC_META_OFF); memcpy((u8 *)nyx_str->hekate, (u8 *)reloc->start, reloc->end - reloc->start); // Do one last training. minerva_periodic_training(); bpmp_mmu_disable(); bpmp_clk_rate_set(BPMP_CLK_NORMAL); // Some cards (Sandisk U1), do not like a fast power cycle. sdmmc_storage_init_wait_sd(); void (*nyx_ptr)() = (void *)nyx; (*nyx_ptr)(); } static ini_sec_t *_get_ini_sec_from_id(ini_sec_t *ini_sec, char **bootlogoCustomEntry, char **emummc_path) { ini_sec_t *cfg_sec = NULL; LIST_FOREACH_ENTRY(ini_kv_t, kv, &ini_sec->kvs, link) { if (!strcmp("id", kv->key)) { if (b_cfg.id[0] && kv->val[0] && !strcmp(b_cfg.id, kv->val)) cfg_sec = ini_sec; else break; } if (!strcmp("emupath", kv->key)) *emummc_path = kv->val; else if (!strcmp("logopath", kv->key)) *bootlogoCustomEntry = kv->val; else if (!strcmp("emummc_force_disable", kv->key)) h_cfg.emummc_force_disable = atoi(kv->val); } if (!cfg_sec) { *emummc_path = NULL; *bootlogoCustomEntry = NULL; h_cfg.emummc_force_disable = false; } return cfg_sec; } static void _bootloader_corruption_protect() { FILINFO fno; if (!f_stat("bootloader", &fno)) { if (!h_cfg.bootprotect && (fno.fattrib & AM_ARC)) f_chmod("bootloader", 0, AM_ARC); else if (h_cfg.bootprotect && !(fno.fattrib & AM_ARC)) f_chmod("bootloader", AM_ARC, AM_ARC); } } static void _check_for_updated_bootloader() { // Check if already chainloaded update and clear flag. Otherwise check for updates. if (EMC(EMC_SCRATCH0) & EMC_HEKA_UPD) EMC(EMC_SCRATCH0) &= ~EMC_HEKA_UPD; else { // Check if update.bin exists and is newer and launch it. Otherwise create it. if (!f_stat("bootloader/update.bin", NULL)) _launch_payload("bootloader/update.bin", true, false); else { u8 *buf = calloc(0x200, 1); is_ipl_updated(buf, "bootloader/update.bin", true); free(buf); } } } static void _auto_launch() { struct _bmp_data { u32 size; u32 size_x; u32 size_y; u32 offset; u32 pos_x; u32 pos_y; }; u32 boot_wait = h_cfg.bootwait; u32 boot_entry_id = 0; ini_sec_t *cfg_sec = NULL; char *emummc_path = NULL; char *bootlogoCustomEntry = NULL; bool config_entry_found = false; _check_for_updated_bootloader(); bool boot_from_id = (b_cfg.boot_cfg & BOOT_CFG_FROM_ID) && (b_cfg.boot_cfg & BOOT_CFG_AUTOBOOT_EN); if (boot_from_id) b_cfg.id[7] = 0; if (!(b_cfg.boot_cfg & BOOT_CFG_FROM_LAUNCH)) gfx_con.mute = true; LIST_INIT(ini_sections); LIST_INIT(ini_list_sections); // Load emuMMC configuration. emummc_load_cfg(); // Parse hekate main configuration. if (!ini_parse(&ini_sections, "bootloader/hekate_ipl.ini", false)) goto out; // Can't load hekate_ipl.ini. // Load configuration. LIST_FOREACH_ENTRY(ini_sec_t, ini_sec, &ini_sections, link) { // Skip other ini entries for autoboot. if (ini_sec->type == INI_CHOICE) { if (!config_entry_found && !strcmp(ini_sec->name, "config")) { config_entry_found = true; LIST_FOREACH_ENTRY(ini_kv_t, kv, &ini_sec->kvs, link) { if (!strcmp("autoboot", kv->key)) h_cfg.autoboot = atoi(kv->val); else if (!strcmp("autoboot_list", kv->key)) h_cfg.autoboot_list = atoi(kv->val); else if (!strcmp("bootwait", kv->key)) boot_wait = atoi(kv->val); else if (!strcmp("backlight", kv->key)) h_cfg.backlight = atoi(kv->val); else if (!strcmp("noticker", kv->key)) h_cfg.noticker = atoi(kv->val); else if (!strcmp("autohosoff", kv->key)) h_cfg.autohosoff = atoi(kv->val); else if (!strcmp("autonogc", kv->key)) h_cfg.autonogc = atoi(kv->val); else if (!strcmp("updater2p", kv->key)) h_cfg.updater2p = atoi(kv->val); else if (!strcmp("bootprotect", kv->key)) h_cfg.bootprotect = atoi(kv->val); } boot_entry_id++; // Override autoboot. if (b_cfg.boot_cfg & BOOT_CFG_AUTOBOOT_EN) { h_cfg.autoboot = b_cfg.autoboot; h_cfg.autoboot_list = b_cfg.autoboot_list; } // Apply bootloader protection against corruption. _bootloader_corruption_protect(); // If ini list, exit here. if (!boot_from_id && h_cfg.autoboot_list) break; continue; } if (boot_from_id) cfg_sec = _get_ini_sec_from_id(ini_sec, &bootlogoCustomEntry, &emummc_path); else if (h_cfg.autoboot == boot_entry_id && config_entry_found) { cfg_sec = ini_sec; LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg_sec->kvs, link) { if (!strcmp("logopath", kv->key)) bootlogoCustomEntry = kv->val; else if (!strcmp("emupath", kv->key)) emummc_path = kv->val; else if (!strcmp("emummc_force_disable", kv->key)) h_cfg.emummc_force_disable = atoi(kv->val); else if (!strcmp("bootwait", kv->key)) boot_wait = atoi(kv->val); } } if (cfg_sec) break; boot_entry_id++; } } if (h_cfg.autohosoff && !(b_cfg.boot_cfg & BOOT_CFG_AUTOBOOT_EN)) check_power_off_from_hos(); if (h_cfg.autoboot_list || (boot_from_id && !cfg_sec)) { if (boot_from_id && cfg_sec) goto skip_list; cfg_sec = NULL; boot_entry_id = 1; bootlogoCustomEntry = NULL; if (!ini_parse(&ini_list_sections, "bootloader/ini", true)) goto skip_list; LIST_FOREACH_ENTRY(ini_sec_t, ini_sec_list, &ini_list_sections, link) { if (ini_sec_list->type != INI_CHOICE) continue; if (!strcmp(ini_sec_list->name, "config")) continue; if (boot_from_id) cfg_sec = _get_ini_sec_from_id(ini_sec_list, &bootlogoCustomEntry, &emummc_path); else if (h_cfg.autoboot == boot_entry_id) { h_cfg.emummc_force_disable = false; cfg_sec = ini_sec_list; LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg_sec->kvs, link) { if (!strcmp("logopath", kv->key)) bootlogoCustomEntry = kv->val; else if (!strcmp("emupath", kv->key)) emummc_path = kv->val; else if (!strcmp("emummc_force_disable", kv->key)) h_cfg.emummc_force_disable = atoi(kv->val); else if (!strcmp("bootwait", kv->key)) boot_wait = atoi(kv->val); } } if (cfg_sec) break; boot_entry_id++; } } skip_list: if (!cfg_sec) goto out; // No configurations or auto boot is disabled. // Check if entry is payload or l4t special case. char *special_path = ini_check_special_section(cfg_sec); if ((!(b_cfg.boot_cfg & BOOT_CFG_FROM_LAUNCH) && boot_wait) || // Conditional for HOS/Payload. (special_path && special_path == (char *)-1)) // Always show for L4T. { u32 fsize; u8 *logo_buf = NULL; u8 *bitmap = NULL; struct _bmp_data bmpData; bool bootlogoFound = false; // Check if user set custom logo path at the boot entry. if (bootlogoCustomEntry) bitmap = (u8 *)sd_file_read(bootlogoCustomEntry, &fsize); // Custom entry bootlogo not found, trying default custom one. if (!bitmap) bitmap = (u8 *)sd_file_read("bootloader/bootlogo.bmp", &fsize); if (bitmap) { // Get values manually to avoid unaligned access. bmpData.size = bitmap[2] | bitmap[3] << 8 | bitmap[4] << 16 | bitmap[5] << 24; bmpData.offset = bitmap[10] | bitmap[11] << 8 | bitmap[12] << 16 | bitmap[13] << 24; bmpData.size_x = bitmap[18] | bitmap[19] << 8 | bitmap[20] << 16 | bitmap[21] << 24; bmpData.size_y = bitmap[22] | bitmap[23] << 8 | bitmap[24] << 16 | bitmap[25] << 24; // Sanity check. if (bitmap[0] == 'B' && bitmap[1] == 'M' && bitmap[28] == 32 && // Only 32 bit BMPs allowed. bmpData.size_x <= 720 && bmpData.size_y <= 1280) { if (bmpData.size <= fsize && ((bmpData.size - bmpData.offset) < SZ_4M)) { // Avoid unaligned access from BM 2-byte MAGIC and remove header. logo_buf = (u8 *)malloc(SZ_4M); memcpy(logo_buf, bitmap + bmpData.offset, bmpData.size - bmpData.offset); free(bitmap); // Center logo if res < 720x1280. bmpData.pos_x = (720 - bmpData.size_x) >> 1; bmpData.pos_y = (1280 - bmpData.size_y) >> 1; // Get background color from 1st pixel. if (bmpData.size_x < 720 || bmpData.size_y < 1280) gfx_clear_color(*(u32 *)logo_buf); bootlogoFound = true; } } else free(bitmap); } // Clamp value to default if it exceeds 20s to protect against corruption. if (boot_wait > 20) boot_wait = 3; // Render boot logo. if (bootlogoFound) { gfx_render_bmp_argb((u32 *)logo_buf, bmpData.size_x, bmpData.size_y, bmpData.pos_x, bmpData.pos_y); free(logo_buf); // Do animated waiting before booting. If VOL- is pressed go into bootloader menu. if (render_ticker(boot_wait, h_cfg.backlight, h_cfg.noticker)) goto out; } else { // Do animated waiting before booting. If VOL- is pressed go into bootloader menu. if (render_ticker_logo(boot_wait, h_cfg.backlight)) goto out; } } if (b_cfg.boot_cfg & BOOT_CFG_FROM_LAUNCH) display_backlight_brightness(h_cfg.backlight, 0); else if (btn_read_vol() == BTN_VOL_DOWN) // 0s bootwait VOL- check. goto out; if (special_path) { // Try to launch Payload or L4T. if (special_path != (char *)-1) _launch_payload(special_path, false, false); else launch_l4t(cfg_sec, h_cfg.autoboot, h_cfg.autoboot_list, h_cfg.t210b01); goto error; } else { if (b_cfg.boot_cfg & BOOT_CFG_TO_EMUMMC) emummc_set_path(b_cfg.emummc_path); else if (emummc_path && !emummc_set_path(emummc_path)) { gfx_con.mute = false; EPRINTF("emupath is wrong!"); goto wrong_emupath; } hos_launch(cfg_sec); wrong_emupath: if (emummc_path || b_cfg.boot_cfg & BOOT_CFG_TO_EMUMMC) { sd_mount(); emummc_load_cfg(); // Reload emuMMC config in case of emupath. } error: gfx_con.mute = false; gfx_printf("\nPress any key...\n"); display_backlight_brightness(h_cfg.backlight, 1000); msleep(500); btn_wait(); } out: gfx_con.mute = false; // Clear boot reasons from binary. if (b_cfg.boot_cfg & (BOOT_CFG_FROM_ID | BOOT_CFG_TO_EMUMMC)) memset(b_cfg.xt_str, 0, sizeof(b_cfg.xt_str)); h_cfg.emummc_force_disable = false; // L4T: Clear custom boot mode flags from PMC_SCRATCH0. PMC(APBDEV_PMC_SCRATCH0) &= ~PMC_SCRATCH0_MODE_CUSTOM_ALL; _nyx_load_run(); } #define EXCP_EN_ADDR 0x4003FFFC #define EXCP_MAGIC 0x30505645 // "EVP0". #define EXCP_TYPE_ADDR 0x4003FFF8 #define EXCP_TYPE_RESET 0x545352 // "RST". #define EXCP_TYPE_UNDEF 0x464455 // "UDF". #define EXCP_TYPE_PABRT 0x54424150 // "PABT". #define EXCP_TYPE_DABRT 0x54424144 // "DABT". #define EXCP_TYPE_WDT 0x544457 // "WDT". #define EXCP_LR_ADDR 0x4003FFF4 #define PSTORE_LOG_OFFSET 0x180000 #define PSTORE_RAM_SIG 0x43474244 // "DBGC". typedef struct _pstore_buf { u32 sig; u32 start; u32 size; } pstore_buf_t; static void _show_errors() { u32 *excp_lr = (u32 *)EXCP_LR_ADDR; u32 *excp_type = (u32 *)EXCP_TYPE_ADDR; u32 *excp_enabled = (u32 *)EXCP_EN_ADDR; u32 panic_status = hw_rst_status & 0xFFFFF; // Check for exception error. if (*excp_enabled == EXCP_MAGIC) h_cfg.errors |= ERR_EXCEPTION; // Check for L4T kernel panic. if (PMC(APBDEV_PMC_SCRATCH37) == PMC_SCRATCH37_KERNEL_PANIC_MAGIC) { // Set error and clear flag. h_cfg.errors |= ERR_L4T_KERNEL; PMC(APBDEV_PMC_SCRATCH37) = 0; } // Check for watchdog panic. if (hw_rst_reason == PMC_RST_STATUS_WATCHDOG && panic_status && panic_status <= 0xFF && panic_status != 0x20 && panic_status != 0x21) { h_cfg.errors |= ERR_PANIC_CODE; } // Check if we had a panic while in CFW. secmon_exo_check_panic(); // Handle errors. if (h_cfg.errors) { gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); display_backlight_brightness(150, 1000); if (h_cfg.errors & ERR_SD_BOOT_EN) { WPRINTF("Failed to init or mount SD!\n"); // Clear the module bits as to not cram the error screen. h_cfg.errors &= ~(ERR_LIBSYS_LP0 | ERR_LIBSYS_MTC); } if (h_cfg.errors & ERR_LIBSYS_LP0) WPRINTF("Missing LP0 (sleep) lib!\n"); if (h_cfg.errors & ERR_LIBSYS_MTC) WPRINTF("Missing Minerva lib!\n"); if (h_cfg.errors & (ERR_LIBSYS_LP0 | ERR_LIBSYS_MTC)) WPRINTF("\nUpdate bootloader folder!\n\n"); if (h_cfg.errors & ERR_EXCEPTION) { WPRINTFARGS("hekate exception occurred (LR %08X):\n", *excp_lr); switch (*excp_type) { case EXCP_TYPE_WDT: WPRINTF("Hang detected in LP0/Minerva!"); break; case EXCP_TYPE_RESET: WPRINTF("RESET"); break; case EXCP_TYPE_UNDEF: WPRINTF("UNDEF"); break; case EXCP_TYPE_PABRT: WPRINTF("PABRT"); break; case EXCP_TYPE_DABRT: WPRINTF("DABRT"); break; } gfx_puts("\n"); // Clear the exception. *excp_enabled = 0; *excp_type = 0; } if (h_cfg.errors & ERR_L4T_KERNEL) { WPRINTF("Kernel panic occurred!\n"); if (!(h_cfg.errors & ERR_SD_BOOT_EN)) { if (!sd_save_to_file((void *)PSTORE_ADDR, PSTORE_SZ, "L4T_panic.bin")) WPRINTF("PSTORE saved to L4T_panic.bin"); pstore_buf_t *buf = (pstore_buf_t *)(PSTORE_ADDR + PSTORE_LOG_OFFSET); if (buf->sig == PSTORE_RAM_SIG && buf->size && buf->size < 0x80000) { u32 log_offset = PSTORE_ADDR + PSTORE_LOG_OFFSET + sizeof(pstore_buf_t); if (!sd_save_to_file((void *)log_offset, buf->size, "L4T_panic.txt")) WPRINTF("Log saved to L4T_panic.txt"); } } gfx_puts("\n"); } if (h_cfg.errors & ERR_PANIC_CODE) { u32 r = (hw_rst_status >> 20) & 0xF; u32 g = (hw_rst_status >> 24) & 0xF; u32 b = (hw_rst_status >> 28) & 0xF; r = (r << 16) | (r << 20); g = (g << 8) | (g << 12); b = (b << 0) | (b << 4); u32 color = r | g | b; WPRINTF("HOS panic occurred!\n"); gfx_printf("Color: %k####%k, Code: %02X\n\n", color, TXT_CLR_DEFAULT, panic_status); } WPRINTF("Press any key..."); msleep(1000); // Guard against injection VOL+. btn_wait(); msleep(500); // Guard against force menu VOL-. } } static void _check_low_battery() { if (fuse_read_hw_state() == FUSE_NX_HW_STATE_DEV) goto out; int enough_battery; int batt_volt = 0; int charge_status = 0; bq24193_get_property(BQ24193_ChargeStatus, &charge_status); max17050_get_property(MAX17050_AvgVCELL, &batt_volt); enough_battery = charge_status ? 3300 : 3100; // If battery voltage is enough, exit. if (batt_volt > enough_battery || !batt_volt) goto out; // Prepare battery icon resources. u8 *battery_res = malloc(ALIGN(BATTERY_EMPTY_SIZE, SZ_4K)); blz_uncompress_srcdest(battery_icons_blz, BATTERY_EMPTY_BLZ_SIZE, battery_res, BATTERY_EMPTY_SIZE); u8 *battery_icon = malloc(0x95A); // 21x38x3 u8 *charging_icon = malloc(0x2F4); // 21x12x3 u8 *no_charging_icon = calloc(0x2F4, 1); memcpy(charging_icon, battery_res, 0x2F4); memcpy(battery_icon, battery_res + 0x2F4, 0x95A); u32 battery_icon_y_pos = 1280 - 16 - BATTERY_EMPTY_BATT_HEIGHT; u32 charging_icon_y_pos = 1280 - 16 - BATTERY_EMPTY_BATT_HEIGHT - 12 - BATTERY_EMPTY_CHRG_HEIGHT; free(battery_res); charge_status = !charge_status; u32 timer = 0; bool screen_on = false; while (true) { bpmp_msleep(250); // Refresh battery stats. int current_charge_status = 0; bq24193_get_property(BQ24193_ChargeStatus, ¤t_charge_status); max17050_get_property(MAX17050_AvgVCELL, &batt_volt); enough_battery = current_charge_status ? 3300 : 3100; // If battery voltage is enough, exit. if (batt_volt > enough_battery) break; // Refresh charging icon. if (screen_on && (charge_status != current_charge_status)) { if (current_charge_status) gfx_set_rect_rgb(charging_icon, BATTERY_EMPTY_WIDTH, BATTERY_EMPTY_CHRG_HEIGHT, 16, charging_icon_y_pos); else gfx_set_rect_rgb(no_charging_icon, BATTERY_EMPTY_WIDTH, BATTERY_EMPTY_CHRG_HEIGHT, 16, charging_icon_y_pos); } // Check if it's time to turn off display. if (screen_on && timer < get_tmr_ms()) { // If battery is not charging, power off. if (!current_charge_status) { max77620_low_battery_monitor_config(true); // Handle full hw deinit and power off. power_set_state(POWER_OFF_RESET); } // If charging, just disable display. display_end(); screen_on = false; } // Check if charging status changed or Power button was pressed and enable display. if ((charge_status != current_charge_status) || (btn_wait_timeout_single(0, BTN_POWER) & BTN_POWER)) { if (!screen_on) { display_init(); u32 *fb = display_init_framebuffer_pitch(); gfx_init_ctxt(fb, 720, 1280, 720); gfx_set_rect_rgb(battery_icon, BATTERY_EMPTY_WIDTH, BATTERY_EMPTY_BATT_HEIGHT, 16, battery_icon_y_pos); if (current_charge_status) gfx_set_rect_rgb(charging_icon, BATTERY_EMPTY_WIDTH, BATTERY_EMPTY_CHRG_HEIGHT, 16, charging_icon_y_pos); else gfx_set_rect_rgb(no_charging_icon, BATTERY_EMPTY_WIDTH, BATTERY_EMPTY_CHRG_HEIGHT, 16, charging_icon_y_pos); display_backlight_pwm_init(); display_backlight_brightness(100, 1000); screen_on = true; } timer = get_tmr_ms() + 15000; } // Check if forcefully continuing. if (btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) break; charge_status = current_charge_status; } if (screen_on) display_end(); free(battery_icon); free(charging_icon); free(no_charging_icon); out: // Re enable Low Battery Monitor shutdown. max77620_low_battery_monitor_config(true); } static void _r2p_get_config_t210b01() { rtc_reboot_reason_t rr; if (!max77620_rtc_get_reboot_reason(&rr)) return; // Check if reason is actually set. if (rr.dec.reason != REBOOT_REASON_NOP) { // Clear boot storage. memset(&b_cfg, 0, sizeof(boot_cfg_t)); // Enable boot storage. b_cfg.boot_cfg |= BOOT_CFG_AUTOBOOT_EN; } switch (rr.dec.reason) { case REBOOT_REASON_NOP: break; case REBOOT_REASON_REC: PMC(APBDEV_PMC_SCRATCH0) |= PMC_SCRATCH0_MODE_RECOVERY; case REBOOT_REASON_SELF: b_cfg.autoboot = rr.dec.autoboot_idx; b_cfg.autoboot_list = rr.dec.autoboot_list; break; case REBOOT_REASON_MENU: break; case REBOOT_REASON_UMS: b_cfg.extra_cfg |= EXTRA_CFG_NYX_UMS; b_cfg.ums = rr.dec.ums_idx; break; case REBOOT_REASON_PANIC: PMC(APBDEV_PMC_SCRATCH37) = PMC_SCRATCH37_KERNEL_PANIC_MAGIC; break; } } static void _ipl_reload() { hw_reinit_workaround(false, 0); // Reload hekate. void (*ipl_ptr)() = (void *)IPL_LOAD_ADDR; (*ipl_ptr)(); } static void _about() { static const char credits[] = "\nhekate (c) 2018, naehrwert, st4rk\n\n" " (c) 2018-2023, CTCaer\n\n" " ___________________________________________\n\n" "Thanks to: %kderrek, nedwill, plutoo,\n" " shuffle2, smea, thexyz, yellows8%k\n" " ___________________________________________\n\n" "Greetings to: fincs, hexkyz, SciresM,\n" " Shiny Quagsire, WinterMute\n" " ___________________________________________\n\n" "Open source and free packages used:\n\n" " - FatFs R0.13c\n" " (c) 2018, ChaN\n\n" " - bcl-1.2.0\n" " (c) 2003-2006, Marcus Geelnard\n\n" " - Atmosphere (Exo st/types, prc id patches)\n" " (c) 2018-2019, Atmosphere-NX\n\n" " - elfload\n" " (c) 2014, Owen Shepherd\n" " (c) 2018, M4xw\n" " ___________________________________________\n\n"; static const char octopus[] = " %k___\n" " .-' `'.\n" " / \\\n" " | ;\n" " | | ___.--,\n" " _.._ |0) = (0) | _.---'`__.-( (_.\n" " __.--'`_.. '.__.\\ '--. \\_.-' ,.--'` `\"\"`\n" " ( ,.--'` ',__ /./; ;, '.__.'` __\n" " _`) ) .---.__.' / | |\\ \\__..--\"\" \"\"\"--.,_\n" " `---' .'.''-._.-'`_./ /\\ '. \\ _.--''````'''--._`-.__.'\n" " | | .' _.-' | | \\ \\ '. `----`\n" " \\ \\/ .' \\ \\ '. '-._)\n" " \\/ / \\ \\ `=.__`'-.\n" " / /\\ `) ) / / `\"\".`\\\n" " , _.-'.'\\ \\ / / ( ( / /\n" " `--'` ) ) .-'.' '.'. | (\n" " (/` ( (` ) ) '-; %k[switchbrew]%k\n" " ` '-; (-'%k"; gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); gfx_printf(credits, TXT_CLR_CYAN_L, TXT_CLR_DEFAULT); gfx_con.fntsz = 8; gfx_printf(octopus, TXT_CLR_CYAN_L, TXT_CLR_TURQUOISE, TXT_CLR_CYAN_L, TXT_CLR_DEFAULT); btn_wait(); } ment_t ment_cinfo[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("---- SoC Info ----", TXT_CLR_CYAN_L), MDEF_HANDLER("Fuses", print_fuseinfo), MDEF_CHGLINE(), MDEF_CAPTION("-- Storage Info --", TXT_CLR_CYAN_L), MDEF_HANDLER("eMMC", print_mmc_info), MDEF_HANDLER("SD Card", print_sdcard_info), MDEF_CHGLINE(), MDEF_CAPTION("------ Misc ------", TXT_CLR_CYAN_L), MDEF_HANDLER("Battery", print_battery_info), MDEF_END() }; menu_t menu_cinfo = { ment_cinfo, "Console Info", 0, 0 }; ment_t ment_tools[] = { MDEF_BACK(), MDEF_CHGLINE(), MDEF_CAPTION("-------- Other -------", TXT_CLR_WARNING), MDEF_HANDLER("AutoRCM", menu_autorcm), MDEF_END() }; menu_t menu_tools = { ment_tools, "Tools", 0, 0 }; power_state_t STATE_POWER_OFF = POWER_OFF_RESET; power_state_t STATE_REBOOT_RCM = REBOOT_RCM; power_state_t STATE_REBOOT_BYPASS_FUSES = REBOOT_BYPASS_FUSES; ment_t ment_top[] = { MDEF_HANDLER("Launch", _launch_config), MDEF_CAPTION("---------------", TXT_CLR_GREY_DM), MDEF_MENU("Tools", &menu_tools), MDEF_MENU("Console info", &menu_cinfo), MDEF_CAPTION("---------------", TXT_CLR_GREY_DM), MDEF_HANDLER("Reload", _ipl_reload), MDEF_HANDLER_EX("Reboot (OFW)", &STATE_REBOOT_BYPASS_FUSES, power_set_state_ex), MDEF_HANDLER_EX("Reboot (RCM)", &STATE_REBOOT_RCM, power_set_state_ex), MDEF_HANDLER_EX("Power off", &STATE_POWER_OFF, power_set_state_ex), MDEF_CAPTION("---------------", TXT_CLR_GREY_DM), MDEF_HANDLER("About", _about), MDEF_END() }; menu_t menu_top = { ment_top, "hekate v6.0.7", 0, 0 }; extern void pivot_stack(u32 stack_top); void ipl_main() { // Do initial HW configuration. This is compatible with consecutive reruns without a reset. hw_init(); // Pivot the stack so we have enough space. pivot_stack(IPL_STACK_TOP); // Tegra/Horizon configuration goes to 0x80000000+, package2 goes to 0xA9800000, we place our heap in between. heap_init((void *)IPL_HEAP_START); #ifdef DEBUG_UART_PORT uart_send(DEBUG_UART_PORT, (u8 *)"hekate: Hello!\r\n", 16); uart_wait_xfer(DEBUG_UART_PORT, UART_TX_IDLE); #endif // Check if battery is enough. _check_low_battery(); // Set bootloader's default configuration. set_default_configuration(); // Prep RTC regs for read. Needed for T210B01 R2P. max77620_rtc_prep_read(); // Initialize display. display_init(); // Mount SD Card. h_cfg.errors |= !sd_mount() ? ERR_SD_BOOT_EN : 0; // Check if watchdog was fired previously. if (watchdog_fired()) goto skip_lp0_minerva_config; // Enable watchdog protection to avoid SD corruption based hanging in LP0/Minerva config. watchdog_start(5000000 / 2, TIMER_FIQENABL_EN); // 5 seconds. // Save sdram lp0 config. void *sdram_params = h_cfg.t210b01 ? sdram_get_params_t210b01() : sdram_get_params_patched(); if (!ianos_loader("bootloader/sys/libsys_lp0.bso", DRAM_LIB, sdram_params)) h_cfg.errors |= ERR_LIBSYS_LP0; // Train DRAM and switch to max frequency. if (minerva_init()) //!TODO: Add Tegra210B01 support to minerva. h_cfg.errors |= ERR_LIBSYS_MTC; // Disable watchdog protection. watchdog_end(); skip_lp0_minerva_config: // Initialize display window, backlight and gfx console. u32 *fb = display_init_framebuffer_pitch(); gfx_init_ctxt(fb, 720, 1280, 720); gfx_con_init(); display_backlight_pwm_init(); //display_backlight_brightness(h_cfg.backlight, 1000); // Overclock BPMP. bpmp_clk_rate_set(h_cfg.t210b01 ? BPMP_CLK_DEFAULT_BOOST : BPMP_CLK_LOWER_BOOST); // Get R2P config from RTC. if (h_cfg.t210b01) _r2p_get_config_t210b01(); // Show exceptions, HOS errors, library errors and L4T kernel panics. _show_errors(); // Load saved configuration and auto boot if enabled. if (!(h_cfg.errors & ERR_SD_BOOT_EN)) _auto_launch(); // Failed to launch Nyx, unmount SD Card. sd_end(); // Set ram to a freq that doesn't need periodic training. minerva_change_freq(FREQ_800); while (true) tui_do_menu(&menu_top); // Halt BPMP if we managed to get out of execution. while (true) bpmp_halt(); }