Pour cet article, nous allons laisser de côté la partie BASIC-80 pour regarder du côté d'un fonctionnement spécifique au VG5000µ. Pas que le principe soit original, il est présent dans de nombreuses machines, mais que les capacités sont diverses suivant les différentes machines.
Les « hooks » (la traduction de « crochet » me semble un peu hasardeuse, une « accroche » me semble un peu meilleur) est un moyen qu'offre le système pour intervenir lors de certaines opérations en augmentant le fonctionnement de la ROM. En y mettant son grain de sel en quelque sorte.
Plus prosaïquement, les « hooks » sont des appels à des adresses précises, en RAM, à des routines qui peuvent être modifiées. Il peut s'agir aussi sur d'autres machines de récupérer dans une variable système une adresse d'indirection. Sur le VG5000µ, toutes les adresses de « hooks » sont fixes.
Les « hooks » sont parfois aussi appelés vecteurs d'indirection. Ou bien tout simplement vecteurs.
L'initialisation des « hooks »
L'initialisation des « hooks » arrive très tôt dans l'initialisation de la machine, juste après la détection de la mémoire disponible.
ld a,$c9
ld hl,inthk
ld b,$1e
hk_ini_lop: ld (hl),a
inc hl
djnz hk_ini_lop
ld a,$c3
ld (inst_lpen),a
ld (inst_disk),a
ld (inst_modem),a
ld hl,no_device
ld ($47f2),hl
ld ($47f5),hl
ld ($47f8),hl
ld hl,resetlang
ld ($47ef),hl
ld (nmihk),a
A
prend la valeur C9
, qui est le code pour l'instruction RET
sur Z80. HL
est initialisé à inthk
, qui est le premier hook d'une plage consécutive. Et B
prend $1e
, la taille de cette plage.
La première étape et de remplir cette plage avec des RET
. Cette plage contient les 10 premiers hooks, que sont les suivants.
- $47D0,
inthk
: interruption masquable - $47D3,
calhk
: vecteurCALL
- $47D6,
sonhk
: vecteur de générateur de son - $47D9,
plyhk
: début de commande PLAY - $47DC,
rsthk
: vecteur pour l'instruction RST utilisateur - $47DF,
prthk
: début de commande PRINT - $47E2,
outhk
: début d'impression de caractère - $47E5,
crdhk
: début de retour chariot - $47E8,
inlhk
: début de lecture d'une ligne - $47EB,
inphk
: début de commande INPUT
Chaque hook fait 3 octets de long, nous verrons pourquoi plus loin. Et pour le moment tous remplis de RET
. Un CALL
à ces adresses ne fait donc rien d'autre que de revenir à l'appelant immédiatement.
A
prend ensuite la valeur C3
, qui, suivi d'une adresse sur deux octets, est un JP
absolu à cette adresse. Les hooks lpnhk
, dskhk
et modhk
sont remplis avec ce jp no_device
, qui est un branchement vers l'erreur indiquant que le périphérique n'est pas géré.
Plus étonnant est la valeur que prend le hook nmihk
, qui est appelé en cas d'interruption non masquable. Cette interruption est appelée lors de l'appui sur la touche Delta
du VG5000µ. La routine resetlang
met le système en anglais au niveau des messages et du clavier puis ressort de l'interruption. Et c'est tout.
À vrai dire, cela ne dure qu'un instant. Juste après, le VG5000µ initialise sa partie graphique puis remplace le hook par une nouvelle valeur :
ld hl,test_reset
ld ($47ef),hl
Cette nouvelle routine test_reset
vérifie si la touche Ctrl
est appuyée. Si ce n'est pas le cas, la routine sort immédiatement. Sinon, un reset à chaud a lieu.
Mise en place d'une routine
« Accrocher » une routine est assez facile, et demande juste quelques précautions. Afin de prendre un premier exemple, je vais accrocher une routine sur l'interruption qui provoque l'affichage sur le VG5000µ.
Rapidement, dans le VG5000µ, le processeur graphique est la cause d'une interruption INT
à chaque rafraîchissement. C'est la seule raison, de base, qui provoque cette interruption.
Lors de l'interruption, le PC
est branché en $0038
et la première instruction y est call inthk
. On a donc une possibilité d'agir lors de l'interruption, avant que le rafraîchissement potentiel de l'écran n'ait lieu (il n'a pas lieu à chaque fois).
Le code nécessaire pour une routine de « hook » est en deux parties. La première se charge de modifier le branchement du « hook » vers notre routine. La seconde est la routine elle-même.
Commençons par la première partie. À noter que pour être propre, il faudrait effectuer un chaînage en faisant appeler à notre propre routine une routine éventuellement déjà installée. Je ne m'en occuperai pas ici.
defc inthk = $47D0 ; Adresse du hook
org $7A00 ; Spécification de l'adresse mémoire d'implémentation
push AF ; Sauvegarde des registres sur la pile
push HL
ld A,$C3 ; Mise en place de la routine sur le HOOK
ld (inthk),A ; le 'JP'
ld HL,int_routine
ld (inthk+1),HL ; et l'adresse
pop HL ; Restauration des registres depuis la pile
pop AF
ret ; Retour au programme appelant
int_routine:
ret
Le programme est directement commenté. Si depuis le BASIC, ce programme est injecté et appelé, alors... il ne se passera pas grand chose de visible, mais en réalité, le RET
de int_routine
sera appelé à chaque interruption.
Une routine plus intéressante
Pour rendre les choses plus intéressantes, voici une routine à installer qui affiche à l'écran une petite barre qui tourne.
defc screen = $4000
int_routine:
push AF ; Sauvegarde de AF
ld A,(IX+$00)
dec A
jp nz, no_display ; Respect du timer de rafraîchissement
push HL ; Sauvegarde de HL
ld A,(count) ; Compteur du caractère à afficher
inc A
cp A, $4 ; S'il est à la dernière position, on boucle
jp nz, display
ld A, $0
display:
ld (count),A ; Mise à jour du compteur
ld HL,cursor ; Récupération du caractère à afficher
add A,L
ld L,A
ld A,(HL)
ld HL,screen + 32 ; Affichage
ld (HL), A
ld (ix+$01),$01 ; Force le ré-affichage
pop HL ; Restauration des registres depuis la pile
no_display:
pop AF
ret
count:
defb 0
cursor:
defb $2F, $60, $5C, $7C ; Les 4 caractères qui forment l'animation
Il faut veiller dans cette routine à bien préserver les registres utilisés, nous sommes ici en pleine interruption, nous n'avons aucune connaissance du contexte.
La lecture de la variable système à travers le registre IX
permet de savoir si le système va considérer un rafraîchissement de l'affichage. La commande DISPLAY
du BASIC influe directement sur une valeur de compteur qui, lorsqu'il arrive à zéro, provoque éventuellement un affichage avant de revenir à sa valeur spécifiée.
L'affichage n'est cependant pas systématique. Le bit 0
de la variable système qui suit le compteur doit être à 1
pour que l'affichage est vraiment lieu. Et l'on trouve parsemé dans la ROM des ld (ix+$01),$01
qui signifient qu'un rafraîchissement de l'écran est demandé. Ce que je fais à la fin de la routine.
La partie après PUSH HL
est un bête compteur cyclique de 0 à 3, qui est ensuite utilisé pour pointer dans un tableau de 4 caractères provoquant l'animation.
L'adresse écran est calculé en dur et on y place directement le caractère. Puis le contexte est restauré.
Une dernière note pour comprendre l'affichage du VG5000µ dans sa ROM BASIC. La ROM maintient en $4000
une image logique du contenu de l'écran, et un ensemble de variables systèmes, pointées en tout temps par IX
. Lorsqu'un rafraîchissement à lieu, tout ce contenu est envoyé vers le processeur graphique pour une grande (et lente !) mise à jour.
Il se peut donc que des modifications aient lieu en mémoire graphique côté processeur qui ne soient pas répercutées tout de suite vers le processeur graphique.
Mais tout ceci est une autre histoire qui n'est pas le sujet ici.
Le test
Voici un programme BASIC qui va monter la routine en mémoire. Suivi d'un RUN
pour l'exécuter puis du CALL
pour lancer le hook.
10 CLEAR 50,&"79FF"
20 S=&"7A00"
30 READ A$
40 IF A$="FIN" THEN END
50 A$="&"+CHR$(34)+A$+CHR$(34):A=VAL(A$)
60 POKE S,A
70 S=S+1
80 GOTO 30
300 DATA F5,C5,D5,E5,3E,C3,32,D0,47,21,14,7A,22,D1,47,E1,D1,C1,F1,C9
310 DATA F5,DD,7E,0,3D,C2,3A,7A,E5,3A,3C,7A,3C,FE,4,C2,28,7A,3E,0
320 DATA 32,3C,7A,21,3D,7A,85,6F,7E,21,20,40,77,DD,CB,1,C6,E1,F1,C9
330 DATA 0,2F,60,5C,7C
1000 DATA FIN
RUN
CALL &"7A00"
Pour éviter d'avoir à tout rentrer au clavier, voici le fichier .k7. À charger avec CLOAD
, suivi d'un RUN
et du CALL &"7A00"
.
La suite
Nous avons vu un exemple de mise en place de routine sur un « hook » du VG5000µ. Dans les articles suivant, j'irai examiner les autres « hook » et dans quels contextes ils sont appelés.