Idée de projet - add-in pour debug
Posté le 28/05/2018 20:01
salut;
Je sais pas si vous programmez sur Casio mais si c’est le cas vous devez savoir à quel point programmer dessus c’est pratique: on programme un jeu, on le compile, on vérifie sur l’Émulateur si tout fonctionne, si tout fonctionne on le transfère sur la calto puis on peut transporter son jeu de partout.
Problématique
Si vous programmez un gros jeu il est possible que des petits bugs apparaissent APRÈS avoir compilé qui font planter la Casio.
Et pourtant le jeu fonctionnait sur l’Émulateur sauf que l’Émulateur ne sera jamais comme la console.
Et c’est là que ça devient méga chiant parce que, modifier un octet, compiler, connecter la calto et voir que ça ne fonctionne pas...on vient de perdre 5 minutes de notre vie à brasser de l’aire.
De plus si vos faites des jeux en multijoueur on ne peut pas vérifier via l’Émulateur, il faut donc tout se taper et c’est...long, trop long.
Idée
C’est pourquoi une idée m’est venue alors que j’écrivais la librairie “mySerail.h”.
Pourquoi ne pas créer un add-in qui récupère les données via le port USB puis exécute le jeu à la fin du transfère ?
Pour résumer éviter d’aller dans “LINK”, et attendre que la calto se connecte à l’ordi, attendre que l’add-in soit transféré.
En gros avoir un add-in de debug qui nous permet de ne pas avoir à toucher à la console pour tester ses programmes.
Théorie
Pour ça il nous faut une connexion directe entre la Casio et le PC...bon ya un port USB donc c’est rapide.
L’add-in se met directement en ‘Receive’ (elle attend un fichier).
Ensuite on vérifie s'il y a assez de place dans la RAM.
Si ya assez de place, on met tout le programme dans la RAM, puis on l’exécute.
(c’est tellement simple à dire^^)
Pourquoi la RAM ?
C’est un add-in de debug je vous rappelle donc on s’en fout que le programme soit stocké dans la ROM.
De plus la RAM est rapide à écrire, bien plus rapide que le ROM.
Possible ?
Oui MAIS seulement des petits programmes car la ‘vrai’ RAM disponible sur SH4 est de 12 ko (d’après
la doc de
Lephenixoir) OU 64 ko (d’après le
wiki).
Donc bon...
Oublions 2 secondes la taille de la RAM et le fait qu’il y en est pas autant sur les SH3 que sur le SH4.
1.On sait communiquer via le port USB (exemple :
P7 de
Cakeisalie5), du moins on sait communiquer PC->Casio par contre il me semble pas avoir entendu parler de syscall permettant de jouer avec le port USB (j’ai chercher mais je n'ai rien comprit
).
2.On sait exécuter du code Assembleur depuis la RAM (#sysCall) (
gint fait ça merveilleusement bien aussi <3).
3.On sait faire des interfaces par dégueux.
Donc oui c’est possible !
Es-ce que j’ai commencer a faire un truc ?
Non, déjà parce que j'ai pas beaucoup de temps en ce moment et j’ai déjà plusieurs projets à finir.
C’est juste pour signaler que c’est théoriquement possible donc s'il y a des personnes intéressées par ce projet...bah allez-y ça pourrait être sympa et il y a sûrement beaucoup de choses à apprendre
.
Citer : Posté le 30/01/2019 14:24 | #
Non absolument pas, c'est pour ça que je me dis qu'il n'est pas impossible que le crash vienne de mon crt0.c.
Ben au contraire ; ton crt0.c est toujours exécuté dans les premiers dizièmes de seconde au démarrage de ton application. Si couper les interruptions suffit pour garder ton programme en vie plusieurs secondes, alors ton problème ne vient définitivement pas de l'exécution de ton crt0.c.
show__...() montre juste l'addres actuel de la VBR (et la mienne) mais n'est pas changée a ce moment la. Ell est changer dans __set_vbr().
Faudrait voir à pas me prendre pour un vieux con non plus.
{
puts("stop interrupt...");
__block_interrupt();
puts("DONE\nDump INTC:\n");
dump_intc();
puts("reset interrupt...");
stop_interrupt();
puts("DONE\n");
interrupt_counter = 0;
puts("set new VBR...");
__set_vbr((void*)&glados_vbr);
/* ... */
}
À la limite tu le mets où tu veux, mais pas dans crt0.c. En principe, crt0.c fait partie de la lib standard. Je déroge personnellement à cette règle pour éviter d'avoir à utiliser des constructeurs pour initialiser gint et ses drivers, et parce que c'est plus pratique, mais en aucun cas ça ne peut être ton programme principal. Est-il nécessaire, pour écrire un add-in avec Glados, d'écrire soit-même le crt0.c dans son fichier principal ?
Mais merci pour l'info il me semblait bien que cette tèchnique était bancal, je regarderais la méthode de SimLo je suis curieux de savoir comment il fait
Certes, mais je sens déjà que pour toi il n'est pas toujours clair (ou du moins clair et juste) quels registres sont SH3 et quels registres sont SH4 ; ça me paraît être une priorité. La détection n'est peut-être pas fondamentale mais je crois que cette méthode est fausse dans certains cas donc ça ne va pas t'arranger.
SimLo a juste localisé un registre (le registre de contrôle du port L, si je me souviens bien) qui est au même endroit sur les deux MPU mais qui a certains bits réservés sous SH3 qui sont utilisés sous SH4. Il écrit donc des 1 dedans, puis relit le registre pour voir si les 1 sont restés (auquel cas c'est un SH4), ou s'ils ont été remplacés par des 0, comme il est de coutume avec les bits réservés (auquel cas c'est un SH3). Il y a une autre différence de ce type pour distinguer les deux types de SH3 -SH7355 et SH7337- et à partir des SH4 il existe des registres de versions (PRR et PVR) pour avoir les détails de façon propre.
Un peu hacky comme tu peux le voir, mais toujours plus fiable que la version de l'OS, si tu veux mon avis.
Il n'y a pas de mauvaise façon de faire, personnellement ça me paraît plus facile de l'allouer statiquement pour éviter les erreurs sur les pointeurs, les appels de syscall et le tas mais c'est un peu de la paranoïa.
Elle restaure certains registres, notamment la VBR, c'est pour ça qu'elle plante quand je l'utilise (j'ai oublié de bloquer les interruptions avant de tout restaurer :/ ).
Non. C'est une implémentation de longjmp(). Ça restaure l'état des registres tel qu'il était au moment où __save_stuff() (aussi dite setjmp()) a été appelée, et ça reprend l'exécution à cet endroit. Autrement dit, quand tu appelles _restore_stuff(), ça remonte dans le temps (en termes d'état des registres et de la pile seulement) et ça recommence l'exécution juste après le moment où tu as appelé __save_stuff().
Bon courage !
Citer : Posté le 30/01/2019 21:13 | # | Fichier joint
Faudrait voir à pas me prendre pour un vieux con non plus.
Ha ! Pardon, je me suis trompé de fonction (dans mes souvenirs j'avais appelé mes fonctions show_vbr() et show_vram())
(j'ai écrit un peu vite ce matin et j'ai tendance a oublier / confondre les choses :/ ).
À la limite tu le mets où tu veux, mais pas dans crt0.c. En principe, crt0.c fait partie de la lib standard. Je déroge personnellement à cette règle pour éviter d'avoir à utiliser des constructeurs pour initialiser gint et ses drivers, et parce que c'est plus pratique, mais en aucun cas ça ne peut être ton programme principal. Est-il nécessaire, pour écrire un add-in avec Glados, d'écrire soit-même le crt0.c dans son fichier principal ?
En theorie non, les addin pour Glados n'auront pas a utiliser mon crt0.c ce qui me semble logique...Du coup je comprend pourquoi le mettre dans crt0.c est une grosse erreur
D'un autre côté Glados n'a pas pour vocation d'arriver a maturiter (même si j'aimerai fortement) j'ai trop peu de notion / connaissance pour y arriver (même si j'essaie d'apprendre au maximum de mes erreurs).
Non. C'est une implémentation de longjmp(). Ça restaure l'état des registres tel qu'il était au moment où __save_stuff() (aussi dite setjmp()) a été appelée, et ça reprend l'exécution à cet endroit. Autrement dit, quand tu appelles _restore_stuff(), ça remonte dans le temps (en termes d'état des registres et de la pile seulement) et ça recommence l'exécution juste après le moment où tu as appelé __save_stuff().
Logique puisque je remets SPC dans PC, mais du coup je me demande comment ne pas faire une boucle infinie car __save_stuff() est appelé avant le main() >_<
Autre question, c'est comme ça que fonctionne exit() ?
J'ai réussi à trouver le problème !!
Il est super étrange par contre...
Comme je savais plus quoi faire, j'essayais d'afficher les address d'IPR* pour verifier, et bien figure-toi qu'elles n'étaient pas du tout alignées...
Et le problème vient de... unsigned short (et unit16_t) qui font 4 octets au lieu de 2 (ce n'est pas une blague) 0___o
Ma structure doit être sacrément cassé pour qu'un truc comme ça arrive x) (ou alors une erreur toute simple que je n'ai pas vue :/ ).
Je te mets les sources en fichier joint
Citer : Posté le 30/01/2019 21:30 | #
C'est tant mieux si on peut se passer de ton crt0.c mais tu as quand même envie de simplifier la vie de l'utilisateur !
♫ Je n'ai rien entendu ♫
Wtf ? Le saut dans setjmp() est fait par l'intermédiaire de pr, qui est rechargé dans pc par l'instruction rts. Ça n'a rien à voir avec la gestion des interruptions !
Si un vrai système joli, non. Sur un vrai système joli, exit() demande au kernel d'arrêter le processus (ce qu'il fait).
Attention le terme "aligné" a un sens précis (la plus grande puissance de 2 qui divise l'adresse), d'après ta description de l'erreur elles étaient au mauvais endroit mais, si je calcule bien, elles étaient parfaitement alignée sur une adresse multiple de 4 (voire 8)...
Ma structure doit être sacrément cassé pour qu'un truc comme ça arrive x) (ou alors une erreur toute simple que je n'ai pas vue :/ ).
Packe ta structure et on verra s'ils font toujours 4 octets !
Hint : quand tu packes, la structure peut perdre son alignment, il est donc très important de le spécifier à nouveau :
La macro de gint qui fait ça s'appelle GPACKED(x) et tu es obligé de spécifier l'alignement... pour éviter de l'oublier. J'ai eu ce bug en vrai et ça m'a pris une semaine pour le trouver. x)
Citer : Posté le 02/02/2019 18:09 | #
Packe ta structure et on verra s'ils font toujours 4 octets !
Effectivement, après quelque test j'arrive à "compacter" mes structure / union comme je voulais et mes soucis ont disparu
J'ai écrit un "mini-driver" pour le TMU. J'ai le strict minimum: déclencher un timer, le mettre en pause, lui assigner une fonction qu'il executera une fois l'interruption effectuer, récupérer des infos (TCNT, TCOR, TCR et TSTR) et une fonction init pour tout initialiser (et sauvegarder) le comptenu des registres.
Mais je me retrouve encore bloqué au même endroit qu'il y a quelques jours.
J'aimerais commencer le gestionnaire d'interruption mais je plante alors que pour l'instant je veux juste boucler dans mon handler. (histoire d'afficher INTEVT, TEA, et EXPEVT. (même si, pour le coup TEA et EXPEVT ne servent à rien ici (?))).
Voilà comment je fait pour 1 timer:
je clear le flag UNF. (pour être sûr)
je mets le flag UNIE à 1 (pour indiquer que je veux les interruptions).
je mets une valeur dans TCNT et TCOR. (pour avoir un peu de temps avant l'interruption).
je mets le flag STRX correspondant au timer. (ici je teste le timer 0 donc STR0 (qui demande à "démarrer" le timer0)).
je mets le flag IPRA.TMU0_0 à 7 (je sais absolument pas quoi mettre pour la priorité).
Dès que le timer arrive à 0 je plante, donc l'interruption a bien lieu mais j'ai du mal à comprendre pourquoi je crash... faut-il clear les flags UNF et INTEVT avant de boucler dans mon handler ?
Citer : Posté le 02/02/2019 18:36 | #
Très bonne approche. C'est bien INTEVT qui compte (SH4), ou INTEVT2 (SH3).
Pour l'instant mets ce que tu veux, après la question c'est des choses du genre « est-ce que le moteur de gris peut se permettre de se faire interrompre par le clavier ? » (la réponse est non)
Je ne pense pas que tu peux écrire dans INTEVT. Nettoyer UNF ce n'est nécessaire que pour accepter l'interruption, à la fin de son traitement (sinon elle revient immédiatement et à l'infini).
Non, je pense plutôt que c'est ton code qui plante (?). On peut le voir ? Juste le gestionnaire d'interruptions.
Citer : Posté le 02/02/2019 18:46 | #
Pour l'instant mets ce que tu veux, après la question c'est des choses du genre « est-ce que le moteur de gris peut se permettre de se faire interrompre par le clavier ? » (la réponse est non)
ça se voit tant que ça ? c'est une perte de synchro où juste trop de bande parasite a l'écran ?
Non, je pense plutôt que c'est ton code qui plante (?). On peut le voir ? Juste le gestionnaire d'interruptions.
/* Exeption handler */
__attribute__((section(".glados.exept"), interrupt_handler))
void exeption_handler(void)
{
while (1);
}
/* TLB miss */
__attribute__((section(".glados.tlb"), interrupt_handler))
void tlb_handler(void)
{
while (1);
}
/* Interrupt handler */
__attribute__((section(".glados.interupt"), interrupt_handler))
void interrupt_handler(void)
{
while (1);
}
Voila... xD
Citer : Posté le 02/02/2019 18:51 | #
Ben il se peut que le gris soit plus instable parce que l'interruption n'est pas parfaitement régulière. Donc tu veux l'éviter au maximum parce que c'est déjà assez dur d'avoir des paramètres corrects. Donc priorité plus élevée qu'à peu près tout le reste.
Effectivement, vu comme ça... il est probable que ton gestionnaire d'interruptions ne soit pas chargé à la bonne adresse !
Citer : Posté le 06/02/2019 13:29 | #
Effectivement, vu comme ça... il est probable que ton gestionnaire d'interruptions ne soit pas chargé à la bonne adresse !
A vrai dire rien n'était chargé en fait... Mon crt0.c ne dumpais rien du tout, ce qui explique pourquoi je crashais à chaque interruption
Mais maintenant problème résolut
En ce moment je m'attaque à la gestion du clavier, j'ai beaucoup de problèmes bizarres quand j'essaie de lire le KEYSC, par exemple ce code:
static char keyscan(void)
{
volatile uint16_t *keysc = (void*)0xa44b0000;
uint16_t shift;
int cursor;
int row;
row = -1;
while(++row < __KEYSC_ROW){ //6
cursor = 0;
shift = __KEYSC_COLUMN_START; // 0x4000
while (shift){
if (keysc[row] & shift){
while (keysc[row] & shift);
return (matrix[row][cursor]);
}
shift >>= 1;
cursor++;
}
}
Me fait boucler indéfiniment dans le while (keysc[row] & shift); et si j'enlève cette boucle, le bit mis à 1 par l'appui d'une touche ne se reset jamais et le clavier est bloqué. Je tiens à signaler que les interruptions sont (tous) désactivées et quand bien même, il y a juste une boucle (avec des logs) dans mes handlers, donc impossible que cela vienne d'une interruption non gérée.
Ce qui est étrange c'est quand je fais ce code:
volatile uint16_t *KEYSC = (void*)0xa44b0000;
while (1){
__clear_vram(glados->vram);
my_put_hexa(0, 0 * __ASCII_HEIGHT, KEYSC[0], 0);
my_put_hexa(0, 1 * __ASCII_HEIGHT, KEYSC[1], 0);
my_put_hexa(0, 2 * __ASCII_HEIGHT, KEYSC[2], 0);
my_put_hexa(0, 3 * __ASCII_HEIGHT, KEYSC[3], 0);
my_put_hexa(0, 4 * __ASCII_HEIGHT, KEYSC[4], 0);
my_put_hexa(0, 5 * __ASCII_HEIGHT, KEYSC[5], 0);
__display_vram(glados->vram);
}
Tout fonctionne comme il le faut, mais si j'omets 1 line (par exemple my_put_hexa(0, 1 * __ASCII_HEIGHT, KEYSC[1], 0);) le KEYSC ne fonctionnera plus... Il faut donc tout vérifier sinon le KEYSC se bloque (ou attend quelque chose)
j'avoue être bloqué depuis pas mal d'heure dessus sans comprendre d'où viens le problème >__<
Citer : Posté le 06/02/2019 14:23 | #
Bienvenue dans le monde des choses non triviales et non documentées. Je te préviens, tu fais joujou avec l'un des deux trucs que j'ai rencontrés sans comprendre entièrement, l'autre étant les ETMU...
Ce genre de comportement peut naître d'une subtilité de hardware. La première fois que j'ai essayé de lire le KEYSC, à la façon dont tu présentes, de façon régulière depuis un timer, j'avais un problème similaire. Les touches n'étaient jamais relâchées.
Si tu veux tout savoir, je n'en sais pas plus que ce qui est résumé ici : http://www.casiopeia.net/forum/viewtopic.php?f=25&t=7329
Pour la lecture partielle je ne sais pas, par contre la fréquence d'accès peut jouer des tours. Il faut lire assez souvent sinon ça ne marche pas. La seule autre référence que j'ai c'est le code de gint, que j'ai poussé à travers le maximum de tests et qui tient encore...
Citer : Posté le 13/02/2019 13:35 | # | Fichier joint
Pour la lecture partielle je ne sais pas, par contre la fréquence d'accès peut jouer des tours.
La lecture partielle plante à tous les coups, ce qui rend incomprehensible la fiabilité de key_down() (EDIT: key_down() ne marche pas du tout de mon côté depuis que j'ai changé la VBR. Donc peut-être que l'interruption be0 est utile pour la gestion du KEYSC (?))
Ce genre de comportement peut naître d'une subtilité de hardware. La première fois que j'ai essayé de lire le KEYSC, à la façon dont tu présentes, de façon régulière depuis un timer, j'avais un problème similaire. Les touches n'étaient jamais relâchées.
Ce qui est bizarre c'est que j'ai réussi à le faire fonctionner mais le programme est super instable. Donc je vais me tourner du côté d'un timer* et lire très souvent le KEYSC. (au risque de ralentir l'exécution du programme :/)
*(ou utiliser la RTC histoire de pas prendre 1 timer juste pour le clavier (mais je n'y crois pas trop)).
À part ça j'ai commencé à déassembler sérieusement le syscall 0x117 et pour l'instant je n'ai rien d'intéressant a part le reset des registres IPR* / ICR1 et le setup du Bus space controler (je n'ai aucune idée de ce que c'est) mais récemment je suis tombé sur un bout de code qui met à 0 l'addresse 0xa44b000c jusqu'à 0xa44b001a (8 registres 16bits) et avec de la chance ces addresses permettent de paramétrer le KEYSC (et permettrai de comprendre comment le clavier fonctionne ) mais je n'en sais pas plus pour l'instant. (Je compte faire des tests une fois que j'aurais réussi à gérer les interruptions.)
D'ailleurs en parlant d'interruption, j'ai deux problèmes que je n'arrive pas à résoudre.
Pour l'instant je m'occupe juste des timers, le souci c'est que j'ai une boucle infinie dans mon handler.
Je fais comme ça:
extern void (*timer_callback[])(void);
void timer_interrupt(uint32_t intevt_code)
{
volatile uint32_t *tcr; //tcr est un registre 16bits et non 32 x)
int i;
i = -1;
while (timer_code[++i] != intevt_code);
tcr = (void*)&(TMU.TIMER[0].TCR.WORD); //patch pas bô (voir plus bas :/ )
my_printf("timer find: %d\n", i);
if (timer_callback[i] != NULL)
timer_callback[i]();
*tcr = 0x0024;
}
Donc, j'exécute la function de callback (s'il y en a une) puis je clear les flags UNF.
Mais je boucle à l'infini dans mon handler (j'ai même essayé de mettre *tcr a 0 mais rien ne fonctionne)...
Je n'arrive pas à comprendre ce que j'ai oublié
EDIT: Je viens de comprendre mon problème, j'avais mis uint32_t *tcr alors que c'est un registre 16 bits. Voilà, problème résolut, tout fonctionne x)
Mon second problème est surement tout bête mais je bloque aussi.
J'ai une structure qui contient TSTR ainsi que chaque structure des timers. Seulement voilà, toutes les adresses sont bonnes et TSTR fonctionne. Mais impossible d'utiliser les timers (je peux ni lire, ni écrire). Par contre, si je copie l'adresse de chaque registres dans d'autres variables tout fonctionne:
void test_callback(void)
{
puts("test\n");
}
void test_tmu(void)
{
volatile uint32_t *tcor;
volatile uint32_t *tcnt;
volatile uint16_t *tcr;
INTC.IPRA.TMU0_0 = 7;
tcor = &(TMU.TIMER[0].TCOR);
tcnt = &(TMU.TIMER[0].TCNT);
tcr = &(TMU.TIMER[0].TCR.WORD);
*tcor = 0x00020000;
*tcnt = 0x00020000;
*tcr = 0x0024;
timer_callback[0] = &test_callback;
TMU.TSTR = 0x01;
// my_printf("timer0: %#x\n", (void*)&(TMU.TIMER[0]));
// my_printf("timer0.TCOR: %#x\n", (void*)&(TMU.TIMER[0].TCOR));
// my_printf("timer0.TCNT: %#x\n", (void*)&(TMU.TIMER[0].TCNT));
// my_printf("timer0.TCR: %#x\n", (void*)&(TMU.TIMER[0].TCR));
while (1)
my_printf("%#x\n", *tcnt);
}
La part contre je sais vraiment pas quoi faire pour fixer ça
(je mets la structure en fichier joint)
Citer : Posté le 13/02/2019 17:27 | #
La lecture partielle plante à tous les coups, ce qui rend incomprehensible la fiabilité de key_down() (EDIT: key_down() ne marche pas du tout de mon côté depuis que j'ai changé la VBR. Donc peut-être que l'interruption be0 est utile pour la gestion du KEYSC (?))
Très curieux. Je ne pense pas que l'interruption soit nécessaire car gint la bloque et tout se passe bien.
Ça marche très bien avec la RTC, j'ai hésité plusieurs fois à l'utiliser. Je l'ai fait, puis défait. Actuellement j'ai 9 timers hardware implémentés sur SH4 donc j'ai décidé de laisser la RTC tranquille
Bus State Controller, je pense. C'est ce qui contrôle le mapping des adresses physiques vers la ROM et la RAM, principalement. Tu n'as pas grand-chose à y trouver, c'est plutôt un terrain de jeu pour FTune.
J'attends avec impatience de voir ça !
Peux-tu me montrer la version qui fonctionne ? Comment est défini TMU ? Et TMU.TIMER[0] ?
Citer : Posté le 13/02/2019 19:06 | #
Peux-tu me montrer la version qui fonctionne ? Comment est défini TMU ? Et TMU.TIMER[0] ?
La version du code qui fonctionne est celui que j'ai mis en exemple sinon ma structure est définie comme ça:
typedef struct timer_s {
uint32_t TCOR;
uint32_t TCNT;
union {
uint16_t WORD;
struct {
unsigned : 7;
unsigned UNF : 1;
unsigned : 2;
unsigned UNIE : 1;
unsigned : 2;
unsigned TPSC : 3;
} __attribute__((packed));
} TCR;
} __attribute((packed, aligned(4))) timer_t;
struct tmu_s
{
uint8_t TSTR;
uint8_t gaps[3];
volatile timer_t TIMER[__TIMER_NB];
} __attribute((packed));
#define TMU (*(volatile struct tmu_s*)0xa4490004)
J'attends avec impatience de voir ça !
J'ai mis un poste sur Casiopeia, en gros j'ai trouvé 8~9 registres qui sont initialisés avec le syscall 0x117.
D'ailleurs je voulais te demander: les addresses qui commence par 0xa405**** représente quoi ? car le syscall en fait souvent référence et je trouve rien dans la doc
Citer : Posté le 13/02/2019 22:41 | #
Pense à aligner ta structure :
{
uint8_t TSTR;
uint8_t gaps[3];
volatile timer_t TIMER[__TIMER_NB];
} __attribute((packed, aligned(4)));
Je doute que ce soit le problème car l'adresse est littérale, mais ça pourrait te jouer des tours.
Désolé du reste, j'ai été un peu vite parce que j'ai pas pensé que tu pouvais le définir comme ça. J'avoue que je ne vois pas immédiatement le problème. N'hésite pas à minimiser ta fonction et à regarder le code assembleur.
Bravo ! Je répondrai là-bas.
Fais une recherche dans la doc du SH7724, tu trouveras tout de suite (indice : PFC).
Citer : Posté le 22/02/2019 00:24 | #
Après réflexion j'aimerais tourner le projet vers la création d'un kernel UNIX-like pour sh4 (et un jour peut-être sh3).
Je sais qu'il me manque beaucoup de connaissances pour faire un tel projet mais je veux juste comprendre, essayer, échouer et recommencer pour progresser.
Pour l'instant je n'ai pas grand-chose:
- un driver écran.
- un driver pour le TMU.
- un "driver" pour le KEYSC.
- un driver pour la RTC.
- un gestionnaire d'interruptions (seulement pour les timers et la rtc).
- un shell. (vraiment crade pour l'instant)
- quelques builtins (`exit` et `date`) (et d'autre trucs pour debug rapidement).
J'allais me lancer dans le développement d'un File System mais je me suis dit qu'un peu de re-structuration / nettoyage du code devait s'imposer.
J'aimerais avoir ton aide sur certain point, notamment en thermes de "convention" et de "modularité" du code.
Pour l'instant le projet de divise en 5 parties:
- le bootstrap: la partie qui contient le crt0.c. (je suis pas sur du therme "bootstrap", Kristaba disait ça aussi mais lui il initialisait ses différentes stacks ainsi que son noyau donc je ne suis pas sur du therme )
- le kernel (noyau ?): gestion des APIs (timer, rtc, clavier, écran) (pour l'instant)(je ne suis pas sur du therme API et kernel dans ce contexte ).
- le shell: exécute des builtins. (pour l'instant).
- les gestionnaires: interruptions, excéptions, tlb.
- la libc: stdio, stdlib, string et unistd (juste pour write).
Donc en gros j'ai un dossier src qui contient les "modules" (bootstrap, ect...) qui eux, ont leurs propres modules et leur propre Makefile. Je ne sais pas si en therme d'organisation général c'est bancal ou non pour ce genre de gros projet (?)
Un des gros problèmes que j'ai en ce moment c'est le crt0.c.
Je ne sais pas quoi mettre dedans et surtout c'est instable au possible.
Pour l'instant il fait ça:
1) il met à 0 la section `.bss` (qui contient tous les `statics` (? je veux juste être sure d'avoir bien compris)).
2) il "dump" la section `.data` ainsi que `.glados` (qui est la partie qui contient mes handlers).
3) il fait un `setjump()` (donc il garde certains registres en mémoire).
4) il "save" tous les registres que je change par la suite.
5) il bloque les interruptions. (`BL` est mis à 1).
6) il met à 0 les registre `IPR* / IMR* / ICR*` ainsi que les registre du `TMU` et de la `RTC`.
7) il change la `VBR`.
8) il active toutes les interruptions dont j'ai besoin.
9) il "débloque" les interruptions (`BL` est mis à 0).
10) il appelle le `main()`
11) il remet les "registres d'interruptions" comme ils étaient avant. (IPR*, ICR*, etc...).
12) il exécute `longjump()`. (il restaure tous les registers "save" par `setjump()`).
13) il `return()` ce que le `main()` lui a retourné.
Pour moi le crt0.c ne doit pas faire tout ça, il doit juste s'occuper des sections et appeler le main() (?)
Il me semble que tu m'à parler de constructeur et destructeur j'aimerai bien savoir ce que cela signifie pour essayer d'y mettre en place (ça réglera surement mon problème expliqué plus bas ).
Autre question: faut-il que je mette toutes les fonctions appelés par le crt0.c dans la même section que lui (à savoir .entry.*) ? (pour que ses fonctions ne soit pas chargées en RAM ? mais d'un autre côté c'est pas ce qu'il y a de mieux d'exécuter tout en ROM non ?).
Là où est mon problème c'est que je ne peux pas "activer" les interruptions (étape 5 jusqu'à étape 9) dans le crt0.c car je plante à tous les coups. La seule solution trouvé c'est de mettre cette partie dans le main() >_<
Dans le futur je pense refaire mes "syscalls" (write, etc...) en utilisant l'instruction TRAPA pour que le noyaux (kernel ? (je dis surement des énormités, mais je n'ai aucune idée des termes techniques à employer )) sois bien séparé du shell (et pour plus tard, assurer la sécurité du noyau). Mais avant ça il me faut absolument un File System en RAM pour pouvoir implementer les premiers "vrai" syscalls (à savoir: open, read, write).
J'aimerais vraiment avoir ton avis sur la structure d'un tel projet car tu as bien plus de connaissances que moi et j'ai envie de le faire "proprement".
PS: quand je dis "kernel" c'est très large (et je ne suis pas sure à 100% de la signification exacte de ce therme), je ne veux aucun cas paraître prétentieux . Je veux juste apprendre
Citer : Posté le 22/02/2019 09:26 | #
Après réflexion j'aimerais tourner le projet vers la création d'un kernel UNIX-like pour sh4 (et un jour peut-être sh3).
Félicitations !
Question obligatoire : tu connais FiXOS ?
- un driver pour le TMU.
- un "driver" pour le KEYSC.
- un driver pour la RTC.
- un gestionnaire d'interruptions (seulement pour les timers et la rtc).
- un shell. (vraiment crade pour l'instant)
- quelques builtins (`exit` et `date`) (et d'autre trucs pour debug rapidement).
Tu veux que je te dise, il n'y a pas beaucoup plus dans gint. Pour être précis, j'ai ce que tu dis là (sauf le shell), plus...
* Un début de driver pour l'écran de la Graph 90.
* Un driver pour les ETMU (Extra TMU, les timers ajoutés par Casio) - le plus difficile de tous !
* Un driver pour le CPG.
* Une interface un peu modulaire pour les interruptions et les drivers...
* Mes gros modules de dessin rapide, bopti et topti.
Le terme "bootstrap" désigne le moment où un procédé s'alimente lui-même. Par exemple, si j'écris en C un compilateur pour un nouveau langage que j'apelle le Lephe, puis que j'écris un nouveau compilateur Lephe en Lephe, alors le langage sera bootstrappé car il sera compilé par lui-même. De la même façon, si ton kernel se charge lui-même dans la mémoire, alors il se bootstrappe. Le terme est juste.
Le noyau (kernel) désigne tout ce qui n'est pas dans l'espace utilisateur. Autrement dit, les interruptions, les drivers, l'ordonnanceur, le chargeur de fichiers exécutables, le gestionnaire mémoire, les communication inter-processus, le système de fichiers, les appels système, tout ça fait partie du noyau. (Ça c'est pour un noyau monolithique, si tu veux implémenter un micronoyau POSIX, regarde le Hurd).
- les gestionnaires: interruptions, excéptions, tlb.
- la libc: stdio, stdlib, string et unistd (juste pour write).
Ça c'est pas mal. Les gestionnaires d'interruptions font clairement partie du noyau mais les deux autres non.
Tu as définitivement envie d'avoir des sous-dossiers dans kernel, mais non... c'est bien ! Éventuellement mets bootstrap dans kernel car ça en fait partie aussi.
Je te conseille vraiment de compiler ton noyau, ta bibliothèque standard et ton shell indépendamment. Tu dois pouvoir faire 3 projets différents avec ; c'est encore mieux si les headers de ton noyau et ta bibliothèque standard sont installés dans un dossier bien défini (comme celui du cross-compilateur, où j'installe personnellement les en-têtes de gint et la bilbiothèque, et où newlib arrive...).
Je ne sais pas quoi mettre dedans et surtout c'est instable au possible.
Le chargement des sections, c'est bien là qu'il faut le faire. Pour ton information, la section .bss (bootstrap stack) contient en gros toutes les variables initialisées à zéro. Voilà, rien de plus. Les variables globales/statiques/etc initialisées à autre chose sont dans la section .data.
Le coup du setjmp(), c'est quelque chose que je faisais à une époque pour pouvoir implémenter exit(), mais c'est définitivement pas nécessaire. Je te conseille de t'en défaire au plus vite. Dans un vrai système, exit() est un appel système que le kernel va recevoir. Le kernel peut alors décider d'arrêter le processus en le déchargeant de la mémoire.
L'initialisation des interruptions, je l'ai mise personnellement dans le code d'initialisation de gint, mais je l'appelle depuis crt0.c. La raison est que crt0.c exécute aussi les constructeurs de l'application et je veux absolument que gint soit chargé avant d'en arriver là.
Donc c'est pas mal.
Ça dépend. Dans une application userspace normale comme tu peux en écrire des dizaines pour ton ordinateur, crt0 est fourni par la bilbiothèque standard et consiste essentiellement en rien (il n'existe même pas toujours) car le chargeur d'exécutables du noyau va mapper tout seul le fichier en mémoire et charger les sections.
Mais tu n'est pas encore en train de compiler des applications pour ton noyau (sauf ton shell), tu es en train de compiler ton noyau. La façon dont le noyau est chargé n'est pas la même que pour une application. Peut-être que le mieux est de renommer ce fichier pour ne pas faire croire qu'il s'agit d'un crt0. Le mien s'appelle core/start.c.
Ce sont des fonctions que tu peux créer dans un programme C, par exemple sous GCC avec __attribute__((constructor)) et qui est exécutée avant ou après le main. Je te laisse avec quelques liens pour que tu puisses attaquer le sujet...
* Using the GNU Compiler Collection (GCC): Common Function Attributes
* Calling Global Constructors - OSDev Wiki
Attention au deuxième lien, l'ABI SuperH utilise le modèle .ctors/.dtors donc il n'y a qu'une petite partie qui est pertinente. Mais tu peux tout lire pour avoir une meilleure idée de comment les éléments interagissent. De façon générale, tu peux lire ceci :
* How kernel, compiler and C library work together - OSDev Wiki
Non, le tout c'est que la fonction d'entrée soit tout au début. Tu peux mettre les autres fonctions de crt0.c dans la même section pour des raisons de localité, mais sur le processeur qu'on utilise il n'y a pas grand-chose à y gagner. Je le fais par idéalisme religieux, mais essentiellement on s'en fout.
Ça c'est du debuggage, il faut bien checher !
Oui, très bien ! C'est exactement ça !
trapa sert à déclencher une interruption pour revenir au mode noyau, c'est donc l'instruction idéale (et conçue) pour implémenter des syscalls. Cela dit tu peux implémenter des syscalls intéressants avoir d'avoir le fs, par exemple exit(). N'oublie pas qu'il te faut un ordonnanceur et tout ça.
C'est le mot anglais pour noyau. Rien de prétentieux donc.
C'est un plan d'envergure, mais c'est passionnant ! Il faut absolument que tu fouilles dans tout, tout, ce qui a été fait sur FiXOS si tu te lances là-dedans.
Citer : Posté le 22/02/2019 21:03 | #
Question obligatoire : tu connais FiXOS ?
Bien sur; j'ai lu tous les messages de son topic et j'ai le code source de FIXOS. J'ai déjà commencé à zyeuter (de loin) certaine partie mais je compte m'y mettre sérieusement bientôt
tu veux que je te dise, il n'y a pas beaucoup plus dans gint. Pour être précis, j'ai ce que tu dis là (sauf le shell), plus...
* Un début de driver pour l'écran de la Graph 90.
* Un driver pour les ETMU (Extra TMU, les timers ajoutés par Casio) - le plus difficile de tous !
* Un driver pour le CPG.
* Une interface un peu modulaire pour les interruptions et les drivers...
* Mes gros modules de dessin rapide, bopti et topti.
Il faudrait que je regarde comment tu a codé l'interface (interruptions / drivers) pour me faire une idée de "comment codé de façons modulables".
Les ETMU m'intéressent (et j'aimerai bien savoir comment tu les as trouvé (quelle partie tu as désassemblé) pour que je les trouve aussi) mais pour l'instant j'aimerai avoir une base de projet propre et un début de documentation avant de commencer quoi que ce soit à ce niveaux-là.
Ce sont des fonctions que tu peux créer dans un programme C, par exemple sous GCC avec __attribute__((constructor)) et qui est exécutée avant ou après le main. Je te laisse avec quelques liens pour que tu puisses attaquer le sujet...
Du coup j'ai regardé un peu et je pense faire comme ça: créer deux sections: .ctor et .dtor puis utiliser __attribute__((section(".ctor"), constructor)) et __attribute__((section(".dtor"), destructor)) pour initialiser le noyau (?) Ou alors le simple fait de mettre __attribute__((constructor)) vas créer la section .ctor (idem pour les destructors) ?
Le noyau (kernel) désigne tout ce qui n'est pas dans l'espace utilisateur. Autrement dit, les interruptions, les drivers, l'ordonnanceur, le chargeur de fichiers exécutables, le gestionnaire mémoire, les communication inter-processus, le système de fichiers, les appels système, tout ça fait partie du noyau. (Ça c'est pour un noyau monolithique, si tu veux implémenter un micronoyau POSIX, regarde le Hurd).
Le noyaux monolithique me semble plus simple a implementer et plus approprié. Par contre le Hurd est completement plus modulable et maintenable. Si j'ai bien comprit, ce n'est pas un gros block contenant tout, mais de plusiseurs "serveurs" qui ont une tache bien definie, indépendant les uns les autres et ils communiquent entre eux par le protocole RPC (?)
Déjà je ne vois pas comment l'implementer sans avoir d'ordonnanceur (car je suppose que chaque serveurs utilise un processus differents (?)) mais, en plus, au vue de la faible capasité des caltos il est preferable d'avoir un noyau monolithique (je pense)).
trapa sert à déclencher une interruption pour revenir au mode noyau, c'est donc l'instruction idéale (et conçue) pour implémenter des syscalls. Cela dit tu peux implémenter des syscalls intéressants avoir d'avoir le fs, par exemple exit(). N'oublie pas qu'il te faut un ordonnanceur et tout ça.
Si j'ai bien compris, l'ordonnanceur permet de "paralléliser" les differents processus ? (je n'ai pas encore une base de projet suffi suffisamment solide pour me lancer dans une telle partie maintenant mais je sens que ça va vite devenir une priorité (et j'ai hâte de commencer)).
Grâce à toi j'arrive à y voir un peu plus clair au niveau de l'organisation:
* Refaire le crt0.c en utilisant les constructeurs / destructors (et je le renomme au passage).
* J'update mes handlers (exception et TLB) pour savoir d'où viennent les erreurs (lecture, écriture, etc...).
* J'implémente un FS (pour avoir un shell "propre" (implementation des tty) et me débarrasser de ma global glados_t).
* J'implémente les builtins: cd, ls, mkdir, exit, touch (et date maintenant qu'il est là).
* Je mets en place les syscalls avec l'instruction trapa.
* J'implémente un loader de fichier ELF. (je vais surement devoir écrire un driver pour lire l'EEPROM et ça me fait super flipper de toucher à cette partie du hardware).
* Je me penche sur le "multi-processus". (qui, je dois l'avouer, me fait peur lui aussi)
* Après on verra...
Le problème c'est que je ne sais pas quels noyaux je compte implementer. J'hésite entre l'Hurd et le monolithique (à voir si l'Hurd vaut le coup sur ce type de machine (en therme de performance et d'utilité)).
Citer : Posté le 22/02/2019 22:07 | #
Essentiellement j'ai séparé l'espace à VBR+0x600 en blocs de 0x20 octets. L'idée est que comme chaque interruption a un code multiple de 0x20, pour traiter l'interruption je saute à VBR+0x600+le code.
En pratique le premier bloc est chargé de récupérer le code de l'interruption est de faire le saut, donc il y a un décalage ; et comme toutes les interruptions ont un code supérieur ou égal à 0x400 j'élimine aussi ce vide. Le résultat doit être un truc comme VBR+0x220+le code.
Chaque interruption a donc un bloc qui lui est assigné. Bon, 0x20 octets ça suffit pour faire des choses triviales comme appeler un callback puis effacer le flag d'interruption mais c'est tout. Parfois tu veux faire des choses plus complexes, par exemple dans le gestionnaire timer j'arrête le timer si le callback renvoie une valeur non nulle. Donc je déborde sur plusieurs blocs adjacents (TMU0, TMU1, TMU2...) et parfois je remplis même des blocs inutilisés pour stocker plus de données. Mais l'idée reste la même - l'espace est divisé en blocs et chaque bloc est employé par une interruption.
La modularité vient du fait que tu peux appeler une fonction de gint pour installer un bloc sur un code choisi et ainsi traiter tes propres interruptions. Tu en as envie si tu ajoutes un driver à un moment, par exemple. À une époque j'avais un système beaucoup plus haut niveau tout écrit en C où les interruptions étaient dispatchées avec un tableau au lieu d'un saut, et les arguments chargés dynamiquement... pour être honnête ça ne servait à rien car le jour où quelqu'un ajoute une interruption il saura aller chercher ces arguments, et ça ralentissait le traitement des interruptions. Donc j'ai laissé tomber.
En termes de drivers, c'est plus joli. Tu commences par définir tes fonctions dans un fichier, puis tu déclares une structure de driver que tu exposes à gint avec la macro GINT_DECLARE_DRIVER(), comme ceci :
.name = "T6K11",
.init = NULL,
.ctx_size = sizeof(ctx_t),
.sys_ctx = &sys_ctx,
.ctx_save = ctx_save,
.ctx_restore = ctx_restore,
};
GINT_DECLARE_DRIVER(5, drv_t6k11);
En interne gint déplace la structure dans une section spéciale qui est parcoure au démarrage pour initialiser les drivers. Le premier argument de la macro est le niveau du driver; il y en a 7, chargés par ordre croissant. Cela permet d'avoir quelques dépendances entre les drivers. Par exemple, le driver du clavier ne peut être lancé que si le timer est prêt, et le timer a besoin du CPG pour connaître la fréquence des horloges. Du coup, ils ont des niveaux respectifs de 4, 2 et 1, comme ça je suis sûr qu'ils sont toujours chargés dans le bon ordre.
Je suis assez content de mon système de drivers, c'est l'un des trucs jolis de gint.
Pour la partie documentation je peux te répondre, à l'origine ça vient de SimLo :
* /simlo/chm/v20/fxCG20_timer.htm (bible.planet-casio.com)
En gros c'est les mêmes que les timers normaux sauf que TCR fait que 8 bits, ils ont un TSTR chacun, et ils comptent à une fréquence fixe de 32768 Hz (pas de prescaler donc). Ça c'est la théorie.
Car en pratique ils sont horribles à gérer. Rien ne se passe comme avec les autres. J'ai bataillé bien un mois pour réussir à effacer les flags d'interruptions, parce que par exemple si TCOR=0 le flag d'interruption se met automatiquement à 1 même si TSTR=0. J'ai eu des exemples où j'écris une valeur et si je relis ensuite, elle est fausse. Et dans la même veine, si tu écris TCNT=0, une interruption se produit donc immédiatement TCNT est rechargé à TCOR, par contre comme le timer ne compte pas il ne bouge plus. Donc tu écris 0 et tu obtiens TCOR ; c'est un énorme casse-tête.
J'ai fini par documenter tout ça, mais l'interprétation qui est dans ce fichier n'est plus à jour.
* /lephenixnoir/custom-timers.html (bible.planet-casio.com)
Bref, c'est une sage décision que de remettre les ETMU à plus tard.
Tu dois juste mettre __attribute__((constructor)) et GCC va automatiquement ajouter dans la section .ctors un pointeur sur la fonction. Attention, ce n'est pas le code de la fonction, c'est juste un pointeur. Tu verras que constructor peut prendre en paramètre une priorité. Dans ce cas, GCC va s'arranger pour que la section .ctors soit triée par priorité.
Tout ce que tu as à faire est donc :
* Mettre __attribute__((constructor))
* Dans ton linker script, mettre des symboles autour de .ctors et .dtors pour savoir où elles commencent et s'arrêtent (comme pour les autres sections)
* Au chargement du noyau, récupérer les symboles qui pointent donc vers un tableau de fonctions et appeler dans l'ordre toute les fonctions du tableau
* À la fin, pareil avec les destructeurs.
Il faut bien que tu différencies les constructeurs de ton noyau et ceux de tes processus. Clairement, il y a deux choses très différentes :
* Quand ton noyau boote, il doit exécuter ses propres constructeurs
* Quand je demande à ton noyau de lancer un processus, il doit lire le fichier ELF, le charger dans la mémoire, exécuter les constructeurs et lancer main()
Tu as donc deux crt0 finalement : un dans ton noyau, qui sert à utiliser ton noyau. Et un dans ta lib standard, qui sert à compiler les applications qui tourneront sur ton noyau. Et là le second crt0 peut appeler les constructeurs des applications que tu vas lancer, comme ton shell. J'espère qu'on est bien d'accord sur la distinction entre les deux.
C'est l'idée générale des micronoyaux : le noyau ne contient que l'ordonnanceur et la communication inter-processus, le reste est implémenté par des services. Pour le Hurd, j'admets ne pas savoir si c'est du RPC en particulier, le tout est de savoir que ça communique. Évidemment il faut que les communications soient rapides pour que ça se passe bien et c'est la raison pour laquelles les micronoyaux ne sont pas encore très répandus : ils sont moins bons en perfs'.
Exactement, il te faut un ordonnancceur et des processus, et de toute façon c'est plus compliqué. Monolithique est une décision sage.
L'ordonnanceur décide quel processus s'exécute. Quand tu as un seul coeur comme sur la calculatrice, tu n'as pas le choix, tu ne peux lancer qu'une tâche à la fois. Le rôle de l'ordonnanceur est d'attribuer à chaque processus des tranches de temps pour que tous aient leur tour, et éventuellement de gérer différentes formes de priorités pour rafinner la répartition du temps.
Oui, attention en principe le TTY est géré par le noyau mais son exploitant, le shell, est un processus. Pour l'instant tu peux décider (t'as pas vraiment le choix) de lancer le shell tout de suite mais essaie vraiment de séparer au maximum le code du shell de celui du noyau. C'est-à-dire que :
* Tu minimises les endroits où le shell appelle le noyau (futurs appels système). Note : pour le dessin, les fonctions de dessin seront plutôt en userspace, tu peux considérer que tu as un accès libre à la VRAM (comme un framebuffer mappé en mémoire, c'est pas la seule solution mais c'en est une bonne).
* Tu sépares le code du shell et du noyau dans ton arborescence.
* Si possibles, tu sépares les Makefile et tu compiles les deux indépendamment.
C'est pas la peine, la ROM est mappée en mémoire, si tu vas à 0x80000000 ou à 0xa0000000, sur 4 Mo, tu as toute la ROM accessible en lecture.
Attention toutefois ! Pour l'écriture, c'est plus subtil et tu as raison de flipper. FiXOS faisait son fs en RAM, je pense que c'est une bonne idée. Tu as 256k de RAM libre, je pense que c'est pas mal de faire les écritures ici. N'aie pas peur de retomber sur Bfile pour éviter de te planter.
Dans la même lignée, pour l'instant ne touche pas au MMU.
Le reste de ta TODO list est très bien.
Si tu as un doute, pars sur le monolithique.
Citer : Posté le 01/03/2019 14:15 | # | Fichier joint
J'ai "légèrement" avancé sur le projet, mais je me heurte à un problème étrange. Avant d'expliquer de quoi il s'agit j'aimerais vous tenir au courant de mes avancés
* Le noyau boot via des contructors et redonne la main à Casio via des destructors.
* La partie shell et totalement indépendante du noyau et utilise l'instruction trapa pour demander des ressources (ex: write()).
* J'ai supprimé définitivement glados_t et me suis promis de jamais refaire une connerie pareilles. >_<
* J'ai réglé quelques problèmes de clavier (pour getline()).
* write() gère maintenant la sortie d'erreur et la sortie standard (stdout, stderr).
(D'ailleurs stderr affiche le texte en inversant la couleur du texte (je trouve ça stylé)).
* J'ai mis au propre toutes mes structures.
* J'ai fixé (et sécurisé) quelques bugs de mes libs. (string.h, stdio.h, etc...).
* J'ai renommé (et mis au propre) crt0.c. (qui se nomme start.c maintenant).
J'allais m'atteler à la création d'un FS sauf qu'un erreur a fait son apparition.
J'utilise printf() dans mes constructors (pour afficher des lignes de debug) et si j'ai le malheur d'utiliser printf() autre part, la cato plante. (en fait pour être précis dès que je mets un prinf() après le changement de VBR tout plante....mais si met le changement de VBR à un autre endroit le prinf() va planter autre part, ou fonctionner (le bug m'est arrivé plusieur fois et a disparut soudainment pour aucune raison) >_<).
Du coup je me suis dit que ça venait (encore une fois) de mon start.c et devinais quoi ? Au moment où je touche au code, impossible de le refaire fonctionner.
Ça fait 3 jours que je ne comprends pas du tout pourquoi il ne veut pas, j'ai beau remettre le code comme il était avant que je le touche, ça plante lors du dump de la section .data et.glados. Je dois avouer être assez énervé contre moi-même et ne plus savoir quoi faire...
REBOOT : [EXIT]
INITIALIZE: [EXE]
ADDRESS(R)
TARGET=00304A8A
PC =88023F8C
TARGET est bon (voir la memory_map en fichier joint) mais PC n'a rien à faire à cette addresse non ? Pourtant je lui envoie absolument pas ça... >_<
Est-ce que tu aurais une idée d'où peuvent venir mes soucis ?
start.c
#include <stdint.h>
extern int main(void);
extern uint32_t bbss;
extern uint32_t ebss;
extern uint32_t bdata_rom;
extern uint32_t bdata_ram;
extern uint32_t edata_ram;
extern uint32_t bglados_rom;
extern uint32_t bglados_ram;
extern uint32_t eglados_ram;
extern uint32_t bctors;
extern uint32_t ectors;
extern uint32_t bdtors;
extern uint32_t edtors;
__attribute__((section(".pretext.init")))
void section_dump(uint32_t *bsection_ram, uint32_t *bsection_rom,
uint32_t *esection_ram)
{
while (bsection_ram < esection_ram)
*bsection_ram++ = *bsection_rom++;
}
__attribute__((section(".pretext.init")))
void section_reset(uint32_t *bsection, uint32_t *esection)
{
while (bsection < esection)
*bsection++ = 0;
}
__attribute__((section(".pretext.init")))
void section_execute(void (**function)(void), void (**end)(void))
{
while(function < end)
(*(*function++))();
}
__attribute__((section(".pretext.entry")))
int bootstrap(void)
{
int exit;
section_reset(&bbss, &ebss);
section_dump(&bdata_ram, &bdata_rom, &edata_ram); /* ça plante a ce niveau-là*/
section_dump(&bglados_ram, &bglados_rom, &eglados_ram);
section_execute((void (**)(void))&bctors, (void (**)(void))&ectors);
exit = main();
section_execute((void (**)(void))&bdtors, (void (**)(void))&edtors);
return (exit);
}
glados.ld
OUTPUT_ARCH(sh3)
ENTRY(_bootstrap)
MEMORY
{
rom : o = 0x00300200, l = 512k
ram : o = 0x08100000, l = 8k
my_ram : o = 0x8800d000, l = 12k
}
SECTIONS
{
.text : ALIGN(4) {
*(.pretext.entry)
*(.pretext.init)
/* Constructor, destructor */
_bctors = . ;
*(SORT_BY_INIT_PRIORITY(.ctors.*))
_ectors = . ;
_bdtors = . ;
*(SORT_BY_INIT_PRIORITY(.dtors.*))
_edtors = . ;
/* setjump, longjump */
*(.text.utils)
/* main(), ... */
*(.text)
} > rom
.rodata : ALIGN(4) {
*(.rodata)
_erodata = . ;
} > rom
.bss : ALIGN(4) {
_bbss = . ;
*(.bss)
*(COMMON)
_ebss = . ;
} > ram
.data : AT(_erodata + SIZEOF(.bss)) ALIGN(4) {
_bdata_rom = LOADADDR(.data) ;
_bdata_ram = . ;
*(.data)
_edata_ram = . ;
} > ram
/*
Handlers part.
Due to the SH3 / SH4 VBR system, there are 3 vectors offset
called by the processor:
- vbr + 0x100 -> general exeption.
- vbr + 0x400 -> tlb miss.
- vbr + 0x600 -> interrupt.
*/
.glados : AT(_erodata + SIZEOF(.bss) + SIZEOF(.data)) ALIGN(4) {
. = ALIGN(0x100) ;
_bglados_rom = LOADADDR(.glados) ;
_bglados_ram = . ;
_glados_vbr = . ;
. = _glados_vbr + 0x100 ;
*(.glados.exeption) ;
. = _glados_vbr + 0x400 ;
*(.glados.tlb) ;
. = _glados_vbr + 0x600 ;
*(.glados.interrupt) ;
. = ALIGN(4) ;
_eglados_ram = . ;
} > my_ram
.eh_frame : AT (_erodata + SIZEOF(.bss) + SIZEOF(.data) + SIZEOF(.glados)) ALIGN(4) {
*(.eh_frame)
*(.eh_frame.*)
}
}
Tu as donc deux crt0 finalement : un dans ton noyau, qui sert à utiliser ton noyau. Et un dans ta lib standard, qui sert à compiler les applications qui tourneront sur ton noyau. Et là le second crt0 peut appeler les constructeurs des applications que tu vas lancer, comme ton shell. J'espère qu'on est bien d'accord sur la distinction entre les deux.
Je comptais partir sur quelque chose comme ça. Mais pour l'instant je ne suis pas encore au stade de gérer différents processus (même si j'ai des bouts d'idée écrits à droite à gauche).
Oui, attention en principe le TTY est géré par le noyau mais son exploitant, le shell, est un processus. Pour l'instant tu peux décider (t'as pas vraiment le choix) de lancer le shell tout de suite mais essaie vraiment de séparer au maximum le code du shell de celui du noyau.
Le TTY est mis en place et fonctionne très bien (même si pour l'instant ce n'est pas un fichier). Il gère stdout et stderr. La sortie d'erreur affiche le texte en inversant les couleurs (ça permet de facilement savoir quel message est une erreur). Pour stdin, je l'ai mis sur papier, il faut juste que j'arrive à régler ce problème du mémoire qui m'empêche d'avancer >_<
C'est pas la peine, la ROM est mappée en mémoire, si tu vas à 0x80000000 ou à 0xa0000000, sur 4 Mo, tu as toute la ROM accessible en lecture.
Oui m'enfin il faut que je comprenne comment le FS de Casio fonctionne pour savoir ou et comment lire les données :/
Mais c'est vrai que j'avais oublié qu'on pouvait lire facilement la ROM
Je vais essayer de mettre en place une interface comme la tienne pour mes drivers / interruptions mais pour l'instant je suis bloqué à la case départ
Citer : Posté le 01/03/2019 14:31 | #
Mais... mais... mais... putain c'est pas mal ça :o
Facile ton problème, regarde dans ta carte mémoire :
Tadaaa, c'est l'adresse qui plante. Et c'est le début de .data. Et oh, elle est pas alignée à 4 octets...
Ma conclusion : en modifiant du code tu changeais par hasard complet la taille de .text (d'un multiple de 4 à un multiple de 4 plus 2), ce qui déplaçait la zone de RAM.
Citer : Posté le 01/03/2019 19:16 | # | Fichier joint
Tadaaa, c'est l'adresse qui plante. Et c'est le début de .data. Et oh, elle est pas alignée à 4 octets...
Mais...ça m'apprendra à chercher trop loin >_<
Merci mon start.c fonctionne <3
Maintenant il me reste un dernier problème: printf().
Je n'ai plus aucun souci avec les chaines de caractère et appeler vfprintf() ne plante plus...mais il crash un peu plus loin part contre. Si vfprintf() essaie d'appeler vfprintf_print_arg() ça plante peut importe ce qu'elle contient. (actuellement il n'y a qu'un while(1);).
Cette fois-ci je ne me suis pas fait avoir et j'ai vérifié l'alignement de la fonction: 0x303b64 (c'est bon à ce niveau-là ). Du coup je ne sais pas ou regarder pour débuger
D'ailleur il y a la fonction ___udivsi3 qui n'est pas alignée (0x304f3e) et je ne vois pas comment faire etant donnée qu'elle viens de libgcc.a. (Ça m'étonnerait que ça vienne de là mais on sait jamais...)
Désolé de te demander autant d'aide en si peu de temps :/
(memory_map en fichier joint).
Citer : Posté le 01/03/2019 20:56 | #
N'oublie pas que le code doit être aligné à deux octets (la taille d'une instruction). En pratique, le code est toujours aligné, c'est rarement un problème.
Je crois pas avoir les sources de ces fonctions à part l'archive du 30 Janvier, qui ne semble pas trop correspondre.
0x304f3e c'est aligné pour du code...