Cartouche PCjr QuickSilver

Introduction

La cartouche QuickSilver vendue par PC Enterprises pour le PCjr permet d'accélérer le démarrage de la machine, d'où son nom. Voici les trois choses que cette cartouche accomplit selon le manuel:

  • Test de mémoire plus rapide (moins exhaustif).
  • Pas de signal sonore durant les tests au démarrage, à moins qu'une erreur survienne. (Durant le signal sonore l'ordinateur ne fait rien, alors cela sauve un peu de temps aussi)
  • Correctif de compatibilité pour l'interruption clavier Int 9 (modification pour que son comportement soit comme sur un PC)
Une telle cartouche n'est absolument pas une nécessité, sauf peut-être pour le correctif int9, quoi qu'un petit TSR pourrait sans doute s'occuper de ce dernier. Toutefois, accélérer le démarrage peut être très pratique si vous devez éteindre et allumer votre PCjr à répétition et que le temps passé à attendre que le test de mémoire finisse s'accumule... Par exemple, lorsque en développement vous changez l'EPROM sur une cartouche de test.

Je crois avoir lu quelque part que la cartouche QuickSilver originale ne remplaçait que des portions du BIOS, probablement en se basant sur les signaux d'adresse pour prendre le contrôle lorsque l'ordinateur accède à des plages d'adresses spécifiques. Faire ainsi semble logique car simplement inclure un ROM contenant une copie à peine modifiée du BIOS original dont IBM détient les droits aurait pu attirer des ennuis légaux. Mais il se peut fort bien que cela ait permit aussi d'économiser en utilisant un plus petit ROM dans la cartouche. (Je n'ai jamais vu l'intérieur d'une cartouche QuickSilver, j'ignore donc si c'est le cas...)




Comparaison du BIOS normal et patché

Utiliser un petit outil permettant de copier le contenu du BIOS vers un fichier, sur un PCjr avec et sans la cartouche QuickSilver installée, résulte en deux fichiers de 64k presque identiques.

Comme je me demandais de quelle envergure étaient les modifications apportées au BIOS par cette cartouche, j'ai comparé un dump original du BIOS (SYSTEM.ROM) avec un dump effectué alors qu'une cartouche QuickSilver était présente (QUIKSILV.ROM).

Sur un système Linux, j'ai utilisé hexdump pour convertir ces fichiers binaires en format hex:
hexdump -C QUIKSILV.ROM > quiksilv.hex
hexdump -C SYSTEM.ROM > system.hex

Voici à quoi le résultat ressemble:
00000000  31 35 30 34 30 33 36 20  43 4f 50 52 2e 20 49 42  |1504036 COPR. IB|
00000010  4d 20 31 39 38 31 2c 31  39 38 33 49 01 57 01 6d  |M 1981,1983I.W.m|
00000020  01 86 01 ba 01 20 4b 42  47 0a 47 0a bb 0a 84 0a  |..... KBG.G.....|
00000030  45 52 52 4f 52 41 42 43  44 45 46 47 48 78 03 78  |ERRORABCDEFGHx.x|
00000040  02 ef f7 b0 00 e6 a0 fe  c8 e6 10 e4 a0 fa b8 8f  |................|
00000050  10 ba c0 00 b9 04 00 0a  c4 ee 80 c4 20 e2 f8 b0  |............ ...|
...

Ensuite j'ai utilisé la commande diff qui permet d'exposer les différences entre deux fichiers texte:
$ diff -u system.hex quiksilv.hex
--- system.hex  2020-10-03 13:39:02.227589679 +0900
+++ quiksilv.hex        2020-10-03 13:38:47.920171210 +0900
@@ -139,7 +139,7 @@
 000008a0  80 e6 f2 c6 06 84 00 00  bf 78 00 1e 07 b8 14 14  |.........x......|
 000008b0  ab ab b8 01 01 ab ab e4  21 24 fe e6 21 1e b8 50  |........!$..!..P|
 000008c0  00 8e d8 80 3e 18 00 00  1f 74 10 b2 02 e8 3c 11  |....>....t....<.|
-000008d0  b4 00 cd 16 80 fc 1c 75  f7 eb 05 b2 01 e8 2c 11  |.......u......,.|
+000008d0  b4 00 cd 16 80 fc 1c 75  f7 eb 05 eb 03 ee 2c 11  |.......u......,.|
 000008e0  bd 3d 00 33 f6 2e 8b 56  00 b0 aa ee 1e ec 1f 3c  |.=.3...V.......<|
 000008f0  aa 75 06 89 94 08 00 46  46 45 45 83 fd 41 75 e5  |.u.....FFEE..Au.|
 00000900  33 db ba fa 03 ec a8 f8  75 08 c7 87 00 00 f8 03  |3.......u.......|
@@ -183,7 +183,7 @@
 00000b60  8b 1e 72 04 81 fb 34 12  8c c2 8e da 75 0b f3 ab  |..r...4.....u...|
 00000b70  8e d8 89 1e 72 04 8e da  c3 81 fb 21 43 74 ef 88  |....r......!Ct..|
 00000b80  05 8a 05 32 c4 74 03 e9  82 00 fe c4 8a c4 75 ef  |...2.t........u.|
-00000b90  8b e9 b8 aa aa 8b d8 ba  55 55 f3 ab 4f 4f fd 8b  |........UU..OO..|
+00000b90  eb dc b8 aa aa 8b d8 ba  55 55 f3 ab 4f 4f fd 8b  |........UU..OO..|
 00000ba0  f7 8b cd ad 33 c3 75 64  8b c2 ab e2 f6 8b cd fc  |....3.ud........|
 00000bb0  46 46 8b fe 8b da ba ff  00 ad 33 c3 75 4e 8b c2  |FF........3.uN..|
 00000bc0  ab e2 f6 8b cd fd 4e 4e  8b fe 8b da f7 d2 0a d2  |......NN........|
@@ -340,11 +340,11 @@
 00001530  ff 20 ff 54 55 56 57 58  59 5a 5b 5c 5d 68 69 6a  |. .TUVWXYZ[\]hij|
 00001540  6b 6c 6d 6e 6f 70 71 37  38 39 2d 34 35 36 2b 31  |klmnopq789-456+1|
 00001550  32 33 30 2e 47 48 49 ff  4b ff 4d ff 4f 50 51 52  |230.GHI.K.M.OPQR|
-00001560  53 fb 50 53 51 52 56 57  1e 06 fc e8 1d fe 8a e0  |S.PSQRVW........|
-00001570  3c ff 75 1b bb 80 00 b9  48 00 e8 b8 ca 80 26 17  |<.u.....H.....&.|
-00001580  00 f0 80 26 18 00 0f 80  26 88 00 1f e9 bb 00 24  |...&....&......$|
-00001590  7f 0e 07 bf 5c 14 b9 08  00 f2 ae 8a c4 74 03 e9  |....\........t..|
-000015a0  98 00 81 ef 5d 14 2e 8a  a5 64 14 a8 80 75 51 80  |....]....d...uQ.|
+00001560  53 fb 50 53 51 52 56 57  1e 06 fc e8 1d fe e4 60  |S.PSQRVW.......`|
+00001570  8a e0 3c ff 75 1b bb 80  00 b9 48 00 e8 b6 ca 80  |..<.u.....H.....|
+00001580  26 17 00 f0 80 26 18 00  0f 80 26 88 00 1f e9 b9  |&....&....&.....|
+00001590  00 24 7f 0e 07 bf 5c 14  b9 08 00 f2 ae 8a c4 75  |.$....\........u|
+000015a0  1f 90 81 ef 5d 14 2e 8a  a5 64 14 a8 80 75 51 80  |....]....d...uQ.|
 000015b0  fc 10 73 07 08 26 17 00  e9 8f 00 f6 06 17 00 04  |..s..&..........|
 000015c0  75 78 3c 52 75 22 f6 06  17 00 08 75 6d f6 06 17  |ux<Ru".....um...|
 000015d0  00 20 75 0d f6 06 17 00  03 74 0d b8 30 52 e9 0b  |. u......t..0R..|
Les différences sont à l'intérieur des premiers 32k. La cartouche peut donc se soucier uniquement de CS6 (F0000-F7FFF) et peut mettre un niveau logique bas sur BASE2 pour prendre le contrôle du BUS lorsque l'ordinateur accède à des zones devant être modifiées. Parlant de zones, voici desquelles il s'agit:
  • 08DB-08DC : 3 octets modifiés
  • 0B90-0B91 : 2 octets modifiés
  • 156E-15A1 : 52 octets modifiés
Un listing du BIOS est fourni dans le manuel technique du PCjr dans "Appendix A - ROM BIOS LISTING". Je m'en suis servi pour écrire les sections suivantes pour voir exactement ce que fait chaque modification.


08DA-08DC : Pas de signal sonore

La première série d'octets modifiés permet de retirer le signal sonore. Voici le code original:
...
08C9    74 10                       JE F15A_0              ; Continue if no error
08CB    B2 02                       MOV DL,2               ; 2 Short beeps (error)
08CD    E8 1A0C R                   CALL ERR_BEEP
0BD0                    ERR_WAIT:
0BD0    B4 00                       MOV AH, 00
0BD2    CD 16                       INT 16H
0BD4    B0 FC 1C                    CMP AH, 1CH
0BD7    75 F7                       JNE ERR_WAIT
0BD9    EB 05                       JMP SHORT F15C
08DB    B2 01    F15A_0:     MOV DL,1               ; 1 SHORT BEEP (NO ERRORS)
0BDD    E8 1A0C R            CALL ERR_BEEP
                        ; -- Setup printer and rs232 base addresses if device attached
08E0    BD 003D R       F15C:       MOV BP,OFFSET F4
Alors le jump equals à l'adresse 08C9 dirige le CPU vers F15_A (adresse 08DB) lorsqu'il n'y a pas eu d'erreur. À cet endroit, DL est mis à 1 afin que l'appel à ERR_BEEP génère une seule impulsion sonore.

Voici le code une fois modifié:
08DB    EB 03    F15A0_0:    JMP SHORT F15C
0BDD    EE 2C 11             ; Skipped non-sense (EE is OUT DX, al), 2C 11 is (SUB AL, 11H)
                        ; -- Setup printer and rs232 base addresses if device attached
08E0    BD 003D R       F15C:       MOV BP,OFFSET F4
Le "MOV DL, 1" qui prépare normalement l'argument pour ERR_BEEP (nombre d'impulsions) est simplement remplacé par un saut vers F15C (adresse 08E0). Ce qui suit n'est jamais exécuté et ne semble pas très utile. Je me demande tout de même pourquoi ce troisième octet a été modifié. Pourquoi EE (OUT DX, AL)? Est-ce un ajustement pour qu'un checksum fonctionne encore? (Est-ce qu'il y a un test du checksum du BIOS sur PCjr?)




0B90-0B91 : Test de mémoire plus rapide

Il s'agit ici de la modification qui accélère le test de mémoire en le rendant moins exhaustif. Ce code s'étale sur plusieurs lignes et contient quelques boucles qui ne sont pas montrées ici.
0B59    Start of the memory test proc here
...     setup, warm-start test, etc. will jump to P1 (0879) on cold boot.  ...

0B6E    F3/ AB          P12:        REP STOSW             ; Simple fill with 0 on warm-start
...		some clean-up code not shown ...
0B78    C3                          RET                   ; And Exit

0879    81 FB 4321      P1:         CMD BX, 4321H         ;
...     does a short test
...
0B90    8B E9                       MOV BP, CX            ; Save word count
...     Do tests using all 256 data patters (slow)
Le test de mémoire commence à 0B59. Après quelques opérations le code saute à l'adresse P1 (adresse 0879) lorsqu'il s'agit d'un test de mémoire à froid (contrairement au test "à chaud" qui a lieu lors d'un reboot par CTRL+ALT+DEL par exemple).

Alors le code à P1 fait d'abord un premier test, suivi d'un long test utilisant les 256 valeurs possibles.

Le code à l'adresse 0B90 qui prépare pour le long test est remplacé par ceci:
0B90    EB DC                       JMP SHORT P12 (0B6E)
Donc la cartouche remplace le MOV BP,CX à l'adresse 0B90 par un jump. Ce jump a lieu juste après le test court et évite donc de passer du temps à tester la mémoire en utilisant les 256 valeurs d'octet possibles. Le code cible à P12 rempli la mémoire avec des zéros, comme s'il s'agissait d'un redémarrage à chaud, restaure une série de registres et retourne.


156E-15A1 : Correctif de compatiblité clavier

Ceci est clairement la modification la plus volumineuse des trois. Selon le manuel, l'implémentation de INT9 sur PCjr est légèrement différente de celle sur PC. Sur PC, INT9 récupère le scancode en lisant le port 60h. L'implémentation PCjr toutefois s'attend à ce que le scancode soit déjà dans le registre AL, ce qui cause des problèmes de compatibilité avec certains logiciels.

Autrement dit, il faudrait avoir un in AL, 60h quelque-part dans l'implémentation. Voici le début du code INT9 d'origine sur PCjr:
                        ; ------ Keyboard Interrupt Routine
1651                    KB_INT      PROC FAR
1561    FB                          STI
1562    ... a bunch of register pushes ...
156A    FC                          CLD
156B    E8 138B R                   CALL DDS
156E    8A E0                MOV AH, AL

Les octets 8A E0 dans le code original sont remplacés par E4 60. Devinez ce que c'est?
156E E4 60       IN AL, 60H
Exactement. Mais ceci est en fait un ajout de deux octets au début d'une série de code pratiquement identique. En fait, la routine est pratiquement identique, tel que mis en évidence en alignant les deux version ainsi:
Original code:       8a e0 3c ff 75 1b bb 80 00 b9 48 00 e8 b8 ca 80 26 17 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 bb 00 24 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 74 03 e9 98 00
Patched code:  e4 60 8a e0 3c ff 75 1b bb 80 00 b9 48 00 e8 b6 ca 80 26 17 00 f0 80 26 18 00 0f 80 26 88 00 1f e9 b9 00 24 7f 0e 07 bf 5c 14 b9 08 00 f2 ae 8a c4 75 1f 90
L'endroit où "E8 B8 CA" devient "E8 B6 CA" est simplement un CALL avec adressage relatif qui a du être ajusté car l'appelant est situé deux octets plus bas qu'avant. Même chose pour le "E9 BB 00" qui devient "E9 B9 00" (cette fois c'est la cible d'un jump qui est ajustée).

Mais comme deux nouveaux octets ont été insérées, il y a un problème. Le code original se termine alors deux octets plus loin en mémoire. Autrement dit, ça ne rentre plus! Voyons voir comment cela a été résolu. D'abord le code d'origine:
159D    74 03                       JE K17              ; Jump if match found
159F    E9 163A R                   JMP K25             ; If no match, then no shift found
                        ; ----- Shift key found
15A2    81 EF 145D R    K17:        SUB DI, OFFSET K6+1
...
...
163A                    K25:
...
Donc les 5 derniers octets étaient occupées par un "jump si égal" et un "jump" inconditionnel. La nouvelle série d'instruction doit occuper un maximum de 3 octets, et c'est le cas. Il s'agit désormais d'un "jump si non-égal" et d'un NOP qui ne fait rien.
159F    75 1F                       JNE 0x21 (15B4)     ; Jump if no match found (shift not found)
15A1    90                          NOP                 ; Fallthrough if match found
....
15A2    81 EF 145D R    K17:        SUB DI, OFFSET K6+1

15B4    08 26 0017 R                OR KB_FLAG, AH      ; Turn on shift bit
15B8    E9 164A R                   JMP K26             ; Interrupt return
...
15C0    75 7B                       JNZ K25
...
163A                    K25:        ; test for hold state, test for break key and stuff
...
164A                    K26:
... series of POPs, then IRET.
Je ne m'explique pas complètement cette modification. Bon, je comprends l'idée de remplacer le JE, JMP par la logique inverse ("jump" si pas égal, ou passe tout droit), mais la raison pourquoi la cible du JNE est 15B4 n'est pas claire. Le code original aurait sauté vers K25. Peut-être que K25 est trop éloigné pour être la cible d'un JNE, mais alors, pourquoi ne pas sauter vers 15C0, seulement 12 octets plus loin et pouvant parfaitement être la cible de ce JNE avec son argument 8 bit signé? 15C0 est en plein milieu d'une routine, mais par chance, il s'agit d'une instruction JNZ K25. Le CPU sauterait alors vers K25 immédiatement, puisque JNZ et JNE sont équivalents et la condition préalable au saut (ZF à zéro) demeure.

Telle que les choses sont en ce moment, passer par le code à 15B4 a pour résultat d'aller ensuite à K26 (ménage et retour de l'interruption) et de contourner complètement les opérations faites à K25. J'avoue ne pas comprendre assez bien ce qui est fait pour dire si cela brise quelque chose et quoi. Il se peut fort bien qu'il s'agisse d'un autre correctif de compatibilité. Toutefois le manuel n'en fait pas mention.


Des idées sur le matériel

Je n'ai pas eu la chance de regarder à l'intérieur d'une cartouche QuickSilver pour voir ce qu'il en est exactement, mais je me suis amusé à réfléchir à comment cette cartouche peut bien fonctionner.

Rappelons d'abord que les zones modifiées sont les suivantes:

  • 08DB-08DC : 3 octets
  • 0B90-0B91 : 2 octets
  • 156E-15A1 : 52 octets
Il me semble peu probable que la logique à l'intérieur de la cartouche tente de détecter ces plages d'adresses exactement. Je crois qu'il y a potentiellement un compromis à faire entre le nombre de signaux d'adresses devant être surveillés et la complexité du circuit en tenant compte de limitations diverses (nombre d'entrées orties sur GAL/PAL ou espace physique pour des portes logiques sur le circuit imprimé. Et le coût bien sûr.

Pour ces raisons, il est probable que le ROM de la cartouche contenait en fait un certains nombre d'octets originaux précédant ou suivant les octets modifiés.

À titre d'exemple, si on se base uniquement sur les bits d'adresse uniques pour chaque plage, on obtient ceci:
0000 1000 1101 1011    08DB
0000 1000 1101 1100    08DC
0000 1000 1101 1xxx
Zone choisie quand [A14..3] est 0x08D8 (8 octets). (08D8 - 08DF). 5 octets originaux.
0000 1011 1001 0000    0B90
0000 1011 1001 0001    0B91
0000 1011 1001 000x
Zone choisie quand [A14..1] est 0x0B90 (2 octets). (0B90 - 0B91). Aucuns octets originaux.
0001 0101 0110 1110    156E
0001 0101 1010 0001    15A1
0001 0101 xxxx xxxx
Zone choisie quand [A14..8] est 0x1500 (256 octets). (1500 - 15FF). 204 octets originaux.

Cette dernière zone contient beaucoup de code original. Il serait peut-être préférable de le séparer en plus petits morceaux. Les les deux premières zones pourraient aussi être élargies un peu, si cela permet de simplifier le circuit. Il s'agit ici d'un compromis entre la simplicité technique et la légalité, ce qui n'est pas évident... Qui peut me dire exactement combien d'octets provenant du BIOS original il est possible de copier et inclure dans un produit comme celui-ci sans attirer la foudre d'IBM?

Est-ce que quelqu'un aurait par hasard des photos de l'intérieur d'une cartouche QuickSilver?


L'intérieur d'une cartouche

Michael Brutman a partagé avec moi ces photos de l'intérieur d'une cartouche:

Côté pièces

Côté pièces

Côté soudures

Côté soudures



Alors il y a un pal 16L8 et ce qui semble être un EPROM 27C512 de 64kB. Voici le pinout de ces deux pièces:


Visuellement, je peux dire qu'au moins les lignes d'adresses suivantes se rendent sur des entrées du PAL: A14, A13, A12, A11, A7, A6, A5. Le signal CS6 provenant du PCJR est également dirigé vers la broche 7 du PAL en passant par un pont de soudure. Les broches 12 et 19 du PAL sont des sorties dédiées. Elles se rendent sans doute au ROM (Chip select et signal de lecture) et au contact BASE2 du port de cartouche afin de déactiver le ROM du BIOS interne au PCjr lors des accès aux régions de mémoire visées.

Je pense pouvoir conclure que le décodage d'adresse se fait jusqu'au bit 5, ce qui implique des séquences minimales de 32 octests dans le ROM.

Si cela est exact, il se peut fort bien qu'uniquement les zones suivantes du EPROM soient programmées:
  • 08C0-08DF : No beep patch
  • 0B80-0B9F : Fast memory test
  • Possibly 1560-157F and 1580-15BF : Keyboard compatiblity fix
Pour en être tout à fait certain, il faudrait suivre les traces du circuit plus attentivement, extraire les équations du PAL et lire l'EPROM directement (en contournant le PAL). Mais à ce point ci, ce serait sans doute aller trop loin. D'ailleurs, je pense que j'ai déjà donné suffisamment d'attention à ce "problème"! ;-)


Remerciements

Merci beaucoup à Michael Brutman pour avoir pris le temps de photographier une cartouche pour moi, mais également pour son merveilleux site d'information sur le PCjr.

Visitez Mike's PCjr page à l'adresse suivante: https://www.brutman.com/PCjr/pcjr.html.