Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A20 geht nicht auf

5 views
Skip to first unread message

Markus Wichmann

unread,
Sep 7, 2006, 12:02:50 PM9/7/06
to
Hi all,
ich bin gerade dabei, einen Bootloader für mein OS zu schreiben (alles
noch in der Mache). Der Bootloader hat ja die Aufgabe, die CPU in den PM
zu versetzen, um dann den Kernel zu rufen. Bis es soweit ist, muss ich
erstmal das A20 öffnen (wenn ich alles richtig verstanden habe). Das
wollte ich mit folgender Prozedur tun.

openA20:
cli ;disable interrupts
mov cx,5
startAttempt1:
call wkc ;gotta wait for keyb controller
mov al,0d0h ;ask for status byte
out 64h, al ;send request
call wkd ;wait for data part
xor eax,eax
in al,60h ;fetch status byte
push eax ;safe eax
call wkc ;begin to grow roots while waiting for keyboard
mov al,0d1h ;ask to set status byte
out 64h, al ;send request
call wkc ;roots grow longer...
pop eax
or al,10b ;set A20 byte
out 60h,al ;hope it will eat it

call wkc ;again the roots grow...

mov al,0d0h ;ask for status byte again (is A20 now open?)
out 64h,al

call wkd ;wait for data part

xor ax,ax
in al,60h
test al,10b ;please, A20 bit, PLEASE
jnz .success ;if so: Hooray!!
dec cx ;else: try this another 5 times
jnz startAttempt1
mov si,A20FailureMsg ;you got this far? well, write error message
call putstr
call halt ; halt system
.success
mov si,A20SuccessMsg ;now write success message
call putstr
sti ;bring back interrupts
ret

Die Prozedur wkc holt sich von Port 64h den aktuellen Status und wartet,
bis bei diesem das Bit 10b gesetzt ist. wkd macht das gleiche, bloß
guckt es nach dem Bit 1b.
Die Prozeduren putstr und halt sollten selbsterklärend sein. Die Ausgabe
läuft ja auch korrekt. Trotzdem läuft irgendetwas schief: Ich erhalte
immer meine Fehlernachricht. Das Protokoll von Bochs ist leider etwas
/zu/ umfangreich: Ich kann mich nicht durchfitzen. Ob was dazu
drinsteht, weiß ich auch nicht. Ich habe einfach mal 32-Bit-Code
angeordnet. Ist doch OK, oder? Ausgeführt wird der Code ja.
Nehmt also einfach an, der Code würde auf einer Diskette im Bootsektor
laufen. Das ich einen x86 habe, brauche ich wohl nicht zu erwähnen ;-).
Mein Keyboard ist ein kabelloses am PS/2-Anschluss. Ist da A20 schon von
Anfang an offen/gar nicht berücksichtigt?
Und bevor ihr fragt: A20SuccessMsg und A20FailureMsg sind lediglich
sz-Variablen (ja sz. Dafür habe ich gesorgt.) Und putstr erwartet die
Basisaddresse des Strings in SI. (Ich überlege noch, ob ich nicht doch
auf Pascal-Strings umstelle, da mit C ja schon einmal ein OS geschrieben
wurde, mit Pascal hingegen noch nicht.)
Doch ich schweife ab, und davon geht A20 auch bloß nicht auf.
Was mache ich falsch?
Btw habe ich den obigen Code von
http://www.osdever.net/tutorials/a20.php?the_id=4 genommen und so
abgewandelt, dass ich immer die Prozeduren aufrufe, nicht in neu
erstellte Schleifen reingehe. (ist nicht so clever, immer den gleichen
Code selbst zu tippen, wenn es doch einmal Anspringen auch tut.)
tia und cya,
nullplan

--
To err is human. To forgive is divine.
To forget is also human...

Herbert Kleebauer

unread,
Sep 7, 2006, 2:20:54 PM9/7/06
to

Markus Wichmann wrote:
>
> Hi all,
> ich bin gerade dabei, einen Bootloader für mein OS zu schreiben (alles
> noch in der Mache). Der Bootloader hat ja die Aufgabe, die CPU in den PM
> zu versetzen, um dann den Kernel zu rufen. Bis es soweit ist, muss ich
> erstmal das A20 öffnen (wenn ich alles richtig verstanden habe). Das
> wollte ich mit folgender Prozedur tun.

Ist bei den neueren Rechnern das A20 Gate nicht standardmäßig
freigeschalten (bzw. nicht mehr vorhanden). Schreib halt
etwas bei 2 Mbyte in den Speicher und wenn dann das Gleiche
bei 3 MByte steht, muß du es wohl erst freischalten.

Zu 80486 Zeiten habe ich folgenden Code in meinem Debugger benutzt:


a20frei:
; Hinweis: Einlesen des 8042 Ausgangsports mittels des Befehls $d0,
; setzen des Bits 1 und anschliessendes zurueckschreiben mit
; $d1 funktioniert nicht. Problem liegt wohl bei Bit 4, das
; beim Lesen 0 ist, aber offensichtlich als 1 zurueckgeschrieben
; werden muss, da sonst die Tastatur keine Interrupts mehr
; ausloest (warum?). Deshalb wird zum Oeffnen des A20-Gates der
; Ausgangsport des 8042, unabhaengig von seinem alten Wert, mit
; $df (aus Literatur, wieso?) beschrieben.

move.l r0,-(sp)
move.w sr,-(sp)
bclr.w #9,sr ; Interrupt sperren

_10: in.b #$64,r0 ; Status Tastaturprozessor lesen
tst.b #2,r0 ; bereit fuer neuen Befehl?
bne.b _10 ; wenn nein, warten
move.b #$d1,r0 ; Code fuer Output-Port schreiben
out.b r0,#$64 ; Code an Tastaturprozessor

_20: in.b #$64,r0 ; Status Tastaturprozessor lesen
tst.b #2,r0 ; bereit fuer neuen Befehl?
bne.b _20 ; wenn nein, warten
; move.b #$dd,r0 ; A20-Leitung sperren
move.b #$df,r0 ; A20-Leitung freigeben
out.b r0,#$60 ; an Tastaturprozessor

_30: in.b #$64,r0 ; Status Tastaturprozessor lesen
tst.b #2,r0 ; bereit fuer neuen Befehl?
bne.b _30 ; wenn nein, warten

move.w (sp)+,sr ; Interrupt freigeben
move.l (sp)+,r0
rts.w

Daniel Alder

unread,
Sep 8, 2006, 5:10:10 AM9/8/06
to
Hallo Markus

Ich schreib dir nicht vor wie du's machen musst - schliesslich hab ich
auch mal so angefangen ;-)

Je nachdem was du machst kannst du deinen Kernel auch gleich so
verpacken, dass er mit den verbreiteten Boot-Managern GRUB bzw. ISOLinux
geladen werden kann. Dann hast du gleich von Anfang an ein
Boot-Verfahren, das so stabil ist, dass man es ohne grössere Tricks
sogar übers Netzwerk oder von CD verwenden könnte... Und wenn aus deinem
OS wirklich mal was wird, wird sich niemand über fehlende
Multiboot-Unterstützung ärgern.
Ich jedenfalls würde meinen nächsten Versuch so starten - wenns denn
einen gäbe :-)

Gruss
Daniel

Markus Wichmann

unread,
Sep 8, 2006, 10:17:35 AM9/8/06
to
Daniel Alder schrieb:

Hi,
ich finde die Idee nicht so prickelnd, weil ich mal ausnahmsweise alles
selbst machen will. Sonst (bei normalen Apps) verlasse ich mich gerne
auf M$ bzw. die Autoren von FreePascal. Aber das hier soll _mein_ OS
werden. Nicht das von GNU oder Novell. Oder von irgendwem sonst.
Nebenbei ist eine Verwendung von CD doch das gleich wie von Diskette
oder Festplatte, oder? Momentan teste ich alles, indem ich Bochs ein
Diskettenimage als Bootfloppy gebe. Solange mein Kernel nicht die
Platzgrenzen sprengt, muss ich mir da auch keine andere Möglichkeit
einfallen lassen oder? (Weil mein FDD FUBAR ist, muss ich ein
Diskettenlaufwerk emulieren. Hat allerdings gedauert, bis einen Emu
gefunden habe). Außerdem ist der Kernel eh noch nicht aktuell -- erstmal
ist der Bootloader dran. Wenn der den PM angeschmissen gekriegt hat,
kann ich mich um einen Kernel bemühen. Aber bevor die Voraussetzungen
nicht gegeben sind, wäre es doch sinnlos, etwas darauf aufbauendes zu
proggen, oder?
Tschö,

Markus Wichmann

unread,
Sep 8, 2006, 10:25:54 AM9/8/06
to
Herbert Kleebauer schrieb:

Hi,
ich werd den Code mal testen. Melde mich dann wieder.
Tschö,

Daniel Alder

unread,
Sep 9, 2006, 11:46:11 AM9/9/06
to
Hallo Markus

> ich finde die Idee nicht so prickelnd, weil ich mal ausnahmsweise alles
> selbst machen will. Sonst (bei normalen Apps) verlasse ich mich gerne
> auf M$ bzw. die Autoren von FreePascal. Aber das hier soll _mein_ OS
> werden. Nicht das von GNU oder Novell. Oder von irgendwem sonst.

Ist schon gut. Bei mir hat es so angefangen dass ich relativ früh schon
versucht hab den Kernel von einer Datei zu holen statt von einem
bestimmten Sektor. Das war mir dann aber schliesslich zu aufwendig für
den Anfang (wegen dem Dateisystem), und ich hab mir schliesslich ein
DOS-Programm geschrieben das den Kernel in den RAM lädt und in den PM
schaltet - mit einer eigenen "Übergabe-"Spezifikation.
Erst später hab ich dann erfahren, wie einfach ich mir diesen Schritt
mit Hilfe von GRUB hätte sparen können, und dann hätte ich mich schon
viel früher dem Multitasking widmen können.
Aber wie schon gesagt: Jeder darf natürlich bei 0 anfangen. Ist
vielleicht sogar ein notwendiger Schritt, um die notwendigen Kenntnisse
zu erlangen...

Übrigens: Booten von CD ist nur dann gleich, wenn du die
Floppy-Emulation verwendest. Ansonsten musst du dich mit einer ganz
anderen API und einem anderen Dateisystem rumschlagen...

Gruss und viel Erfolg ;-)
Daniel

Markus Wichmann

unread,
Sep 13, 2006, 12:05:25 PM9/13/06
to
So, hab es jetzt mal getestet. Bisher wusste ich nicht, dass die "BITS
32"-Anweisung für einen Bootsektor tödlich ist. Jetzt weis ich es...
Ansonsten hat der Code gut funktioniert. Wenn man mal davon absieht,
dass ich erstmal Probleme mit der komischen Sprache von Herbert hatte
(also der Assembler-Sprache. Dein Deutsch war schon verständlich). Hab
einfach mal angenommen, r0 ist ein Register und alles, was da mit Rauten
davor dastand, sind Zahlenwerte. Dienen die drei ersten Zeilen alle dem
Löschen des Interruptflags? Da finde ich persönlich ein 'cli' einfacher
;-) Ich habe dem ganzen noch ein paar Testzeilen angefügt, um zu sehen,
ob man wirklich das A20 geöffnet hat. Ansonsten den Code nach Intel
übersetzt und unverändert übernommen. Klappt. Danke!
Nur versteh ich nicht, wieso du dreimal die gleiche Routine in den Code
schreibst, anstatt sie einmal zu schreiben, ein 'ret' dahinter zu setzen
und sie dann immer wieder zu rufen? Verbraucht viel weniger Platz (und
der ist bei einem Bootloader immer knapp)
cya,
und lass dich von meinen Fragen nicht zu sehr irritieren,

Herbert Kleebauer

unread,
Sep 13, 2006, 1:12:12 PM9/13/06
to
Markus Wichmann wrote:
>
> So, hab es jetzt mal getestet. Bisher wusste ich nicht, dass die "BITS
> 32"-Anweisung für einen Bootsektor tödlich ist. Jetzt weis ich es...
> Ansonsten hat der Code gut funktioniert. Wenn man mal davon absieht,
> dass ich erstmal Probleme mit der komischen Sprache von Herbert hatte
> (also der Assembler-Sprache. Dein Deutsch war schon verständlich). Hab
> einfach mal angenommen, r0 ist ein Register und alles, was da mit Rauten

r0 = eax, ax, al
r1 = edx, dx, dl
r2 = ecx, cx, cl
usw.

> davor dastand, sind Zahlenwerte. Dienen die drei ersten Zeilen alle dem
> Löschen des Interruptflags? Da finde ich persönlich ein 'cli' einfacher

00000000: 00000000: 66 50 move.l r0,-(sp)
00000002: 00000002: 9c move.w sr,-(sp)
00000003: 00000003: fa bclr.w #9,sr

eax und das Statusregister retten und dann Interrupts sperren. Und am
Ende dann:

0000001e: 0000001e: 9d move.w (sp)+,sr
0000001f: 0000001f: 66 58 move.l (sp)+,r0
00000021: 00000021: c3 rts.w

Beim Wiederherstellen des alten Statusregisters wird das Interrupt
Enable Flag wieder so gesetzt wie es vor dem Löschen war.

> Nur versteh ich nicht, wieso du dreimal die gleiche Routine in den Code
> schreibst, anstatt sie einmal zu schreiben, ein 'ret' dahinter zu setzen
> und sie dann immer wieder zu rufen? Verbraucht viel weniger Platz (und
> der ist bei einem Bootloader immer knapp)

Viel weniger Platz? Bist du sicher?

00000000: 00000000: e4 64 _10: in.b #$64,r0
00000002: 00000002: a8 02 tst.b #2,r0
00000004: 00000004: 75 fa bne.b _10
00000006: 00000006: b0 d1 move.b #$d1,r0
00000008: 00000008: e6 64 out.b r0,#$64

0000000a: 0000000a: e4 64 _20: in.b #$64,r0
0000000c: 0000000c: a8 02 tst.b #2,r0
0000000e: 0000000e: 75 fa bne.b _20
00000010: 00000010: b0 df move.b #$df,r0
00000012: 00000012: e6 60 out.b r0,#$60

00000014: 00000014: e4 64 _30: in.b #$64,r0
00000016: 00000016: a8 02 tst.b #2,r0
00000018: 00000018: 75 fa bne.b _30


Sind gerade mal zwei Bytes mehr als:

00000000: 00000000: e8 000e bsr.w sub
00000003: 00000003: b0 d1 move.b #$d1,r0
00000005: 00000005: e6 64 out.b r0,#$64
00000007: 00000007: e8 0007 bsr.w sub
0000000a: 0000000a: b0 df move.b #$df,r0
0000000c: 0000000c: e6 60 out.b r0,#$60
0000000e: 0000000e: e8 0000 bsr.w sub


00000011: 00000011: e4 40 sub: in.b #64,r0
00000013: 00000013: a8 02 tst.b #2,r0
00000015: 00000015: 75 fa bne.b sub
00000017: 00000017: c3 rts.w

Und für zwei Bytes mache ich nicht 3 Unterprogrammaufrufe.

Dirk Wolfgang Glomp

unread,
Sep 14, 2006, 5:41:25 AM9/14/06
to
Herbert Kleebauer schrieb:

> r0 = eax, ax, al
> r1 = edx, dx, dl

Ich dachte "r1" wäre "ecx, cx, cl"...

> r2 = ecx, cx, cl
> usw.

...und "r2" wäre "edx, dx, dl"?

Bin verwirrt.

Dirk

Herbert Kleebauer

unread,
Sep 15, 2006, 4:32:45 AM9/15/06
to

Ich hab halt versucht eine "logische" Nummerierung zu
verwenden. Und ich finde es halt sinnvoller edx r1 und
ecx r2 zu nennen als umgekehrt.

Dirk Wolfgang Glomp

unread,
Sep 15, 2006, 3:19:15 PM9/15/06
to
Herbert Kleebauer schrieb:

Wo genau ist das logischer?

Dirk

Herbert Kleebauer

unread,
Sep 16, 2006, 1:45:31 PM9/16/06
to
Dirk Wolfgang Glomp wrote:
> Herbert Kleebauer schrieb:

> >>>r0 = eax, ax, al
> >>>r1 = edx, dx, dl

> >>Ich dachte "r1" wäre "ecx, cx, cl"...

> >>Bin verwirrt.

> > Ich hab halt versucht eine "logische" Nummerierung zu
> > verwenden. Und ich finde es halt sinnvoller edx r1 und
> > ecx r2 zu nennen als umgekehrt.
>
> Wo genau ist das logischer?

Wenn man ganz unvoreingenommen an die Sache heran geht,
denke ich es macht Sinn, die vier Byte adressierbaren und
die anderen vier Register zu gruppieren. Da esp als normales
Register kaum zu verwenden ist, ist es naheliegend, es mit r7
ganz hinten anzusiedeln. Damit ergibt sich bereits:

{r0,r1,r2,r3} = {eax,ebx,ecx,edx} und
{r4,r5,r6} = {esi,edi,ebp}
r7=esp

Nun, ich denke eax verdient die Nummer 0 zu sein:
manche Befehle funktionieren nur mit eax und andere
haben bei Verwendung von eax eine kürzere Codierung
als bei Verwendung anderer Register (z.b. add immediate).

Wenn eax=r0 ist, dann sollte edx=r1 sein, da diese
beiden Register in einigen Befehlen als Pärchen auftreten:

99 ext.l r0,r1|r0
ec in.b r1,r0
ee out.b r0,r1
f7 ff divs.l r6,r1|r0
f7 ef muls.l r6,r0,r1|r0

Da bei der 16 Bit Adressierung aus der ersten Gruppe nur
bx zur Speicheradressierung benutzt werden kann, ist es
logisch, ebx unmitelbar vor den anderen Registern die zur
Speicheradressierung benutzt werden können einzuordnen.
Damit ergibt sich:

eax=r0
edx=r1
ecx=r2
ebx=r3
esp=r7

Da bei meiner Synatx im Befehl die Quelle vor dem Ziel steht
(move.l src,dest) ist es auch logisch, esi vor edi einzuordnen.
Damit bleibt nur noch die Frage, wohin mit ebp. Da ebp wie esp
standardmäßig eine Adresse im SS und nicht wie bei den anderen
Registern im DS adressiert, könnte man ebp neben esp einordnen.
Adererseits ist gerade das der Grund, warum ich eigentlich ebp
nicht zur Speicheraddressierung verwende (da sonst eine Segment-
Override prefix nötig ist) sondern fast ausschließlich als
normales Datenregister. Und damit habe ich es als erstes der
zweiten Gruppe einsortiert. Damit ergibt sich ganz logisch:

eax=r0
edx=r1
ecx=r2
ebx=r3
ebp=r4
esi=r5
edi=r6
esp=r7

Dirk Wolfgang Glomp

unread,
Sep 16, 2006, 3:05:06 PM9/16/06
to
Herbert Kleebauer schrieb:

> Dirk Wolfgang Glomp wrote:
>
>>Herbert Kleebauer schrieb:
>
>>>>>r0 = eax, ax, al
>>>>>r1 = edx, dx, dl
>
>>>>Ich dachte "r1" wäre "ecx, cx, cl"...
>>>>Bin verwirrt.
>
>>>Ich hab halt versucht eine "logische" Nummerierung zu
>>>verwenden. Und ich finde es halt sinnvoller edx r1 und
>>>ecx r2 zu nennen als umgekehrt.
>>
>>Wo genau ist das logischer?
>
> Wenn man ganz unvoreingenommen an die Sache heran geht,

..machen wir.

> denke ich es macht Sinn, die vier Byte adressierbaren und
> die anderen vier Register zu gruppieren. Da esp als normales
> Register kaum zu verwenden ist, ist es naheliegend, es mit r7
> ganz hinten anzusiedeln. Damit ergibt sich bereits:
>
> {r0,r1,r2,r3} = {eax,ebx,ecx,edx} und
> {r4,r5,r6} = {esi,edi,ebp}
> r7=esp

OK.

> Nun, ich denke eax verdient die Nummer 0 zu sein:
> manche Befehle funktionieren nur mit eax und andere
> haben bei Verwendung von eax eine kürzere Codierung
> als bei Verwendung anderer Register (z.b. add immediate).

Klar das muß.

> Wenn eax=r0 ist, dann sollte edx=r1 sein, da diese
> beiden Register in einigen Befehlen als Pärchen auftreten:
>
> 99 ext.l r0,r1|r0
> ec in.b r1,r0
> ee out.b r0,r1
> f7 ff divs.l r6,r1|r0
> f7 ef muls.l r6,r0,r1|r0

Ah daher leitest du die beiden zusammen.

> Da bei der 16 Bit Adressierung aus der ersten Gruppe nur
> bx zur Speicheradressierung benutzt werden kann, ist es
> logisch, ebx unmitelbar vor den anderen Registern die zur
> Speicheradressierung benutzt werden können einzuordnen.
> Damit ergibt sich:
>
> eax=r0
> edx=r1
> ecx=r2
> ebx=r3
> esp=r7

Dann ist das klar.

> Da bei meiner Synatx im Befehl die Quelle vor dem Ziel steht
> (move.l src,dest) ist es auch logisch, esi vor edi einzuordnen.
> Damit bleibt nur noch die Frage, wohin mit ebp. Da ebp wie esp
> standardmäßig eine Adresse im SS und nicht wie bei den anderen
> Registern im DS adressiert, könnte man ebp neben esp einordnen.

Sehe ich ebenso.

> Adererseits ist gerade das der Grund, warum ich eigentlich ebp
> nicht zur Speicheraddressierung verwende (da sonst eine Segment-
> Override prefix nötig ist) sondern fast ausschließlich als
> normales Datenregister.

So mache ich es ebenfalls. Kann auch neben ecx als weiterer
Schleifenzähler gut verwendet werden.

> Und damit habe ich es als erstes der
> zweiten Gruppe einsortiert. Damit ergibt sich ganz logisch:
>
> eax=r0
> edx=r1
> ecx=r2
> ebx=r3
> ebp=r4
> esi=r5
> edi=r6
> esp=r7

Eigenwillig aber logisch. Schönen Dank werde ich jetzt beherzigen.

Dirk

0 new messages