Nachladen des Kernels durch eigenen Bootloader
-
Es gibt da einen Punkt bei der OS-Entwicklung mit eigenem Bootloader, der mir gerade Grenzen aufzeigt. Ich kann hier ...
mov al, 63 ; read n sectors (max. 63!!!)
... nur max. 63 Sektoren einlesen. Der Kernel wird aber größer und größer.
Hat jemand einen Hinweis oder Link, wie man dieses Thema mit eigenem Bootloader sauber löst? Der Kernel wird doch ab 0x8000 geladen? Da müsste doch genügend Platz sein.Hier der bisher für "kleine" Kernel erfolgreich verwendete Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; read kernel from floppy disk ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel ; trouble? try again mov bx, 0x8000 ; set up start address of kernel ; set parameters for reading function ; 8-bit-wise for better overview mov dl,[bootdrive] ; select boot drive mov al, 63 ; read n sectors (max. 63!!!) mov ch, 0 ; cylinder = 0 mov cl, 2 ; sector = 2 mov dh, 0 ; head = 0 mov ah, 2 ; function "read" int 0x13 jc load_kernel ; trouble? try again
INT 13H (0x13)
Function 02H (0x02) Read sector
Call with: AH = 02H
AL = number of sectors
CH = cylinder
CL = sector
DH = head
DL = drive
00H-7FH floppy disk
80H-FFH fixed diskES:BX = segment: offset of buffer
Returns: If function successful
Carry flag = clear
AH = 00H
AL = number of sectors transferredIf function unsuccessful
Carry flag = set
AH = statusWoher kommt die Beschränkung mit den 63 Sektoren in obigem Fall?
Ist der Bootloader aus Sektor 1 im Weg (nachfolgend wird dieser vorher aus dem Weg geschafft)? Ein gewisser Linus Torvalds lädt hier z.B. deutlich mehr:http://stuff.mit.edu/afs/sipb/user/tlyu/tmp/memtest86/bootsect.S
Code-Ausschnitt:load_setup: xor dx, dx ! drive 0, head 0 mov cx,#0x0002 ! sector 2, track 0 mov bx,#0x0200 ! address = 512, in INITSEG mov ax,#0x0200+SETUPSECS ! service 2, nr of sectors ! (assume all on head 0, track 0) int 0x13 ! read it jnc ok_load_setup ! ok - continue push ax ! dump error code call print_nl mov bp, sp call print_hex pop ax xor dl, dl ! reset FDC xor ah, ah int 0x13 jmp load_setup ok_load_setup:
-
Naja, die BIOS Funktion hat seine ganzen Parameter ja nicht umsonst. Da musst du wohl die Funktion mehrfach aufrufen, mit jeweils aus der Logischen Block Adresse (LBA) berechneten Parametern für Zylinder, Kopf und Sektor.
Die Formel für die Umrechnung ist an vielen Stellen zu finden. Unter anderem auch hier:
http://lowlevel.brainsware.org/wiki/index.php/Logical_Block_Addressing
Wikipedia hat dazu auch ein paar Haufen Beispiel-Zahlen: http://en.wikipedia.org/wiki/Cylinder-head-sectorDazu musst du allerdings die Parameter deines Laufwerks bzw. des Mediums wissen. Bei FAT stehen sie im Bootsektor in dieser netten Datenstruktur am Anfang. Oder du rechnest die selbst aus. Oder Nimmst diese Tabelle:
http://www.pcguide.com/ref/fdd/mediaGeometry-c.htmlGeschickte Assemblerprogrammierer kommen natürlich beim inkrementellen lesen eines linearen Abbilds wie deinem Kernel ohne Multiplikationen und Divisionen aus.
Du solltest da auf off-by-one-Probleme aufpassen, weil LBA bei 0 anfängt, während CHS Sektoren bei 1 anfängt zu zählen.
-
Erhard Henkes schrieb:
Woher kommt die Beschränkung mit den 63 Sektoren in obigem Fall?
6 Bit -> 64 Werte. Und ein paar gehen wohl aus technischen Gründen flöten. Willkommen im Jahr 1980.
Ist der Bootloader aus Sektor 1 im Weg (nachfolgend wird dieser vorher aus dem Weg geschafft)? Ein gewisser Linus Torvalds lädt hier z.B. deutlich mehr:
http://stuff.mit.edu/afs/sipb/user/tlyu/tmp/memtest86/bootsect.S
Code-Ausschnitt:
[code]load_setup:
xor dx, dx ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG
mov ax,#0x0200+SETUPSECS ! service 2, nr of sectorsSETUPSECS hat den Wert 1. Er lädt nur einen Sektor. Siehe -> http://stuff.mit.edu/afs/sipb/user/tlyu/tmp/memtest86/mtest.h
An anderer Stelle lädt er sicherlich noch weitere Sektoren. Aber nicht mit schwarzer Magie, sondern schön nacheinander.
-
Jetzt muss ich doch noch mal blöd fragen:
Floppy Disk (1,44 MB):Was kommt nach CHS = 0, 0, 63? Muss ich dann den Head-Wert von 0 auf 1 erhöhen (oder den Cylinder)?
(Floppy hat ja nur 2 Heads) also CHS = 0, 1, 1 ... und wenn ich dann bei CHS = 0, 1, 63 anlange, geht es mit CHS = 1, 0, 1 weiter oder anders?Eigentlich gibt es doch nur CHS = (80)(2)(18) = 2880 Sektoren auf einer Floppy.
Wieso kann ich da eigentlich bei den Sektoren über 18 gehen bis 63?So Lowlevel musste ich bisher nicht rumtigern, richtig lustig.
MitGRUBwärDasNichPassiert
Das wird vermieden, solange es irgendwie geht!
-
Du kannst in diesem Fall mehr als 18 Sektoren am Stück lesen, wenn du ein relativ modernes BIOS hast, das die Berechnungen für dich übernimmt. Siehe auch http://www.ctyme.com/intr/rb-0607.htm
Den Sektor 63 kannst du allerdings nicht adressieren, wenn er nicht existiert.
Ob du Head oder Cylinder erhöhen musst ist theoretisch eine Frage der Konvention. Praktisch ist es durch LBA festgelegt, dass zuerst Head erhöht wird. Siehe auch die spannende, bereits verlinkte Tabelle: http://en.wikipedia.org/wiki/Cylinder-head-sector#Equivalent_values_for_CHS_and_LBA
Du bist übrigens gar nicht mal so "Lowlevel". Du nutzt einfach nur einen veralteten Treiber, mit einem inkonsistenten Interface.
-
Du kannst in diesem Fall mehr als 18 Sektoren am Stück lesen, wenn du ein relativ modernes BIOS hast, das die Berechnungen für dich übernimmt.
Danke für den Hinweis.
Ich habe bisher folgenden Code, der aber nicht zum Erfolg führt:
mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel1: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel1 ; trouble? try again mov bx, 0x8000 ; set up start address of kernel ; set parameters for reading function (8-bit-wise for better overview) mov dl,[bootdrive] ; select boot drive mov al, 63 ; read n sectors mov ch, 0 ; cylinder = 0 mov cl, 2 ; sector = 2 mov dh, 0 ; head = 0 mov ah, 2 ; function "read" int 0x13 jc load_kernel1 ; trouble? try again load_kernel2: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel2 ; trouble? try again mov bx, 0xFE00 ; mov dl,[bootdrive] ; select boot drive mov al, 63 ; read n sectors mov ch, 0 ; cylinder = 1 mov cl, 11 ; sector = 11 mov dh, 1 ; head = 1 mov ah, 2 ; function "read" int 0x13 jc load_kernel2 ; trouble? try again
Ideen:
C 0 0 1 1 H 0 1 0 1 S 1...18 1...18 1...18 1...18
0x8000 + (63*0x200) = 0xFE00 für zweite Runde Sektoren laden.
Könnte das natürlich auch 18er weise machen.
Muss ich jedes mal resetten?
-
Den Sektor 63 kannst du allerdings nicht adressieren, wenn er nicht existiert.
mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel1: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel1 ; trouble? try again mov bx, 0x8000 ; set up start address of kernel ; set parameters for reading function (8-bit-wise for better overview) mov dl,[bootdrive] ; select boot drive mov al, 62 ; read n sectors mov ch, 0 ; cylinder mov cl, 2 ; sector mov dh, 0 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel1 ; trouble? try again load_kernel2: ;xor ax, ax ; mov ax, 0 => function "reset" ;int 0x13 ;jc load_kernel2 ; trouble? try again mov bx, 0xFC00 ; mov dl,[bootdrive] ; select boot drive mov al, 52 ; read n sectors mov ch, 1 ; cylinder mov cl, 10 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel2 ; trouble? try again
geht auch nicht.
-
mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel1: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel1 ; trouble? try again mov bx, 0x8000 ; set up start address of kernel ; set parameters for reading function (8-bit-wise for better overview) mov dl,[bootdrive] ; select boot drive mov al, 17 ; read n sectors mov ch, 0 ; cylinder mov cl, 2 ; sector mov dh, 0 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel1 ; trouble? try again load_kernel2: ;xor ax, ax ; mov ax, 0 => function "reset" ;int 0x13 ;jc load_kernel2 ; trouble? try again mov bx, 0xA200 ; mov dl,[bootdrive] ; select boot drive mov al, 63 ; read n sectors mov ch, 0 ; cylinder mov cl, 1 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel2 ; trouble? try again
... auch nicht. Habe auch cyl/head 0 und 1 umgekehrt probiert.
-
Most BIOSes support "multitrack" reads, where the value in AL exceeds the number of sectors remaining on the track, in which case any additional sectors are read beginning at sector 1 on the following head in the same cylinder
Also wäre doch der Ansatz
C 0 0 1 1 H 0 1 0 1 S 1...18 1...18 1...18 1...18
nicht verkehrt. Offenbar geht das ja auch Cylinder-übergreifend, allerdings nur bis sector 63.
-
Evtl erst 62 Sektoren von CHS:0/0/2 nach 0x8000, dann 63 Sektoren von 0/1/1 nach 0xFC00?
-
Das hatte ich im mittleren Versuch bereits probiert (mit 52 im zweiten Satz).
mov bx, 0xFC00 ; mov dl,[bootdrive] ; select boot drive mov al, 52 ; read n sectors mov ch, 1 ; cylinder mov cl, 10 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13
Ich teste allerdings mit Bochs, vielleicht verkehrt (das mit 63 in einem Ansatz hat bisher jedoch auf bochs funktioniert).
Ein in Bochs lauffähiges OS lief nun nicht in real life auf einem PC, daher macht das jetzt auch keinen Sinn ein "schwieriges" OS so zu testen.
Mist! Bin wohl langsam doch mürbe für Linux und GRUB. Man wird meistens nur an-/ausgelacht, wenn man sich mit eigenem Bootloader und Windows quält.
Was ich benötige, ist ein Vorbild eines Bootloaders, der einen Kern größer 63 Sektoren laden muss. Habe dies bisher nichr gefunden. Pls help.
-
Das Problem scheint eine harte Begrenzung bei 64K zu sein.
http://groups.google.com/group/de.comp.lang.assembler.x86/browse_thread/thread/67307a357a9ca137/e62f588a36e8ad46?lnk=raotCHS habe ich im Griff:
http://lowlevel.brainsware.org/forum/index.php?topic=2219.msg25073#msg25073Daher habe ich nun versucht, meinen Kernel nach 0x10000 zu verlagern, denn obwohl ich CHS richtig gesteuert habe, knallt es bei 0xFE00 einfach brutal und verlagern auf ein anderes Segment hilft nicht!
Leider besteht hier mit der neuen Kernel-Adresse noch ein Problem - entweder mit dem Target-Puffer ES:BX oder mit dem far jump zum PM:
... ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Load GDT, switch to PM, and jump to kernel ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cli ; clear interrupts lgdt [gdtr] ; load GDT via GDTR (defined in file "gtd.inc") mov eax, cr0 ; switch-over to Protected Mode or eax, 1 ; set bit 0 of CR0 register mov cr0, eax ; jmp 08:0x10000 ; this is a 16-bit-FAR-Jmp, but CS is loaded with "index" 8 in GDT ; hence, the code will be interpreted as 32 bit from here on ; the address can be found in the linker script ...
(EDIT: gekürzt)
kernel.ld:
OUTPUT_FORMAT("binary") ENTRY(KernelStart) SECTIONS { . = 0x00010000; .text : { *(EXCLUDE_FILE(shared_pages.o).text) } . = 0x00015000; .text1 : { shared_pages.o(.text) } . = 0x00016000; .data : { *(.data) } .rodata : { *(.rodata) } .bss : { *(.bss) } }
kernel.asm:
[Bits 32] section .text ; ld needs that for coff format [global KernelStart] KernelStart: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor eax, eax ; null desriptor --> FS and GS mov fs, ax mov gs, ax mov esp, 0x200000 ; set stack below 2 MB limit [extern _main] ; entry point in ckernel.c call _main ; ->-> C-Kernel jmp $
Wenn ich den far jump auf
jmp 0x1000:0x0000
abändere, hilft das leider auch nicht.
Bochs resettet in jedem Fall.
Kann jemand helfen?PS: Kein Wunder, das alle GRUB einsetzen. Bootloader scheint aufgrund einiger schwer überwindbarer antiker Strukturen ein wirklich hartes Brot zu sein.
-
Bei deinem Sprung ("jmp 0x8:0x10000") gibt mein Assembler (NASM) sogar eine Warnung aus: warning: word data exceeds bounds
Er assembliert das also zu jmp word 0x8:0x0000. Was ja auch klar ist, wenn man im Real Mode ist, weil Segment und Offset halt 16 Bit sind.
Du solltest auf deine Ausgaben gucken, oder deinen Assembler updaten.
Aber was für ein Glück, dass man das Offset auf 32 Bit erweitern kann: jmp dword 0x8:0x10000
-
jmp dword 08:0x10000
Thx!
Gibt man "jmp dword 08:0x10000" in Google ein, findet man nur uns hier:
http://www.google.de/search?hl=de&q="jmp+dword+08%3A0x10000"&btnG=Suche&meta= (Ich liebe solche Unikate)Du solltest auf deine Ausgaben gucken
werde ich beherzigen.
-
Allerdings dürfte der nächste Schlag nun bei einem Kernel mit mehr als 128 Sektoren vorprogrammiert sein (EDIT: ich habe es ausprobiert: es stimmt!). Weiß da jemand etwas Genaues, wie man diese harte Begrenzung umgeht?
-
Wie in dem Verlinkten Thema steht, duerfte das an dem antike DMA-Controller fuer Floppys scheitern. Einen direkten Workarround bei diesen ISA-Fossilien kenne ich auch nicht.
Es wird dir also wohl nichts anderes uebrig bleiben, als dich damit zu arrangieren. Moeglichkeiten gibt es schliesslich viele (von denen einige eigentlich auch vom BIOS selbst umgesetzt werden koennen sollten, aber egal...).
Naheliegend waere es zB., in einen guenstig gelegenen Buffer einzulesen und von dort Sektor fuer Sektor an die gewuenschte Adresse zu kopieren oder eben selbst genau darauf zu achten, dass du dem DMA mit seinen 64K-Pages nicht in's Gehege kommst.
-
Merkwürdigerweise finde ich zu diesem Thema keine Vorbilder. Bootloader für solche Anwendungen sind offenbar ein eher "verstecktes" Thema. In Foren wird man sofort auf GRUB zugesteuert.
Naja, 64KB (128 Sektoren) ist schon eine Menge für einen Hobby-Kernel.
Immerhin wurde nun die durch 6 Bit bedingte 63-Sektoren-Grenze durch die eigene Berechnung der CHS geschafft. Immer step-by-step oder "festina lente" wie der Lateiner sagt.
-
Evtl hilft auch der offen liegende Code von GRUB weiter? Hab ihn mir interessehalber gerade auch mal angesehen, dies hier müsste der Boot-Code sein (ansonsten DL für alles hier).
-
Danke für den Link zu GRUB!
Ärgerlich finde ich, dass ich den Kernel nun ab 0x10000 beginnen musste. Wir hatten ihn ja extra nach 0x8000 gelegt, um den Speicher zu "schonen". Naja, immerhin versteht man nun besser, warum man typischerweise dort beginnt.
MitGRUBwärDasNichPassiert
In meinem Tutorial versuche ich GRUB solange zu vermeiden, bis es wirklich notwendig wird. Bisher wurde immer ein work-around gefunden. Man lernt mit eigenem Bootloader auch eindeutig mehr über die Basics (z.B. die harte Grenze durch ISA DMA oder die eigene CHS-Berechnung 0,0,1...18 | 0,1,1...18 | 1,0,1...18 | 1,1,1...18 | 2,0,1...18 | ... ).
Damit kann man auch große Kernel nachladen:
org 0x7C00 ; set up start address of bootloader ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; setup a stack and segment regs ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, ax ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; read kernel from floppy disk ; ; starting from C(ylinder) H(ead) S(ector): 0 0 2 ; ; 17 + 18 + 18 + 18 + 57 = 128 (hard segment limit) ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov si,loadmsg ; show loading message call print_string call Waitingloop mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel ; trouble? try again mov bx, 0x1000 mov es, bx ; target in memory: segment mov bx, 0x0000 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 17 ; read n sectors mov ch, 0 ; cylinder = 0 mov cl, 2 ; sector = 2 mov dh, 0 ; head = 0 mov ah, 2 ; function "read" int 0x13 jc load_kernel ; trouble? try again load_kernel2: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel2 ; trouble? try again mov bx, 0x2200 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 18 ; read n sectors mov ch, 0 ; cylinder mov cl, 1 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel2 ; trouble? try again load_kernel3: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel3 ; trouble? try again mov bx, 0x4600 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 18 ; read n sectors mov ch, 1 ; cylinder mov cl, 1 ; sector mov dh, 0 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel3 ; trouble? try again load_kernel4: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel4 ; trouble? try again mov bx, 0x6A00 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 18 ; read n sectors mov ch, 1 ; cylinder mov cl, 1 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel4 ; trouble? try again load_kernel5: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel5 ; trouble? try again mov bx, 0x8E00 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 57 ; read n sectors (max. 57!!! due to max. 128) mov ch, 2 ; cylinder mov cl, 1 ; sector mov dh, 0 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel5 ; trouble? try again ; new segment load_kernel10: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel10 ; trouble? try again mov bx, 0x2000 ; next segment mov es, bx ; target in memory: segment mov bx, 0x0000 ; target in memory: offset mov dl,[bootdrive] ; select boot drive mov al, 63 ; read n sectors mov ch, 3 ; cylinder mov cl, 4 ; sector mov dh, 1 ; head mov ah, 2 ; function "read" int 0x13 jc load_kernel10 ; trouble? try again call Waitingloop ;;;;;;;;;;;;; ; A20-Gate ; ;;;;;;;;;;;;; in al, 0x92 ; switch A20 gate via fast A20 port 92 cmp al, 0xff ; if it reads 0xFF, nothing's implemented on this port je .no_fast_A20 or al, 2 ; set A20_Gate_Bit (bit 1) and al, ~1 ; clear INIT_NOW bit (don't reset pc...) out 0x92, al jmp .A20_done .no_fast_A20: ; no fast shortcut -> use the slow kbc... call empty_8042 mov al, 0xD1 ; kbc command: write to output port out 0x64, al call empty_8042 mov al, 0xDF ; writing this to kbc output port enables A20 out 0x60, al call empty_8042 .A20_done: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Load GDT, switch to PM, and jump to kernel ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; cli ; clear interrupts lgdt [gdtr] ; load GDT via GDTR (defined in file "gtd.inc") mov eax, cr0 ; switch-over to Protected Mode or eax, 1 ; set bit 0 of CR0 register mov cr0, eax ; jmp dword 08:0x10000 ; this is a 16-bit-FAR-Jmp, but CS is loaded with "index" 8 in GDT ; hence, the code will be interpreted as 32 bit from here on ; the address can be found in the linker script (phys) ;;;;;;;;; ; Calls ; ;;;;;;;;; print_string: mov ah, 0x0E ; VGA BIOS fnct. 0x0E: teletype .loop: lodsb ; grab a byte from SI test al, al ; NUL? jz .done ; if the result is zero, get out int 0x10 ; otherwise, print out the character! jmp .loop .done: ret empty_8042: call Waitingloop in al, 0x64 cmp al, 0xff ; ... no real kbc at all? je .done test al, 1 ; something in input buffer? jz .no_output call Waitingloop in al, 0x60 ; yes: read buffer jmp empty_8042 ; and try again .no_output: test al, 2 ; command buffer empty? jnz empty_8042 ; no: we can't send anything new till it's empty .done: ret Waitingloop: mov cx,0xFFFF .loop_start: dec cx jnz .loop_start ret ;;;;;;;;;;;; ; Includes ; ;;;;;;;;;;;; %include "gdt.inc" ;;;;;;;; ; data ; ;;;;;;;; bootdrive db 0 loadmsg db "bootloader message: loading kernel ...",13,10,0 times 510-($-$$) hlt db 0x55 db 0xAA
Nun fehlt nur noch ein netter Algorithmus, der ES:BX und CHS sektorenweise berechnet, dann könnte man dies in einer Schleife für n Sektoren erledigen.
Den Teil "LBA2CHS" findet man hier:
http://en.wikipedia.org/wiki/CHS_conversion#Assembler_code_exampleGetCHS proc mov AX, LBA xor DX, DX mov BX, SPT div BX inc DX mov SECT, DL xor DX, DX mov BX, HPC div BX mov CYL, AL mov HEAD, DL ret GetCHS endp
-
Nun fehlt nur noch ein netter Algorithmus, der ES:BX und CHS sektorenweise berechnet, dann könnte man dies in einer Schleife für n Sektoren erledigen.
Read-Algorithmus wurde von PorkChicken (LowLevel-Forum) vorgeschlagen.
org 0x7C00 ; set up start address of bootloader ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; setup a stack and segment regs ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; xor ax, ax mov ds, ax mov es, ax mov ax, 0x7000 ; stack address mov ss, ax xor sp, sp ; set stackpointer to 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; read kernel from floppy disk ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; read_kernel: mov si,loadmsg call print_string mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; di: number of sectors, bx: segment, ch, dh, cl: cylinder, head, sector mov bx, 500 mov di, bx mov bx, 0x0800 ; kernel start address (cf. linker script) mov ch, 0 mov dh, 0 mov cl, 2 call read_sectors ;;;;;;;;;;;;; ; A20-Gate ; ;;;;;;;;;;;;; switch_a20: in al, 0x92 ; switch A20 gate via fast A20 port 92 cmp al, 0xff ; if it reads 0xFF, nothing's implemented on this port je .no_fast_A20 or al, 2 ; set A20_Gate_Bit (bit 1) and al, ~1 ; clear INIT_NOW bit (don't reset pc...) out 0x92, al jmp .A20_done .no_fast_A20: ; no fast shortcut -> use the slow kbc... call empty_8042 mov al, 0xD1 ; kbc command: write to output port out 0x64, al call empty_8042 mov al, 0xDF ; writing this to kbc output port enables A20 out 0x60, al call empty_8042 .A20_done: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Load GDT, switch to PM, and jump to kernel ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; load_gdt: cli ; clear interrupts lgdt [gdtr] ; load GDT via GDTR (defined in file "gtd.inc") switch_to_PM: mov eax, cr0 ; switch-over to Protected Mode or eax, 1 ; set bit 0 of CR0 register mov cr0, eax ; jmp_to_PM: jmp dword 08:0x00008000 ; this is a 16-bit-FAR-Jmp, but CS is loaded with "index" 8 in GDT ; hence, the code will be interpreted as 32 bit from here on ; the address can be found in the linker script ;;;;;;;;; ; Calls ; ;;;;;;;;; print_string: mov ah, 0x0E ; VGA BIOS fnct. 0x0E: teletype .loop: lodsb ; grab a byte from SI test al, al ; NUL? jz .done ; if the result is zero, get out int 0x10 ; otherwise, print out the character! jmp .loop .done: ret empty_8042: call Waitingloop in al, 0x64 cmp al, 0xff ; ... no real kbc at all? je .done test al, 1 ; something in input buffer? jz .no_output call Waitingloop in al, 0x60 ; yes: read buffer jmp empty_8042 ; and try again .no_output: test al, 2 ; command buffer empty? jnz empty_8042 ; no: we can't send anything new till it's empty .done: ret Waitingloop: mov cx,0xFFFF .loop_start: dec cx jnz .loop_start ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; read sectors from floppy disk to buffer ES:BX in memory ; ; ; ; input: ; ; di - number of sectors ; ; bx - segment ; ; ch, dh, cl - cylinder, head, sector ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; read_sectors: .next: mov es, bx xor bx, bx .again: mov dl, [bootdrive] mov ax, 0x0201 int 0x13 jc .again inc cl cmp cl, 19 jl .loop mov cl, 1 inc dh cmp dh, 2 jl .loop mov dh, 0 inc ch cmp ch, 80 jae .error .loop: mov bx, es add bx, 512/16 sub di, 1 jnz .next .done: ret .error mov si, errormsg ; show error message call print_string .end jmp .end ;;;;;;;;;;;; ; Includes ; ;;;;;;;;;;;; %include "gdt.inc" ;;;;;;;; ; data ; ;;;;;;;; bootdrive db 0 loadmsg db "bootloader message: loading kernel ...",13,10,0 errormsg db "bootloader message: sector read error ...",13,10,0 times 510-($-$$) hlt db 0x55 db 0xAA
Stack wurde von 0x10000 nach 0x7000 verlegt, weil er im Weg war.