mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
Rewrote crt0, init, and chainloading code
start.s, init.c, linker.ld and linker.specs are meant to be re-used by user applications, should they remove the defines from init.c and the .chainloader* sections from the linker script
This commit is contained in:
parent
699ddfc043
commit
e8306361f0
29 changed files with 848 additions and 185 deletions
|
@ -27,7 +27,8 @@ CFLAGS = \
|
|||
-std=gnu11 \
|
||||
-Werror \
|
||||
-Wall \
|
||||
-fstrict-volatile-bitfields
|
||||
-fstrict-volatile-bitfields \
|
||||
-DFUSEE_STAGE1_SRC
|
||||
|
||||
LDFLAGS = -specs=linker.specs -g $(ARCH)
|
||||
|
||||
|
|
|
@ -3,19 +3,57 @@ OUTPUT_ARCH(arm)
|
|||
ENTRY(_start)
|
||||
|
||||
/* Mostly copied from https://github.com/devkitPro/buildscripts/blob/master/dkarm-eabi/crtls/3dsx.ld */
|
||||
MEMORY
|
||||
{
|
||||
NULL : ORIGIN = 0x00000000, LENGTH = 0x1000
|
||||
main : ORIGIN = 0x40010000, LENGTH = 0x20000
|
||||
low_iram : ORIGIN = 0x40003000, LENGTH = 0x8000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
PROVIDE(__start__ = 0x40010000);
|
||||
PROVIDE(__start__ = 0x40010000);
|
||||
PROVIDE(__stack_top__ = 0x40010000);
|
||||
PROVIDE(__stack_bottom__ = 0x4000C000);
|
||||
PROVIDE(__heap_start__ = 0);
|
||||
PROVIDE(__heap_end__ = 0);
|
||||
|
||||
. = __start__;
|
||||
. = ALIGN(32);
|
||||
.text :
|
||||
|
||||
.crt0 :
|
||||
{
|
||||
. = ALIGN(32);
|
||||
/* .init */
|
||||
KEEP( *(.text.start) )
|
||||
KEEP( *(.init) )
|
||||
. = ALIGN(4);
|
||||
} >main
|
||||
|
||||
.chainloader_loadable :
|
||||
{
|
||||
. = ALIGN(32);
|
||||
PROVIDE (__chainloader_start__ = .);
|
||||
PROVIDE (__chainloader_lma__ = LOADADDR(.chainloader_loadable));
|
||||
KEEP(*(.chainloader.text.start))
|
||||
build/chainloader.o(.text*)
|
||||
build/chainloader.o(.rodata*)
|
||||
build/chainloader.o(.data*)
|
||||
. = ALIGN(8);
|
||||
|
||||
} >low_iram AT>main
|
||||
|
||||
.chainloader_bss :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
PROVIDE (__chainloader_bss_start__ = .);
|
||||
build/chainloader.o(.bss* COMMON)
|
||||
. = ALIGN(8);
|
||||
PROVIDE (__chainloader_end__ = .);
|
||||
} >low_iram AT>main
|
||||
|
||||
.text :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
|
||||
/* .text */
|
||||
*(.text)
|
||||
|
@ -30,7 +68,7 @@ SECTIONS
|
|||
/* .fini */
|
||||
KEEP( *(.fini) )
|
||||
. = ALIGN(4);
|
||||
}
|
||||
} >main
|
||||
|
||||
.rodata :
|
||||
{
|
||||
|
@ -41,14 +79,14 @@ SECTIONS
|
|||
*(.gnu.linkonce.r*)
|
||||
SORT(CONSTRUCTORS)
|
||||
. = ALIGN(4);
|
||||
}
|
||||
} >main
|
||||
|
||||
.preinit_array ALIGN(4) :
|
||||
{
|
||||
PROVIDE (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array))
|
||||
PROVIDE (__preinit_array_end = .);
|
||||
}
|
||||
} >main
|
||||
|
||||
.init_array ALIGN(4) :
|
||||
{
|
||||
|
@ -56,7 +94,7 @@ SECTIONS
|
|||
KEEP (*(SORT(.init_array.*)))
|
||||
KEEP (*(.init_array))
|
||||
PROVIDE (__init_array_end = .);
|
||||
}
|
||||
} >main
|
||||
|
||||
.fini_array ALIGN(4) :
|
||||
{
|
||||
|
@ -64,7 +102,7 @@ SECTIONS
|
|||
KEEP (*(.fini_array))
|
||||
KEEP (*(SORT(.fini_array.*)))
|
||||
PROVIDE (__fini_array_end = .);
|
||||
}
|
||||
} >main
|
||||
|
||||
.ctors ALIGN(4) :
|
||||
{
|
||||
|
@ -72,7 +110,7 @@ SECTIONS
|
|||
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
|
||||
KEEP (*(SORT(.ctors.*)))
|
||||
KEEP (*(.ctors))
|
||||
}
|
||||
} >main
|
||||
|
||||
.dtors ALIGN(4) :
|
||||
{
|
||||
|
@ -80,11 +118,11 @@ SECTIONS
|
|||
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
|
||||
KEEP (*(SORT(.dtors.*)))
|
||||
KEEP (*(.dtors))
|
||||
}
|
||||
} >main
|
||||
|
||||
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) }
|
||||
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >main
|
||||
__exidx_start = .;
|
||||
ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) }
|
||||
ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >main
|
||||
__exidx_end = .;
|
||||
|
||||
.data :
|
||||
|
@ -94,19 +132,19 @@ SECTIONS
|
|||
*(.gnu.linkonce.d*)
|
||||
CONSTRUCTORS
|
||||
. = ALIGN(4);
|
||||
}
|
||||
} >main
|
||||
|
||||
__bss_start__ = ALIGN(32);
|
||||
.bss :
|
||||
{
|
||||
__bss_start__ = ALIGN(32);
|
||||
*(.dynbss)
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
*(.gnu.linkonce.b*)
|
||||
*(COMMON)
|
||||
. = ALIGN(8);
|
||||
}
|
||||
__bss_end__ = .;
|
||||
__bss_end__ = .;
|
||||
} >main
|
||||
__end__ = ABSOLUTE(.) ;
|
||||
|
||||
/* ==================
|
||||
|
|
34
fusee/fusee-primary/src/chainloader.c
Normal file
34
fusee/fusee-primary/src/chainloader.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "chainloader.h"
|
||||
|
||||
char g_chainloader_arg_data[CHAINLOADER_ARG_DATA_MAX_SIZE] = {0};
|
||||
chainloader_entry_t g_chainloader_entries[CHAINLOADER_MAX_ENTRIES] = {0}; /* keep them sorted */
|
||||
size_t g_chainloader_num_entries = 0;
|
||||
uintptr_t g_chainloader_entrypoint = 0;
|
||||
|
||||
#pragma GCC optimize (3)
|
||||
|
||||
static void *xmemmove(void *dst, const void *src, size_t len)
|
||||
{
|
||||
const uint8_t *src8 = (const uint8_t *)src;
|
||||
uint8_t *dst8 = (uint8_t *)dst;
|
||||
|
||||
if (dst8 < src8) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
dst8[i] = src8[i];
|
||||
}
|
||||
} else if (src8 > dst8) {
|
||||
for (size_t i = len; len > 0; len--)
|
||||
dst8[i - 1] = src8[i - 1];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void relocate_and_chainload_main(int argc) {
|
||||
for(size_t i = 0; i < g_chainloader_num_entries; i++) {
|
||||
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
||||
xmemmove((void *)entry->load_address, (const void *)entry->src_address, entry->size);
|
||||
}
|
||||
|
||||
((void (*)(int, void *))g_chainloader_entrypoint)(argc, g_chainloader_arg_data);
|
||||
}
|
25
fusee/fusee-primary/src/chainloader.h
Normal file
25
fusee/fusee-primary/src/chainloader.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef FUSEE_CHAINLOADER_H
|
||||
#define FUSEE_CHAINLOADER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHAINLOADER_ARG_DATA_MAX_SIZE 0x6200
|
||||
#define CHAINLOADER_MAX_ENTRIES 128
|
||||
|
||||
typedef struct chainloader_entry_t {
|
||||
uintptr_t load_address;
|
||||
uintptr_t src_address;
|
||||
size_t size;
|
||||
size_t num;
|
||||
} chainloader_entry_t;
|
||||
|
||||
extern chainloader_entry_t g_chainloader_entries[CHAINLOADER_MAX_ENTRIES]; /* keep them sorted */
|
||||
extern size_t g_chainloader_num_entries;
|
||||
extern uintptr_t g_chainloader_entrypoint;
|
||||
|
||||
extern char g_chainloader_arg_data[CHAINLOADER_ARG_DATA_MAX_SIZE];
|
||||
|
||||
void relocate_and_chainload(int argc);
|
||||
|
||||
#endif
|
|
@ -28,7 +28,7 @@ void clock_enable_fuse(u32 enable);
|
|||
void display_color_screen(u32 color);
|
||||
|
||||
/*! Init display in full 1280x720 resolution (32bpp, line stride 768, framebuffer size = 1280*768*4 bytes). */
|
||||
u32 *display_init_framebuffer();
|
||||
u32 *display_init_framebuffer(void *address);
|
||||
|
||||
/*! Enable or disable the backlight. Should only be called when the screen is completely set up, to avoid flickering. */
|
||||
void display_enable_backlight(bool on);
|
||||
|
|
|
@ -188,9 +188,9 @@ void display_enable_backlight(bool on) {
|
|||
}
|
||||
|
||||
|
||||
u32 *display_init_framebuffer(void)
|
||||
u32 *display_init_framebuffer(void *address)
|
||||
{
|
||||
u32 *lfb_addr = (u32 *)0xC0000000;
|
||||
u32 *lfb_addr = (u32 *)address;
|
||||
|
||||
//This configures the framebuffer @ 0xC0000000 with a resolution of 1280x720 (line stride 768).
|
||||
exec_cfg((u32 *)DISPLAY_A_BASE, cfg_display_framebuffer, 32);
|
||||
|
|
|
@ -52,7 +52,7 @@ void display_end();
|
|||
void display_color_screen(u32 color);
|
||||
|
||||
/*! Init display in full 1280x720 resolution (32bpp, line stride 768, framebuffer size = 1280*768*4 bytes). */
|
||||
u32 *display_init_framebuffer(void);
|
||||
u32 *display_init_framebuffer(void *address);
|
||||
|
||||
/*! Enable or disable the backlight. Should only be called when the screen is completely set up, to avoid flickering. */
|
||||
void display_enable_backlight(bool on);
|
||||
|
|
130
fusee/fusee-primary/src/init.c
Normal file
130
fusee/fusee-primary/src/init.c
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <sys/iosupport.h>
|
||||
#include "utils.h"
|
||||
|
||||
void __libc_init_array(void);
|
||||
void __libc_fini_array(void);
|
||||
|
||||
extern uint8_t __bss_start__[], __bss_end__[];
|
||||
extern uint8_t __heap_start__[], __heap_end__[];
|
||||
|
||||
extern char *fake_heap_start;
|
||||
extern char *fake_heap_end;
|
||||
|
||||
int __program_argc;
|
||||
void **__program_argv;
|
||||
|
||||
void __attribute__((noreturn)) __program_exit(int rc);
|
||||
void __attribute__((noreturn)) (*__program_exit_callback)(int rc) = NULL;
|
||||
|
||||
static void __program_parse_argc_argv(int argc, char *argdata);
|
||||
static void __program_cleanup_argv(void);
|
||||
|
||||
static void __program_init_heap(void) {
|
||||
fake_heap_start = (char*)__heap_start__;
|
||||
fake_heap_end = (char*)__heap_end__;
|
||||
}
|
||||
|
||||
static void __program_init_newlib_hooks(void) {
|
||||
__syscalls.exit = __program_exit; /* For exit, etc. */
|
||||
}
|
||||
|
||||
static void __program_move_additional_sections(void) {
|
||||
#if defined(FUSEE_STAGE1_SRC) || defined(FUSEE_STAGE2_SRC)
|
||||
extern uint8_t __chainloader_lma__[], __chainloader_start__[], __chainloader_bss_start__[], __chainloader_end__[];
|
||||
memcpy(__chainloader_start__, __chainloader_lma__, __chainloader_bss_start__ - __chainloader_start__);
|
||||
memset(__chainloader_bss_start__, 0, __chainloader_end__ - __chainloader_bss_start__);
|
||||
#endif
|
||||
}
|
||||
|
||||
void __program_init(int argc, char *argdata) {
|
||||
/* Zero-fill the .bss section */
|
||||
memset(__bss_start__, 0, __bss_end__ - __bss_start__);
|
||||
|
||||
__program_init_heap();
|
||||
__program_init_newlib_hooks();
|
||||
__program_parse_argc_argv(argc, argdata);
|
||||
|
||||
/* Once argv is parsed, we can discard the low IRAM region */
|
||||
__program_move_additional_sections();
|
||||
__libc_init_array();
|
||||
}
|
||||
|
||||
void __program_exit(int rc) {
|
||||
__libc_fini_array();
|
||||
__program_cleanup_argv();
|
||||
if (__program_exit_callback == NULL) {
|
||||
/* Default callback */
|
||||
generic_panic();
|
||||
} else {
|
||||
__program_exit_callback(rc);
|
||||
}
|
||||
for (;;);
|
||||
}
|
||||
|
||||
#ifdef FUSEE_STAGE1_SRC
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
__program_argc = 0;
|
||||
__program_argv = NULL;
|
||||
}
|
||||
#elif defined(FUSEE_STAGE2_SRC)
|
||||
#include "stage2.h"
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
size_t pos = 0, len;
|
||||
|
||||
__program_argc = argc;
|
||||
|
||||
__program_argv = malloc(argc * sizeof(void **));
|
||||
if (__program_argv == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
len = strlen(argdata);
|
||||
__program_argv[0] = malloc(len + 1);
|
||||
if (__program_argv[0] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
strcpy((char *)__program_argv[0], argdata);
|
||||
pos += len + 1;
|
||||
|
||||
__program_argv[1] = malloc(len + 1);
|
||||
if (__program_argv[1] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
memcpy(__program_argv[1], argdata + pos, sizeof(stage2_args_t));
|
||||
}
|
||||
#else
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
size_t pos = 0, len;
|
||||
|
||||
__program_argc = argc;
|
||||
|
||||
__program_argv = malloc(argc * sizeof(void **));
|
||||
if (__program_argv == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
len = strlen(argdata + pos);
|
||||
__program_argv[i] = malloc(len + 1);
|
||||
if (__program_argv[i] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
strcpy((char *)__program_argv[i], argdata + pos);
|
||||
pos += len + 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __program_cleanup_argv(void) {
|
||||
#ifndef FUSEE_STAGE1_SRC
|
||||
for (int i = 0; i < __program_argc; i++) {
|
||||
free(__program_argv[i]);
|
||||
__program_argv[i] = NULL;
|
||||
}
|
||||
free(__program_argv);
|
||||
#endif
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 3
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
|
|
|
@ -4,15 +4,13 @@
|
|||
#include "se.h"
|
||||
#include "sd_utils.h"
|
||||
#include "stage2.h"
|
||||
#include "chainloader.h"
|
||||
#include "sdmmc.h"
|
||||
#include "lib/fatfs/ff.h"
|
||||
#include "lib/printk.h"
|
||||
#include "display/video_fb.h"
|
||||
|
||||
/* TODO: Should we allow more than 32K for the BCT0? */
|
||||
#define BCT0_LOAD_ADDRESS (uintptr_t)(0x40038000)
|
||||
#define BCT0_LOAD_END_ADDRESS (uintptr_t)(0x4003F000)
|
||||
#define MAGIC_BCT0 0x30544342
|
||||
static char g_bct0_buffer[BCTO_MAX_SIZE];
|
||||
|
||||
#define DEFAULT_BCT0_FOR_DEBUG \
|
||||
"BCT0\n"\
|
||||
|
@ -22,21 +20,21 @@
|
|||
"stage2_entrypoint = 0xFFF00000\n"
|
||||
|
||||
const char *load_config(void) {
|
||||
if (!read_sd_file((void *)BCT0_LOAD_ADDRESS, BCT0_LOAD_END_ADDRESS - BCT0_LOAD_ADDRESS, "BCT.ini")) {
|
||||
if (!read_sd_file(g_bct0_buffer, BCTO_MAX_SIZE, "BCT.ini")) {
|
||||
printk("Failed to read BCT0 from SD!\n");
|
||||
printk("[DEBUG] Using default BCT0!\n");
|
||||
memcpy((void *)BCT0_LOAD_ADDRESS, DEFAULT_BCT0_FOR_DEBUG, sizeof(DEFAULT_BCT0_FOR_DEBUG));
|
||||
memcpy(g_bct0_buffer, DEFAULT_BCT0_FOR_DEBUG, sizeof(DEFAULT_BCT0_FOR_DEBUG));
|
||||
/* TODO: Stop using default. */
|
||||
/* printk("Error: Failed to load BCT.ini!\n");
|
||||
* generic_panic(); */
|
||||
}
|
||||
|
||||
if ((*((u32 *)(BCT0_LOAD_ADDRESS))) != MAGIC_BCT0) {
|
||||
if (memcmp(g_bct0_buffer, "BCT0", 4) != 0) {
|
||||
printk("Error: Unexpected magic in BCT.ini!\n");
|
||||
generic_panic();
|
||||
}
|
||||
/* Return pointer to first line of the ini. */
|
||||
const char *bct0 = (const char *)BCT0_LOAD_ADDRESS;
|
||||
const char *bct0 = g_bct0_buffer;
|
||||
while (*bct0 && *bct0 != '\n') {
|
||||
bct0++;
|
||||
}
|
||||
|
@ -60,12 +58,10 @@ void load_sbk(void) {
|
|||
}
|
||||
|
||||
int main(void) {
|
||||
stage2_entrypoint_t stage2_entrypoint;
|
||||
void **stage2_argv = (void **)(BCT0_LOAD_END_ADDRESS);
|
||||
const char *bct0;
|
||||
u32 *lfb_base;
|
||||
char buf[0x400];
|
||||
memset(buf, 0xCC, 0x400);
|
||||
const char *stage2_path;
|
||||
stage2_args_t stage2_args = {0};
|
||||
|
||||
/* Initialize DRAM. */
|
||||
/* TODO: What can be stripped out to make this minimal? */
|
||||
|
@ -75,7 +71,7 @@ int main(void) {
|
|||
display_init();
|
||||
|
||||
/* Register the display as a printk provider. */
|
||||
lfb_base = display_init_framebuffer();
|
||||
lfb_base = display_init_framebuffer((void *)0xC0000000);
|
||||
video_init(lfb_base);
|
||||
|
||||
/* Turn on the backlight after initializing the lfb */
|
||||
|
@ -102,23 +98,21 @@ int main(void) {
|
|||
bct0 = load_config();
|
||||
|
||||
/* Load the loader payload into DRAM. */
|
||||
stage2_entrypoint = load_stage2(bct0);
|
||||
load_stage2(bct0);
|
||||
|
||||
/* Setup argv. */
|
||||
memset(stage2_argv, 0, STAGE2_ARGC * sizeof(*stage2_argv));
|
||||
stage2_argv[STAGE2_ARGV_PROGRAM_PATH] = (void *)stage2_get_program_path();
|
||||
stage2_argv[STAGE2_ARGV_ARGUMENT_STRUCT] = &stage2_argv[STAGE2_ARGC];
|
||||
stage2_args_t *args = (stage2_args_t *)stage2_argv[STAGE2_ARGV_ARGUMENT_STRUCT];
|
||||
|
||||
/* Setup arguments struct. */
|
||||
args->version = 0;
|
||||
args->bct0 = bct0;
|
||||
args->lfb = (uint32_t *)lfb_base;
|
||||
args->console_col = video_get_col();
|
||||
args->console_row = video_get_row();
|
||||
f_unmount("");
|
||||
|
||||
display_enable_backlight(false);
|
||||
display_end();
|
||||
|
||||
/* Setup argument data. */
|
||||
stage2_path = stage2_get_program_path();
|
||||
stage2_args.version = 0;
|
||||
strcpy(stage2_args.bct0, bct0);
|
||||
strcpy(g_chainloader_arg_data, stage2_path);
|
||||
memcpy(g_chainloader_arg_data + strlen(stage2_path) + 1, &stage2_args, sizeof(stage2_args_t));
|
||||
|
||||
/* Jump to Stage 2. */
|
||||
stage2_entrypoint(STAGE2_ARGC, stage2_argv);
|
||||
relocate_and_chainload(STAGE2_ARGC);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#include "utils.h"
|
||||
#include <stdint.h>
|
||||
#include "display/video_fb.h"
|
||||
#include "sd_utils.h"
|
||||
#include "stage2.h"
|
||||
#include "chainloader.h"
|
||||
#include "lib/printk.h"
|
||||
#include "lib/vsprintf.h"
|
||||
#include "lib/ini.h"
|
||||
#include "lib/fatfs/ff.h"
|
||||
|
||||
char g_stage2_path[0x300] = {0};
|
||||
char g_stage2_path[0x100] = {0};
|
||||
|
||||
const char *stage2_get_program_path(void) {
|
||||
return g_stage2_path;
|
||||
|
@ -22,13 +25,13 @@ static int stage2_ini_handler(void *user, const char *section, const char *name,
|
|||
/* Read in load address as a hex string. */
|
||||
sscanf(value, "%x", &x);
|
||||
config->load_address = x;
|
||||
if (config->entrypoint == NULL) {
|
||||
config->entrypoint = (stage2_entrypoint_t)config->load_address;
|
||||
if (config->entrypoint == 0) {
|
||||
config->entrypoint = config->load_address;
|
||||
}
|
||||
} else if (strcmp(name, STAGE2_ENTRYPOINT_KEY) == 0) {
|
||||
/* Read in entrypoint as a hex string. */
|
||||
sscanf(value, "%x", &x);
|
||||
config->entrypoint = (stage2_entrypoint_t)x;
|
||||
config->entrypoint = x;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
@ -38,8 +41,11 @@ static int stage2_ini_handler(void *user, const char *section, const char *name,
|
|||
return 1;
|
||||
}
|
||||
|
||||
stage2_entrypoint_t load_stage2(const char *bct0) {
|
||||
void load_stage2(const char *bct0) {
|
||||
stage2_config_t config = {0};
|
||||
FILINFO info;
|
||||
size_t size;
|
||||
uintptr_t tmp_addr;
|
||||
|
||||
if (ini_parse_string(bct0, stage2_ini_handler, &config) < 0) {
|
||||
printk("Error: Failed to parse BCT.ini!\n");
|
||||
|
@ -51,17 +57,65 @@ stage2_entrypoint_t load_stage2(const char *bct0) {
|
|||
generic_panic();
|
||||
}
|
||||
|
||||
if (strlen(config.path) + 1 + sizeof(stage2_args_t) > CHAINLOADER_ARG_DATA_MAX_SIZE) {
|
||||
printk("Error: Stage2's path name is too big!\n");
|
||||
}
|
||||
|
||||
if (!check_32bit_address_loadable(config.entrypoint)) {
|
||||
printk("Error: Stage2's entrypoint is invalid!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (!check_32bit_address_loadable(config.load_address)) {
|
||||
printk("Error: Stage2's load address is invalid!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
printk("[DEBUG] Stage 2 Config:\n");
|
||||
printk(" File Path: %s\n", config.path);
|
||||
printk(" Load Address: 0x%08x\n", config.load_address);
|
||||
printk(" Entrypoint: 0x%p\n", config.entrypoint);
|
||||
|
||||
if (!read_sd_file((void *)config.load_address, 0x100000, config.path)) {
|
||||
if (f_stat(config.path, &info) != FR_OK) {
|
||||
printk("Error: Failed to stat stage2 (%s)!\n", config.path);
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
size = (size_t)info.fsize;
|
||||
|
||||
/* the LFB is located at 0xC0000000 atm */
|
||||
if (size > 0xC0000000u - 0x80000000u) {
|
||||
printk("Error: Stage2 is way too big!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (!check_32bit_address_range_loadable(config.load_address, size)) {
|
||||
printk("Error: Stage2 has an invalid load address & size combination!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (config.entrypoint < config.load_address || config.entrypoint >= config.load_address + size) {
|
||||
printk("Error: Stage2's entrypoint is outside Stage2!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (check_32bit_address_range_in_program(config.load_address, size)) {
|
||||
tmp_addr = 0x80000000u;
|
||||
} else {
|
||||
tmp_addr = config.load_address;
|
||||
}
|
||||
|
||||
if (read_sd_file((void *)tmp_addr, size, config.path) != size) {
|
||||
printk("Error: Failed to read stage2 (%s)!\n", config.path);
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
strncpy(g_stage2_path, config.path, sizeof(g_stage2_path));
|
||||
g_chainloader_num_entries = 1;
|
||||
g_chainloader_entries[0].load_address = config.load_address;
|
||||
g_chainloader_entries[0].src_address = tmp_addr;
|
||||
g_chainloader_entries[0].size = size;
|
||||
g_chainloader_entries[0].num = 0;
|
||||
g_chainloader_entrypoint = config.entrypoint;
|
||||
|
||||
return config.entrypoint;
|
||||
strncpy(g_stage2_path, config.path, sizeof(g_stage2_path));
|
||||
}
|
||||
|
|
|
@ -11,24 +11,23 @@
|
|||
#define STAGE2_NAME_KEY "stage2_path"
|
||||
#define STAGE2_ADDRESS_KEY "stage2_addr"
|
||||
#define STAGE2_ENTRYPOINT_KEY "stage2_entrypoint"
|
||||
|
||||
typedef void (*stage2_entrypoint_t)(int argc, void **argv);
|
||||
#define BCTO_MAX_SIZE 0x6000
|
||||
|
||||
typedef struct {
|
||||
char path[0x300];
|
||||
char path[0x100];
|
||||
uintptr_t load_address;
|
||||
stage2_entrypoint_t entrypoint;
|
||||
uintptr_t entrypoint;
|
||||
} stage2_config_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
const char *bct0;
|
||||
uint32_t *lfb;
|
||||
uint32_t console_row;
|
||||
uint32_t console_col;
|
||||
char bct0[BCTO_MAX_SIZE];
|
||||
} stage2_args_t;
|
||||
|
||||
const char *stage2_get_program_path(void);
|
||||
stage2_entrypoint_t load_stage2(const char *bct0);
|
||||
void load_stage2(const char *bct0);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,30 +2,27 @@
|
|||
mov r\@, #0
|
||||
.endm
|
||||
|
||||
.section .text.start
|
||||
.section .text.start, "ax", %progbits
|
||||
.arm
|
||||
.align 5
|
||||
.global _start
|
||||
.type _start, %function
|
||||
_start:
|
||||
/* Insert NOPs for convenience (i.e. to use Nintendo's BCTs, for example) */
|
||||
.rept 16
|
||||
nop
|
||||
.endr
|
||||
/* Switch to supervisor mode, mask all interrupts, clear all flags */
|
||||
/* Switch to system mode, mask all interrupts, clear all flags */
|
||||
msr cpsr_cxsf, #0xDF
|
||||
|
||||
/* Relocate ourselves if necessary */
|
||||
ldr r0, =__start__
|
||||
adr r1, _start
|
||||
cmp r0, r1
|
||||
ldr r2, =__start__
|
||||
adr r3, _start
|
||||
cmp r2, r3
|
||||
bne _relocation_loop_end
|
||||
|
||||
ldr r2, =__bss_start__
|
||||
sub r2, r2, r0 /* size >= 32, obviously */
|
||||
ldr r4, =__bss_start__
|
||||
sub r4, r4, r2 /* size >= 32, obviously, and we've declared 32-byte-alignment */
|
||||
_relocation_loop:
|
||||
ldmia r1!, {r3-r10}
|
||||
stmia r0!, {r3-r10}
|
||||
subs r2, #0x20
|
||||
ldmia r3!, {r5-r12}
|
||||
stmia r2!, {r5-r12}
|
||||
subs r4, #0x20
|
||||
bne _relocation_loop
|
||||
|
||||
ldr r12, =_relocation_loop_end
|
||||
|
@ -33,22 +30,25 @@ _start:
|
|||
|
||||
_relocation_loop_end:
|
||||
/* Set the stack pointer */
|
||||
ldr sp, =0x40008000
|
||||
mov fp, #0
|
||||
ldr sp, =__stack_top__
|
||||
mov fp, #0
|
||||
bl __program_init
|
||||
|
||||
/* Clear .bss */
|
||||
ldr r0, =__bss_start__
|
||||
mov r1, #0
|
||||
ldr r2, =__bss_end__
|
||||
sub r2, r2, r0
|
||||
bl memset
|
||||
|
||||
/* Call global constructors */
|
||||
bl __libc_init_array
|
||||
|
||||
/* Set r0 to r12 to 0 (because why not?) & call main */
|
||||
/* Set r0 to r12 to 0 (for debugging) & call main */
|
||||
.rept 13
|
||||
CLEAR_GPR_REG_ITER
|
||||
.endr
|
||||
bl main
|
||||
b .
|
||||
ldr r0, =__program_argc
|
||||
ldr r1, =__program_argv
|
||||
ldr lr, =__program_exit
|
||||
b main
|
||||
|
||||
/* No need to include this in normal programs: */
|
||||
.section .chainloader.text.start, "ax", %progbits
|
||||
.arm
|
||||
.align 5
|
||||
.global relocate_and_chainload
|
||||
.type relocate_and_chainload, %function
|
||||
relocate_and_chainload:
|
||||
ldr sp, =__stack_top__
|
||||
b relocate_and_chainload_main
|
||||
|
|
|
@ -71,9 +71,38 @@ static inline bool check_32bit_additive_overflow(uint32_t a, uint32_t b) {
|
|||
return __builtin_add_overflow_p(a, b, (uint32_t)0);
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_loadable(uintptr_t addr) {
|
||||
/* FWIW the bootROM forbids loading anything between 0x40000000 and 0x40010000, using it for itself... */
|
||||
return (addr >= 0x40010000u && addr < 0x40040000u) || addr >= 0x80000000u;
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_range_loadable(uintptr_t addr, size_t size) {
|
||||
return
|
||||
__builtin_add_overflow_p(addr, size, (uintptr_t)0) && /* the range doesn't overflow */
|
||||
check_32bit_address_loadable(addr) && check_32bit_address_loadable(addr + size) && /* bounds are valid */
|
||||
!(addr >= 0x40010000u && addr + size >= 0x40040000u) /* the range doesn't cross MMIO */
|
||||
;
|
||||
}
|
||||
|
||||
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
|
||||
static inline bool overlaps_a(const void *as, const void *ae, const void *bs, const void *be) {
|
||||
return overlaps((uint64_t)(uintptr_t)as, (uint64_t)(uintptr_t)ae, (uint64_t)(uintptr_t)bs, (uint64_t)(uintptr_t)be);
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_range_in_program(uintptr_t addr, size_t size) {
|
||||
extern uint8_t __chainloader_start__[], __chainloader_end__[];
|
||||
extern uint8_t __stack_bottom__[], __stack_top__[];
|
||||
extern uint8_t __start__[], __end__[];
|
||||
uint8_t *start = (uint8_t *)addr, *end = start + size;
|
||||
|
||||
return overlaps_a(start, end, __chainloader_start__, __chainloader_end__) ||
|
||||
overlaps_a(start, end, __stack_bottom__, __stack_top__) ||
|
||||
overlaps_a(start, end, (void *)0xC0000000, (void *)0xC03C0000) || /* framebuffer */
|
||||
overlaps_a(start, end, __start__, __end__);
|
||||
}
|
||||
|
||||
void panic(uint32_t code);
|
||||
void generic_panic(void);
|
||||
void panic_predefined(uint32_t which);
|
||||
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -27,7 +27,8 @@ CFLAGS = \
|
|||
-std=gnu11 \
|
||||
-Werror \
|
||||
-Wall \
|
||||
-fstrict-volatile-bitfields
|
||||
-fstrict-volatile-bitfields \
|
||||
-DFUSEE_STAGE2_SRC
|
||||
|
||||
LDFLAGS = -specs=linker.specs -g $(ARCH)
|
||||
|
||||
|
|
|
@ -7,12 +7,17 @@ MEMORY
|
|||
{
|
||||
NULL : ORIGIN = 0x00000000, LENGTH = 0x1000
|
||||
main : ORIGIN = 0xFFF00000, LENGTH = 0x00100000
|
||||
low_iram : ORIGIN = 0x40003000, LENGTH = 0x4000
|
||||
low_iram : ORIGIN = 0x40003000, LENGTH = 0x8000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
PROVIDE(__start__ = 0xFFF00000);
|
||||
PROVIDE(__start__ = 0xF0000000);
|
||||
PROVIDE(__stack_top__ = 0x40010000);
|
||||
PROVIDE(__stack_bottom__ = 0x4000C000);
|
||||
PROVIDE(__heap_start__ = 0xE0000000);
|
||||
PROVIDE(__heap_end__ = 0xF0000000);
|
||||
|
||||
. = __start__;
|
||||
. = ALIGN(32);
|
||||
|
||||
|
@ -24,18 +29,26 @@ SECTIONS
|
|||
. = ALIGN(4);
|
||||
} >main
|
||||
|
||||
.chainloader :
|
||||
.chainloader_loadable :
|
||||
{
|
||||
. = ALIGN(32);
|
||||
PROVIDE (__chainloader_start = .);
|
||||
PROVIDE (__chainloader_start__ = .);
|
||||
PROVIDE (__chainloader_lma__ = LOADADDR(.chainloader_loadable));
|
||||
KEEP(*(.chainloader.text.start))
|
||||
build/chainloader.o(.text*)
|
||||
build/chainloader.o(.rodata*)
|
||||
build/chainloader.o(.data*)
|
||||
. = ALIGN(8);
|
||||
|
||||
} >low_iram AT>main
|
||||
|
||||
.chainloader_bss :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
PROVIDE (__chainloader_bss_start__ = .);
|
||||
build/chainloader.o(.bss* COMMON)
|
||||
. = ALIGN(8);
|
||||
PROVIDE (__chainloader_end = .);
|
||||
PROVIDE (__chainloader_end__ = .);
|
||||
} >low_iram AT>main
|
||||
|
||||
.text :
|
||||
|
|
|
@ -1,12 +1,34 @@
|
|||
#include "chainloader.h"
|
||||
|
||||
uint8_t __attribute__((used)) g_payload_arg_data[PAYLOAD_ARG_DATA_MAX_SIZE] = {0};
|
||||
char g_chainloader_arg_data[CHAINLOADER_ARG_DATA_MAX_SIZE] = {0};
|
||||
chainloader_entry_t g_chainloader_entries[CHAINLOADER_MAX_ENTRIES] = {0}; /* keep them sorted */
|
||||
size_t g_chainloader_num_entries = 0;
|
||||
uintptr_t g_chainloader_entrypoint = 0;
|
||||
|
||||
#pragma GCC optimize (3)
|
||||
void relocate_and_chainload_main(uintptr_t load_address, uintptr_t src_address, size_t size, int argc) {
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
*(uint8_t *)(load_address + i) = *(uint8_t *)(src_address + i);
|
||||
|
||||
static void *xmemmove(void *dst, const void *src, size_t len)
|
||||
{
|
||||
const uint8_t *src8 = (const uint8_t *)src;
|
||||
uint8_t *dst8 = (uint8_t *)dst;
|
||||
|
||||
if (dst8 < src8) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
dst8[i] = src8[i];
|
||||
}
|
||||
} else if (src8 > dst8) {
|
||||
for (size_t i = len; len > 0; len--)
|
||||
dst8[i - 1] = src8[i - 1];
|
||||
}
|
||||
|
||||
((void (*)(int, void *))load_address)(argc, g_payload_arg_data);
|
||||
return dst;
|
||||
}
|
||||
|
||||
void relocate_and_chainload_main(int argc) {
|
||||
for(size_t i = 0; i < g_chainloader_num_entries; i++) {
|
||||
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
||||
xmemmove((void *)entry->load_address, (const void *)entry->src_address, entry->size);
|
||||
}
|
||||
|
||||
((void (*)(int, void *))g_chainloader_entrypoint)(argc, g_chainloader_arg_data);
|
||||
}
|
||||
|
|
|
@ -4,10 +4,22 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PAYLOAD_ARG_DATA_MAX_SIZE 0x1000
|
||||
#define CHAINLOADER_ARG_DATA_MAX_SIZE 0x6200
|
||||
#define CHAINLOADER_MAX_ENTRIES 128
|
||||
|
||||
extern uint8_t g_payload_arg_data[PAYLOAD_ARG_DATA_MAX_SIZE];
|
||||
typedef struct chainloader_entry_t {
|
||||
uintptr_t load_address;
|
||||
uintptr_t src_address;
|
||||
size_t size;
|
||||
size_t num;
|
||||
} chainloader_entry_t;
|
||||
|
||||
void relocate_and_chainload(uintptr_t load_address, uintptr_t src_address, size_t size, int argc);
|
||||
extern chainloader_entry_t g_chainloader_entries[CHAINLOADER_MAX_ENTRIES]; /* keep them sorted */
|
||||
extern size_t g_chainloader_num_entries;
|
||||
extern uintptr_t g_chainloader_entrypoint;
|
||||
|
||||
extern char g_chainloader_arg_data[CHAINLOADER_ARG_DATA_MAX_SIZE];
|
||||
|
||||
void relocate_and_chainload(int argc);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -28,7 +28,7 @@ void clock_enable_fuse(u32 enable);
|
|||
void display_color_screen(u32 color);
|
||||
|
||||
/*! Init display in full 1280x720 resolution (32bpp, line stride 768, framebuffer size = 1280*768*4 bytes). */
|
||||
u32 *display_init_framebuffer();
|
||||
u32 *display_init_framebuffer(void *address);
|
||||
|
||||
/*! Enable or disable the backlight. Should only be called when the screen is completely set up, to avoid flickering. */
|
||||
void display_enable_backlight(bool on);
|
||||
|
|
|
@ -188,9 +188,9 @@ void display_enable_backlight(bool on) {
|
|||
}
|
||||
|
||||
|
||||
u32 *display_init_framebuffer(void)
|
||||
u32 *display_init_framebuffer(void *address)
|
||||
{
|
||||
u32 *lfb_addr = (u32 *)0xC0000000;
|
||||
u32 *lfb_addr = (u32 *)address;
|
||||
|
||||
//This configures the framebuffer @ 0xC0000000 with a resolution of 1280x720 (line stride 768).
|
||||
exec_cfg((u32 *)DISPLAY_A_BASE, cfg_display_framebuffer, 32);
|
||||
|
|
|
@ -52,7 +52,7 @@ void display_end();
|
|||
void display_color_screen(u32 color);
|
||||
|
||||
/*! Init display in full 1280x720 resolution (32bpp, line stride 768, framebuffer size = 1280*768*4 bytes). */
|
||||
u32 *display_init_framebuffer(void);
|
||||
u32 *display_init_framebuffer(void *address);
|
||||
|
||||
/*! Enable or disable the backlight. Should only be called when the screen is completely set up, to avoid flickering. */
|
||||
void display_enable_backlight(bool on);
|
||||
|
|
130
fusee/fusee-secondary/src/init.c
Normal file
130
fusee/fusee-secondary/src/init.c
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <sys/iosupport.h>
|
||||
#include "utils.h"
|
||||
|
||||
void __libc_init_array(void);
|
||||
void __libc_fini_array(void);
|
||||
|
||||
extern uint8_t __bss_start__[], __bss_end__[];
|
||||
extern uint8_t __heap_start__[], __heap_end__[];
|
||||
|
||||
extern char *fake_heap_start;
|
||||
extern char *fake_heap_end;
|
||||
|
||||
int __program_argc;
|
||||
void **__program_argv;
|
||||
|
||||
void __attribute__((noreturn)) __program_exit(int rc);
|
||||
void __attribute__((noreturn)) (*__program_exit_callback)(int rc) = NULL;
|
||||
|
||||
static void __program_parse_argc_argv(int argc, char *argdata);
|
||||
static void __program_cleanup_argv(void);
|
||||
|
||||
static void __program_init_heap(void) {
|
||||
fake_heap_start = (char*)__heap_start__;
|
||||
fake_heap_end = (char*)__heap_end__;
|
||||
}
|
||||
|
||||
static void __program_init_newlib_hooks(void) {
|
||||
__syscalls.exit = __program_exit; /* For exit, etc. */
|
||||
}
|
||||
|
||||
static void __program_move_additional_sections(void) {
|
||||
#if defined(FUSEE_STAGE1_SRC) || defined(FUSEE_STAGE2_SRC)
|
||||
extern uint8_t __chainloader_lma__[], __chainloader_start__[], __chainloader_bss_start__[], __chainloader_end__[];
|
||||
memcpy(__chainloader_start__, __chainloader_lma__, __chainloader_bss_start__ - __chainloader_start__);
|
||||
memset(__chainloader_bss_start__, 0, __chainloader_end__ - __chainloader_bss_start__);
|
||||
#endif
|
||||
}
|
||||
|
||||
void __program_init(int argc, char *argdata) {
|
||||
/* Zero-fill the .bss section */
|
||||
memset(__bss_start__, 0, __bss_end__ - __bss_start__);
|
||||
|
||||
__program_init_heap();
|
||||
__program_init_newlib_hooks();
|
||||
__program_parse_argc_argv(argc, argdata);
|
||||
|
||||
/* Once argv is parsed, we can discard the low IRAM region */
|
||||
__program_move_additional_sections();
|
||||
__libc_init_array();
|
||||
}
|
||||
|
||||
void __program_exit(int rc) {
|
||||
__libc_fini_array();
|
||||
__program_cleanup_argv();
|
||||
if (__program_exit_callback == NULL) {
|
||||
/* Default callback */
|
||||
generic_panic();
|
||||
} else {
|
||||
__program_exit_callback(rc);
|
||||
}
|
||||
for (;;);
|
||||
}
|
||||
|
||||
#ifdef FUSEE_STAGE1_SRC
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
__program_argc = 0;
|
||||
__program_argv = NULL;
|
||||
}
|
||||
#elif defined(FUSEE_STAGE2_SRC)
|
||||
#include "stage2.h"
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
size_t pos = 0, len;
|
||||
|
||||
__program_argc = argc;
|
||||
|
||||
__program_argv = malloc(argc * sizeof(void **));
|
||||
if (__program_argv == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
len = strlen(argdata);
|
||||
__program_argv[0] = malloc(len + 1);
|
||||
if (__program_argv[0] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
strcpy((char *)__program_argv[0], argdata);
|
||||
pos += len + 1;
|
||||
|
||||
__program_argv[1] = malloc(len + 1);
|
||||
if (__program_argv[1] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
memcpy(__program_argv[1], argdata + pos, sizeof(stage2_args_t));
|
||||
}
|
||||
#else
|
||||
static void __program_parse_argc_argv(int argc, char *argdata) {
|
||||
size_t pos = 0, len;
|
||||
|
||||
__program_argc = argc;
|
||||
|
||||
__program_argv = malloc(argc * sizeof(void **));
|
||||
if (__program_argv == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (int i = 0; i < argc; i++) {
|
||||
len = strlen(argdata + pos);
|
||||
__program_argv[i] = malloc(len + 1);
|
||||
if (__program_argv[i] == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
strcpy((char *)__program_argv[i], argdata + pos);
|
||||
pos += len + 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __program_cleanup_argv(void) {
|
||||
#ifndef FUSEE_STAGE1_SRC
|
||||
for (int i = 0; i < __program_argc; i++) {
|
||||
free(__program_argv[i]);
|
||||
__program_argv[i] = NULL;
|
||||
}
|
||||
free(__program_argv);
|
||||
#endif
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include "utils.h"
|
||||
#include "loader.h"
|
||||
#include "sd_utils.h"
|
||||
|
@ -47,10 +50,12 @@ bool validate_load_address(uintptr_t load_addr) {
|
|||
|
||||
void load_list_entry(const char *key) {
|
||||
load_file_t load_file_ctx = {0};
|
||||
struct stat st;
|
||||
size_t size;
|
||||
size_t entry_num;
|
||||
chainloader_entry_t *entry;
|
||||
load_file_ctx.key = key;
|
||||
|
||||
printf("Loading %s\n", key);
|
||||
|
||||
if (ini_parse_string(get_loader_ctx()->bct0, loadlist_entry_ini_handler, &load_file_ctx) < 0) {
|
||||
printf("Error: Failed to parse BCT.ini!\n");
|
||||
generic_panic();
|
||||
|
@ -61,20 +66,30 @@ void load_list_entry(const char *key) {
|
|||
generic_panic();
|
||||
}
|
||||
|
||||
printf("Loading %s from %s to 0x%08x\n", key, load_file_ctx.path, load_file_ctx.load_address);
|
||||
|
||||
if (!validate_load_address(load_file_ctx.load_address)) {
|
||||
printf("Error: Load address 0x%08x is invalid!\n", load_file_ctx.load_address);
|
||||
if (!check_32bit_address_loadable(load_file_ctx.load_address)) {
|
||||
printf("Error: Load address 0x%08x is invalid (%s)!\n", load_file_ctx.load_address, key);
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Read file off of SD. */
|
||||
load_file_ctx.load_size = read_sd_file((void *)load_file_ctx.load_address, LOADER_FILESIZE_MAX, load_file_ctx.path);
|
||||
if (load_file_ctx.load_size == 0) {
|
||||
printf("Error: Failed to read %s!\n", load_file_ctx.path);
|
||||
if (stat(load_file_ctx.path, &st) == -1) {
|
||||
printf("Error: Failed to stat file %s: %s!\n", load_file_ctx.path, strerror(errno));
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
size = (size_t)st.st_size;
|
||||
if (!check_32bit_address_range_loadable(load_file_ctx.load_address, size)) {
|
||||
printf("Error: %s has an invalid load address & size combination!\n", key);
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
entry_num = g_chainloader_num_entries++;
|
||||
entry = &g_chainloader_entries[entry_num];
|
||||
entry->load_address = load_file_ctx.load_address;
|
||||
entry->size = size;
|
||||
entry->num = entry_num;
|
||||
strcpy(get_loader_ctx()->file_paths[entry_num], load_file_ctx.path);
|
||||
get_loader_ctx()->nb_files++;
|
||||
|
||||
/* Check for special keys. */
|
||||
if (strcmp(key, LOADER_PACKAGE2_KEY) == 0) {
|
||||
get_loader_ctx()->package2_loadfile = load_file_ctx;
|
||||
|
@ -136,7 +151,7 @@ static int loadlist_ini_handler(void *user, const char *section, const char *nam
|
|||
} else if (strcmp(name, LOADER_ENTRYPOINT_KEY) == 0) {
|
||||
/* Read in entrypoint as a hex string. */
|
||||
sscanf(value, "%x", &x);
|
||||
loader_ctx->chainload_entrypoint = (entrypoint_t)x;
|
||||
loader_ctx->chainload_entrypoint = x;
|
||||
} else if (strcmp(name, LOADER_CUSTOMSPLASH_KEY) == 0) {
|
||||
strncpy(loader_ctx->custom_splash_path, value, sizeof(loader_ctx->custom_splash_path));
|
||||
} else {
|
||||
|
@ -148,8 +163,52 @@ static int loadlist_ini_handler(void *user, const char *section, const char *nam
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int chainloader_entry_comparator(const void *a, const void *b) {
|
||||
const chainloader_entry_t *e1 = (const chainloader_entry_t *)a;
|
||||
const chainloader_entry_t *e2 = (const chainloader_entry_t *)b;
|
||||
|
||||
return (int)(e1->load_address - e2->load_address);
|
||||
}
|
||||
|
||||
static uintptr_t find_carveout(chainloader_entry_t *entries, size_t num_entries, size_t requested) {
|
||||
for(size_t i = 0; i < num_entries; i++) {
|
||||
uintptr_t lbound = entries[i].load_address + entries[i].size;
|
||||
if (check_32bit_address_range_loadable(lbound, requested)) {
|
||||
if (i == num_entries - 1 || lbound + requested <= entries[i + 1].load_address)
|
||||
return lbound;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void load_payload(const char *bct0) {
|
||||
loader_ctx_t *ctx = get_loader_ctx();
|
||||
bool can_load_in_place = true;
|
||||
|
||||
extern uint8_t __chainloader_start__[], __chainloader_end__[];
|
||||
extern uint8_t __stack_bottom__[], __stack_top__[];
|
||||
extern uint8_t __heap_start__[], __heap_end__[];
|
||||
extern uint8_t __start__[], __end__[];
|
||||
|
||||
static chainloader_entry_t nonallocatable_entries[] = {
|
||||
{.load_address = 0x00000000, .size = 0x40010000}, /* Unknown/Invalid + Low IRAM */
|
||||
{.load_address = 0x40040000, .size = 0x80000000 - 0x40040000}, /* Invalid/MMIO */
|
||||
{.load_address = (uintptr_t)__chainloader_start__, .size = 0},
|
||||
{.load_address = (uintptr_t)__stack_bottom__, .size = 0},
|
||||
{.load_address = (uintptr_t)__heap_start__, .size = 0},
|
||||
{.load_address = (uintptr_t)__start__, .size = 0},
|
||||
{.load_address = 0, .size = 0}, /* Min to max loaded address */
|
||||
};
|
||||
static const size_t num_nonallocatable_entries = sizeof(nonallocatable_entries) / sizeof(nonallocatable_entries[0]);
|
||||
if (nonallocatable_entries[2].size == 0) {
|
||||
/* Initializers aren't computable at load time */
|
||||
nonallocatable_entries[2].size = __chainloader_end__ - __chainloader_start__;
|
||||
nonallocatable_entries[3].size = __stack_top__ - __stack_bottom__;
|
||||
nonallocatable_entries[4].size = __heap_end__ - __heap_start__;
|
||||
nonallocatable_entries[5].size = __end__ - __start__;
|
||||
}
|
||||
|
||||
|
||||
/* Set BCT0 global. */
|
||||
ctx->bct0 = bct0;
|
||||
|
@ -158,4 +217,97 @@ void load_payload(const char *bct0) {
|
|||
printf("Error: Failed to parse BCT.ini!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Sort the entries by ascending load addresses */
|
||||
qsort(g_chainloader_entries, g_chainloader_num_entries, sizeof(chainloader_entry_t), chainloader_entry_comparator);
|
||||
|
||||
/* Check for possible overlap and find the entrypoint, also determine whether we can load in-place */
|
||||
ctx->file_id_of_entrypoint = ctx->nb_files;
|
||||
for (size_t i = 0; i < ctx->nb_files; i++) {
|
||||
if (i != ctx->nb_files - 1 &&
|
||||
overlaps(
|
||||
g_chainloader_entries[i].load_address,
|
||||
g_chainloader_entries[i].load_address + g_chainloader_entries[i].size,
|
||||
g_chainloader_entries[i + 1].load_address,
|
||||
g_chainloader_entries[i + 1].load_address + g_chainloader_entries[i + 1].size
|
||||
)
|
||||
) {
|
||||
printf(
|
||||
"Error: Loading ranges for files %s and %s overlap!\n",
|
||||
ctx->file_paths[g_chainloader_entries[i].num],
|
||||
ctx->file_paths[g_chainloader_entries[i + 1].num]
|
||||
);
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if(ctx->chainload_entrypoint >= g_chainloader_entries[i].load_address &&
|
||||
ctx->chainload_entrypoint < g_chainloader_entries[i].load_address + g_chainloader_entries[i].size) {
|
||||
ctx->file_id_of_entrypoint = g_chainloader_entries[i].num;
|
||||
}
|
||||
|
||||
can_load_in_place = can_load_in_place &&
|
||||
check_32bit_address_range_in_program(
|
||||
g_chainloader_entries[i].load_address,
|
||||
g_chainloader_entries[i].load_address + g_chainloader_entries[i].size
|
||||
);
|
||||
}
|
||||
|
||||
if (ctx->chainload_entrypoint != 0 && ctx->file_id_of_entrypoint >= ctx->nb_files) {
|
||||
printf("Error: Entrypoint not in range of any of the files!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (ctx->chainload_entrypoint == 0 && !can_load_in_place) {
|
||||
printf("Error: Files have to be loaded in place when booting Horizon!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (can_load_in_place) {
|
||||
for (size_t i = 0; i < ctx->nb_files; i++) {
|
||||
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
||||
entry->src_address = entry->load_address;
|
||||
if (read_sd_file((void *)entry->src_address, entry->size, ctx->file_paths[entry->num]) != entry->size) {
|
||||
printf("Error: Failed to read file %s: %s!\n", ctx->file_paths[entry->num], strerror(errno));
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t total_size = 0;
|
||||
uintptr_t carveout, min_addr, max_addr, pos;
|
||||
|
||||
for (size_t i = 0; i < ctx->nb_files; i++) {
|
||||
total_size += g_chainloader_entries[i].size;
|
||||
}
|
||||
|
||||
min_addr = g_chainloader_entries[g_chainloader_num_entries - 1].load_address;
|
||||
max_addr = g_chainloader_entries[g_chainloader_num_entries - 1].load_address +
|
||||
g_chainloader_entries[g_chainloader_num_entries - 1].size;
|
||||
|
||||
nonallocatable_entries[num_nonallocatable_entries - 1].load_address = min_addr;
|
||||
nonallocatable_entries[num_nonallocatable_entries - 1].size = max_addr - min_addr;
|
||||
|
||||
/* We need to find a carveout, first outside the loaded range, then inside */
|
||||
qsort(nonallocatable_entries, num_nonallocatable_entries, sizeof(chainloader_entry_t), chainloader_entry_comparator);
|
||||
|
||||
carveout = find_carveout(nonallocatable_entries, num_nonallocatable_entries, total_size);
|
||||
if (carveout == 0) {
|
||||
carveout = find_carveout(g_chainloader_entries, g_chainloader_num_entries, total_size);
|
||||
}
|
||||
if (carveout == 0) {
|
||||
printf("Error: Failed to find a carveout!\n");
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Finally, read the files into the carveout */
|
||||
pos = carveout;
|
||||
for (size_t i = 0; i < ctx->nb_files; i++) {
|
||||
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
||||
entry->src_address = pos;
|
||||
if (read_sd_file((void *)entry->src_address, entry->size, ctx->file_paths[entry->num]) != entry->size) {
|
||||
printf("Error: Failed to read file %s: %s!\n", ctx->file_paths[entry->num], strerror(errno));
|
||||
generic_panic();
|
||||
}
|
||||
pos += entry->size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
#define FUSEE_LOADER_H
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
typedef void (*entrypoint_t)(int argc, void **argv);
|
||||
#include "chainloader.h"
|
||||
|
||||
typedef struct {
|
||||
char path[0x300];
|
||||
char path[0x100];
|
||||
const char *key;
|
||||
uintptr_t load_address;
|
||||
size_t load_size;
|
||||
|
@ -14,12 +13,15 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
const char *bct0;
|
||||
entrypoint_t chainload_entrypoint;
|
||||
uintptr_t chainload_entrypoint;
|
||||
size_t file_id_of_entrypoint;
|
||||
size_t nb_files;
|
||||
load_file_t package2_loadfile;
|
||||
load_file_t exosphere_loadfile;
|
||||
load_file_t tsecfw_loadfile;
|
||||
load_file_t warmboot_loadfile;
|
||||
char custom_splash_path[0x300];
|
||||
char custom_splash_path[0x100];
|
||||
char file_paths[CHAINLOADER_MAX_ENTRIES][0x100];
|
||||
} loader_ctx_t;
|
||||
|
||||
#define LOADER_ENTRYPOINT_KEY "entrypoint"
|
||||
|
@ -32,7 +34,7 @@ typedef struct {
|
|||
#define LOADER_WARMBOOT_KEY "warmboot"
|
||||
|
||||
/* TODO: Should we allow files bigger than 16 MB? */
|
||||
#define LOADER_FILESIZE_MAX 0x01000000
|
||||
//#define LOADER_FILESIZE_MAX 0x01000000
|
||||
|
||||
loader_ctx_t *get_loader_ctx(void);
|
||||
|
||||
|
|
|
@ -1,40 +1,50 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include "utils.h"
|
||||
#include "hwinit.h"
|
||||
#include "loader.h"
|
||||
#include "chainloader.h"
|
||||
#include "stage2.h"
|
||||
#include "nxboot.h"
|
||||
#include "console.h"
|
||||
#include "sd_utils.h"
|
||||
#include "fs_dev.h"
|
||||
#include "display/video_fb.h"
|
||||
|
||||
/* TODO: Add a #define for this size, somewhere. Also, primary can only actually load 0x7000. */
|
||||
static char g_bct0[0x8000];
|
||||
|
||||
static stage2_args_t *g_stage2_args;
|
||||
|
||||
/* Allow for main(int argc, void **argv) signature. */
|
||||
#pragma GCC diagnostic ignored "-Wmain"
|
||||
|
||||
void __init_heap(void) {
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
|
||||
fake_heap_start = (char*)0xF0000000;
|
||||
fake_heap_end = (char*)0xFFF00000;
|
||||
}
|
||||
|
||||
int main(int argc, void **argv) {
|
||||
stage2_args_t args = {0};
|
||||
loader_ctx_t *loader_ctx = get_loader_ctx();
|
||||
void *framebuffer = memalign(0x1000, CONFIG_VIDEO_VISIBLE_ROWS * CONFIG_VIDEO_COLS * CONFIG_VIDEO_PIXEL_SIZE);
|
||||
|
||||
if (argc != STAGE2_ARGC || ((args = *((stage2_args_t *)argv[STAGE2_ARGV_ARGUMENT_STRUCT])).version != 0)) {
|
||||
/* Initialize the display. */
|
||||
display_init();
|
||||
|
||||
if (framebuffer == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Setup console/stdout. */
|
||||
console_resume(args.lfb, args.console_row, args.console_col);
|
||||
/* Initalize the framebuffer and console/stdout */
|
||||
display_init_framebuffer(framebuffer);
|
||||
console_init(framebuffer);
|
||||
|
||||
/* Turn on the backlight after initializing the lfb */
|
||||
/* to avoid flickering. */
|
||||
display_enable_backlight(true);
|
||||
|
||||
if (argc != STAGE2_ARGC) {
|
||||
generic_panic();
|
||||
}
|
||||
g_stage2_args = (stage2_args_t *)argv[STAGE2_ARGV_ARGUMENT_STRUCT];
|
||||
|
||||
if(g_stage2_args->version != 0) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
initialize_sd();
|
||||
if(fsdev_mount_all() == -1) {
|
||||
|
@ -42,29 +52,32 @@ int main(int argc, void **argv) {
|
|||
}
|
||||
fsdev_set_default_device("sdmc");
|
||||
|
||||
/* Copy the BCT0 from unsafe primary memory into our memory. */
|
||||
strncpy(g_bct0, args.bct0, sizeof(g_bct0));
|
||||
|
||||
/* TODO: What other hardware init should we do here? */
|
||||
|
||||
printf(u8"Welcome to Atmosphère Fusée Stage 2!\n");
|
||||
printf("Stage 2 executing from: %s\n", (const char *)argv[STAGE2_ARGV_PROGRAM_PATH]);
|
||||
|
||||
/* This will load all remaining binaries off of the SD. */
|
||||
load_payload(g_bct0);
|
||||
load_payload(g_stage2_args->bct0);
|
||||
|
||||
printf("Loaded payloads!\n");
|
||||
|
||||
/* Unmount everything (this causes all open files to be flushed and closed) */
|
||||
fsdev_unmount_all();
|
||||
|
||||
if (loader_ctx->chainload_entrypoint != NULL) {
|
||||
/* TODO: What do we want to do in terms of argc/argv? */
|
||||
loader_ctx->chainload_entrypoint(0, NULL);
|
||||
/* Deinitialize the framebuffer and display */
|
||||
display_enable_backlight(false);
|
||||
display_end();
|
||||
free(framebuffer);
|
||||
|
||||
if (loader_ctx->chainload_entrypoint != 0) {
|
||||
/* TODO: What else do we want to do in terms of argc/argv? */
|
||||
const char *path = get_loader_ctx()->file_paths[get_loader_ctx()->file_id_of_entrypoint];
|
||||
strcpy(g_chainloader_arg_data, path);
|
||||
relocate_and_chainload(1);
|
||||
} else {
|
||||
/* If we don't have a chainload entrypoint set, we're booting Horizon. */
|
||||
nxboot_main();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
#define STAGE2_ARGV_ARGUMENT_STRUCT 1
|
||||
#define STAGE2_ARGC 2
|
||||
|
||||
#define BCTO_MAX_SIZE 0x6000
|
||||
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
const char *bct0;
|
||||
uint32_t *lfb;
|
||||
uint32_t console_row;
|
||||
uint32_t console_col;
|
||||
char bct0[BCTO_MAX_SIZE];
|
||||
} stage2_args_t;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
.global _start
|
||||
.type _start, %function
|
||||
_start:
|
||||
/* Insert NOPs for convenience (i.e. to use Nintendo's BCTs, for example) */
|
||||
.rept 16
|
||||
nop
|
||||
.endr
|
||||
/* Switch to supervisor mode, mask all interrupts, clear all flags */
|
||||
/* Switch to system mode, mask all interrupts, clear all flags */
|
||||
msr cpsr_cxsf, #0xDF
|
||||
|
||||
/* Relocate ourselves if necessary */
|
||||
|
@ -22,7 +18,7 @@ _start:
|
|||
bne _relocation_loop_end
|
||||
|
||||
ldr r4, =__bss_start__
|
||||
sub r4, r4, r2 /* size >= 32, obviously */
|
||||
sub r4, r4, r2 /* size >= 32, obviously, and we've declared 32-byte-alignment */
|
||||
_relocation_loop:
|
||||
ldmia r3!, {r5-r12}
|
||||
stmia r2!, {r5-r12}
|
||||
|
@ -34,36 +30,25 @@ _start:
|
|||
|
||||
_relocation_loop_end:
|
||||
/* Set the stack pointer */
|
||||
ldr sp, =0x40010000
|
||||
mov fp, #0
|
||||
stmfd sp!, {r0, r1}
|
||||
ldr sp, =__stack_top__
|
||||
mov fp, #0
|
||||
bl __program_init
|
||||
|
||||
/* Clear .bss */
|
||||
ldr r0, =__bss_start__
|
||||
mov r1, #0
|
||||
ldr r2, =__bss_end__
|
||||
sub r2, r2, r0
|
||||
bl memset
|
||||
|
||||
/* Initialize the heap */
|
||||
bl __init_heap
|
||||
|
||||
/* Call global constructors */
|
||||
bl __libc_init_array
|
||||
|
||||
/* Set r0 to r12 to 0 (because why not?) & call main */
|
||||
/* Set r0 to r12 to 0 (for debugging) & call main */
|
||||
.rept 13
|
||||
CLEAR_GPR_REG_ITER
|
||||
.endr
|
||||
ldmfd sp!, {r0, r1}
|
||||
bl main
|
||||
b .
|
||||
ldr r0, =__program_argc
|
||||
ldr r1, =__program_argv
|
||||
ldr lr, =__program_exit
|
||||
b main
|
||||
|
||||
/* No need to include this in normal programs: */
|
||||
.section .chainloader.text.start, "ax", %progbits
|
||||
.arm
|
||||
.align 5
|
||||
.global relocate_and_chainload
|
||||
.type relocate_and_chainload, %function
|
||||
relocate_and_chainload:
|
||||
ldr sp, =0x40010000
|
||||
ldr sp, =__stack_top__
|
||||
b relocate_and_chainload_main
|
||||
|
|
|
@ -41,7 +41,7 @@ ini1_header_t *stratosphere_get_ini1(void) {
|
|||
|
||||
/* Merges some number of INI1s into a single INI1. It's assumed that the INIs are in order of preference. */
|
||||
size_t stratosphere_merge_inis(void *dst, ini1_header_t **inis, unsigned int num_inis) {
|
||||
char sd_path[0x300] = {0};
|
||||
char sd_path[0x100] = {0};
|
||||
/* Validate all ini headers. */
|
||||
for (unsigned int i = 0; i < num_inis; i++) {
|
||||
if (inis[i] == NULL || inis[i]->magic != MAGIC_INI1 || inis[i]->num_processes > INI1_MAX_KIPS) {
|
||||
|
|
|
@ -71,9 +71,39 @@ static inline bool check_32bit_additive_overflow(uint32_t a, uint32_t b) {
|
|||
return __builtin_add_overflow_p(a, b, (uint32_t)0);
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_loadable(uintptr_t addr) {
|
||||
/* FWIW the bootROM forbids loading anything between 0x40000000 and 0x40010000, using it for itself... */
|
||||
return (addr >= 0x40010000u && addr < 0x40040000u) || addr >= 0x80000000u;
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_range_loadable(uintptr_t addr, size_t size) {
|
||||
return
|
||||
__builtin_add_overflow_p(addr, size, (uintptr_t)0) && /* the range doesn't overflow */
|
||||
check_32bit_address_loadable(addr) && check_32bit_address_loadable(addr + size) && /* bounds are valid */
|
||||
!(addr >= 0x40010000u && addr + size >= 0x40040000u) /* the range doesn't cross MMIO */
|
||||
;
|
||||
}
|
||||
|
||||
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
|
||||
static inline bool overlaps_a(const void *as, const void *ae, const void *bs, const void *be) {
|
||||
return overlaps((uint64_t)(uintptr_t)as, (uint64_t)(uintptr_t)ae, (uint64_t)(uintptr_t)bs, (uint64_t)(uintptr_t)be);
|
||||
}
|
||||
|
||||
static inline bool check_32bit_address_range_in_program(uintptr_t addr, size_t size) {
|
||||
extern uint8_t __chainloader_start__[], __chainloader_end__[];
|
||||
extern uint8_t __stack_bottom__[], __stack_top__[];
|
||||
extern uint8_t __heap_start__[], __heap_end__[];
|
||||
extern uint8_t __start__[], __end__[];
|
||||
uint8_t *start = (uint8_t *)addr, *end = start + size;
|
||||
|
||||
return overlaps_a(start, end, __chainloader_start__, __chainloader_end__) ||
|
||||
overlaps_a(start, end, __stack_bottom__, __stack_top__) ||
|
||||
overlaps_a(start, end, __heap_start__, __heap_end__) ||
|
||||
overlaps_a(start, end, __start__, __end__);
|
||||
}
|
||||
|
||||
void panic(uint32_t code);
|
||||
void generic_panic(void);
|
||||
void panic_predefined(uint32_t which);
|
||||
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue