jeudi, août 16, 2007

ARM7 et ARM9 sont sur un bateau ... ARM9 tombe à l'eau. Qui reset?

here: br[here]

I'm trying to make sense of the "execute .nds file" libraries ... Somewhere in an old version of the DSWiki, i found this piece of code (the trick only works with GBamp cartridge, afaik):



REG_IME = IME_DISABLE; // Disable interrupts
WAIT_CR |= (0x8080); // ARM7 has access to GBA cart
*((vu32*)0x027FFFFC) = fileCluster; // Start cluster of NDS to load
*((vu32*)0x027FFE04) = (u32)0xE59FF018; // ldr pc, 0x027FFE24
*((vu32*)0x027FFE24) = (u32)0x027FFE04; // Set ARM9 Loop address
swiSoftReset(); // Reset



which suggests that the software reset will branch the code on 0x27FFE04 at some point, and that the file to be loaded is found at 0x27FFFFC (its first cluster in the FAT flashsystem, at least, which suggests much reverse-engineering of the GBAMP firmware, imho).

J'essaie de comprendre un peu les mécanismes mis en jeu lors du redémarrage de la nintendo DS, et en particulier comment s'y prendre pour lui faire démarrer un fichier .nds précis (lancer le Sprite Editor depuis un programme "menu", par exemple). Une des propositions dans ce sens venait de DSwiki (cf supra), mais ne fonctionne apparemment que pour l'adaptateur GBAmp. Après le reset software, le processeur finit par se placer en 27FFE04, et le mot mémoire @27FFFFC contient le numéro du premier cluster du fichier à démarrer (l'adresse sur le disque, pour ceux qui n'ont pas passé 3 ans à décortiquer le FAT32 ;)

Bien sûr, il est nécessaire d'obtenir la coopération du 2eme processeur, le ARM7 (code ci-dessous), ce qui nous donne à contempler le genre de "magie noire" mise en oeuvre pour synchroniser les deux processeurs de la DS: périodiquement, le ARM7 va inspecter la mémoire (partagée) à l'adresse 27FFE24 (le "pointeur magique"), et si il y trouve l'adresse prévue pour la "boucle Passme", il va déclencher son propre reset.

The trick of course only works if the ARM7 side co-operates, which gives us an idea of how the dual-cpu-with-shared-memory can turn simple things in obscure wizardry:



if (*((vu32*)0x027FFE24) == (u32)0x027FFE04) // Check for ARM9 reset
{
REG_IME = IME_DISABLE; // Disable interrupts
REG_IF = REG_IF; // Acknowledge interrupt
*((vu32*)0x027FFE34) = (u32)0x08000000; // Bootloader start address
swiSoftReset(); // Jump to boot loader
}



In other words, the ARM7 cpu will periodically check whether the "reboot" has been invoked on the ARM9 cpu (in which case, it is trapped in the so-called "passme loop" that consists of a "MAGIC_HERE: jump [MAGIC_POINTER]" instruction where the memory at address MAGIC_POINTER contains the address of the jump instruction, i.e. MAGIC_HERE.

'MAGIC_POINTER (0x27FFE24)' is actually something that is nintendo-firmware related and this is where swiSoftReset() will send us back on the ARM9 cpu. On the ARM7, swiSoftReset() will do the same, but using another magic pointer at 0x27FFE34, which we reprogrammed with the "cartridge entry point" address (0x8000000). Voilà. that should do the trick: ARM9 resets and enters a loop, ARM7 detects it and resets as well. ARM7 then execute the cartridge's initialization as well (which will take care of taking ARM9 out of that loop, hopefully).

Après un petit tour sur "gba&ds tek", il apparaît que le service "softreset" du BIOS de la DS se sert du contenu de 0x027FFE24 (notre "pointeur magique") et de 0x027FFE34 respectivement pour savoir où reprendre le code après la réinitialisation. Pendant que l'ARM9 tourne en rond (le code sur lequel on l'a envoyer lit en permanence le contenu du 'pointeur magique' et s'y rebranche: c'est ça notre "boucle passme" et il suffira à l'ARM7 de modifier le contenu du "pointeur magique" pour débloquer l'ARM9 et de l'envoyer vers le programme de son choix), l'ARM7 relance pour sa part l'exécution vers le point d'entrée de la cartouche en ROM, et l'on devrait se retrouver à nouveau dans la partie du firmware GBAmp qui lance un fichier... sauf qu'il ne s'agira plus de son propre menu, cette fois!

stubs join the game


Now, if we look at the "run .nds file" code in rebootlib or in grizzlyadams' DSchannels, it works a bit differently. The new thing here is the presence of stubs, chunks of code that will help the transition from old software to the new one. The stub will typically take care of filling the RAM with the new program while trying to avoid being destroyed. Some of them simply live on the ARM7 and will thus only support programs that do not use a "custom" loop (basically denying execution of anything that is WiFi-related :-/)

Jetons à présent un oeil au code "executer(char* ndsfilename)" de la rebootlib de lick ou du DSchannels de grizzlyadams. Tout d'abord, ce qui est nouveau ici, ce sont les stubs, des petits bouts de programme qui vont assurer temporairement le chargement entre le programme appelant et le programme appelé. Un stub se charge généralement de préparer l'environnement du nouveau programme et de l'amener en mémoire en prenant soin de ne pas se supprimer lui-même. Dans le plus simple des cas, il s'agira simplement d'un exécutable pour le ARM7 un peu particulier qui se comporte comme le programme-modèle une fois le chargement effectué. L'inconvénient de cette technique, c'est évidemment que les programmes qui ont un "coté 7" spécifique (et notamment faisant appel au WiFi, etc) échoueront lamentablement :-/

In DS channels, grizzlyadams rather use the video memory to store the stub. This of course require special rules for building the stub (as the memory layout of the produced binary has nothing to do with a regular DS environment)

/*
We assume, that VRAM is set up as follows:
- VRAM_A is mapped to 0x6000000 for text screen
- VRAM_B is mapped to 0x6820000 for passed vars
- VRAM_C is mapped to ARM7 WRAM for arm7 stub
- VRAM_D is mapped to 0x6860000 for arm9 stub
*/

(*(vu32*)0x027FFFF8) = 0x06000000; // ARM7 Entry
(*(vu32*)0x027FFE24) = 0x06860000; // ARM9 Entry



Nothing like a "passme loop" here (see specs/ds_arm9vram_crt0.s, the assembly file that contains the initialization code for the stub) the ARM9 prepares the environment and enters "main()". Once the stub has loaded your requested .nds file (optionnally applying DLDI patching), the 'Execute()' function will play hide-and-seek with the ARM7, which is waiting for instructions through the FIFO hardware.

Le stub de DSchannel est un peu particulier en ce sens qu'il va squatter la mémoire vidéo pendant son exécution. Et celle-ci est tellement vaste par rapport à ce qui est nécessaire pour quelques messages texte que cela passera complètement inaperçu pour l'utilisateur. Pourtant, aussi bien le stub7, le stub9 que les arguments qu'ils reçoive (p.ex. le nom du fichier à lancer ;) tiennent dedans ^_^.

C'est le stub9 qui entre en jeu le premier, suite au 'soft reset'. le code d'initialisation (specs/ds_arm9vram_crt0.s) prépare à nouveau les cache, mais ne contient pas l'ombre d'une "boucle passme". Il démarre la fonction main() du stub qui à son tour, va charger l'exécutable NDS en mémoire (ajustant les drivers DLDI au passage, si nécessaire) avant de commencer à piloter l'ARM7 à travers quelques commandes via le FIFO hardware.
Celà signifie donc qu'au moins une commande 'EXEC' (qui ne fait rien de plus que de provoquer un reboot de l'ARM7 vers une adresse indiquée à l'avance par l'ARM9: celle du stub7) avant de faire une petite partie de 'cache-cache' entre les commandes FIFO et les valeurs de retour annoncées par une variable partagée ("synchro" à l'adresse 0x027FFFF8) jusqu'à ce que les deux moitiés de l'exécutable nds soient en place.

Another original thing here is that the original ARM7 code is still running while the ARM9 is already executing its own stub. Things will only change with the 'EXEC' command sent through the FIFO.
/* in stub_main9.c */
if(DebugEnabled) { a7(); iprintf("Booting...\n"); }
FIFO_Send(IPC_CMD_EXEC, 0);

/* in myapp_main7.c */
void IPC_Exec()
{
swiWaitForVBlank();

*(vu16*)(0x04000208) = 0; //REG_IME = IME_DISABLE;
*((vu32*)0x027FFE34) = *((vu32*)0x027FFFF8);

asm("swi 0x00"); //swiSoftReset();
asm("bx lr");
}

The ARM7 stub and the ARM9 stub then start playing hide-and-seek again, exchanging more commands through the FIFO and using a special "synchro" shared variable (0x27FFFF8) to report each other's progression.

1 commentaire:

Arialia a dit…

Merci pour cette explication et ce décryptage de code :)