001
002 /*
003 * @(#) $Id: bootsect.S,v 1.12 2005/08/13 13:47:31 d2 Exp $
004 * Description : Bootsecteur en syntaxe AT&T
005 * Auteurs : Thomas Petazzoni & Fabrice Gautier & Emmanuel Marty
006 * Jerome Petazzoni & Bernard Cassagne & coffeeman
007 * David Decotigny (SOS integration for kernel size detection)
008 * Christopher Goyet (RAM size determination through BIOS int 15H)
009 * Bug reports to kos-misc@enix.org
010 */
011
012 /*
013 * But global de ce bootsecteur :
014 *
015 * - Initialiser la becane
016 * - Charger le kernel
017 * - Passer en mode protege
018 * - Executer le kernel
019 *
020 * Taille restante : Je vous rappelle qu'un bootsecteur ne peut faire
021 * qu'au maximum 512 octets dont 2 octets obligatoires 0xAA55. Sur
022 * les 510 octets reellement utilisables, il reste 3 octets dispo (60
023 * si on decide d'enlever le BPB un jour) !!!
024 *
025 * thomas_petazzoni : - detection des codes d'erreurs de chargement
026 * David_Decotigny : - Passage en GNU as
027 * David_Decotigny : - Chargement du noyau au-dela du 1er Mega (taille
028 * max = 0x9e000 octets = 632ko), pour avoir le
029 * meme noyau sous grub et avec le bootsecteur
030 */
031
032 /*
033 * Sequence d'operations :
034 * - Le BIOS charge le bootsect en 0x7c00 (BOOT_ADRESS). On choisit
035 * la representation 0x7c0:0000 pour que le .org 0 reste valide
036 * - Le bootsect se deplace de lui-meme en 0x9f000 (COPY_ADRESS). On
037 * choisit la representation 0x9f00:0000 pour que le .org 0 reste
038 * valide
039 * - Le bootsect verifie que le processeur est du type 386+
040 * - Il charge le noyau depuis la disquette en memoire a partir de
041 * 0x1000 (LOAD_ADRESS). La place dispo est donc 0x9f000 - 0x1000 , soit
042 * 0x9E000, soit encore 1264 secteurs de 512 octets
043 * - Il passe en pmode flat (apres ouverture a20)
044 * - Il recopie le noyau (situe en LOAD_ADRESS) vers son adresse
045 * finale (FINAL_ADDRESS = 2Mo). La recopie se fait sur tout l'espace
046 * LOAD_ADRESS ---> COPY_ADRESS, c'est a dire sur 0x9e000 octets =
047 * 632ko. Le noyau peut donc au max faire 632ko. Le nombre max de
048 * secteurs de disquette qu'on peut charger est donc 1264
049 */
050
051
052 /* La taille de la pile */
053 #define BOOT_STACK_SIZE 0x4000
054
055 .file "bootsect.S"
056
057 /* Tout est place dans une seule section */
058 .section ".bootsect"
059
060 /* L'essentiel du bootsector (sauf les 1eres instructions)
061 sont a un offset 0. On fait en sorte que le compilo soit
062 d'accord la-dessus. Quand on a des adresse realm exotiques
063 (0x7c00, 0x9f000, ...), on s'arrange toujours pour avoir un
064 offset de 0 => on choisira le segment adapte (0x7c0,
065 0x9f00, ...). Il ne faut pas oublier le ld -Ttext 0 */
066 .org 0
067
068 /* Pour que gas genere du 16bits, afin que ca marche en realm */
069 .code16
070
071 /*
072 * Parametres de la disquette. Comme c'est chiant de faire une
073 * procedure de detection auto, et que ca prend de la place, on fait
074 * ca "a la main". Par exemple, une DD 720 Ko a 9 secteurs/piste, une
075 * 1.44 Mo a 18 secteurs/pistes
076 */
077 #define CYLS 80
078 #define HEADS 1
079 #define SECTS 18
080
081 #define BOOT_ADRESS 0x07C00 /* Adresse de demarrage (lineaire) */
082 #define BOOT_SEG (BOOT_ADRESS>>4) /* Segment de Boot */
083 #define BOOT_SIZE 512 /* Taille bu bootsecteur */
084 #define COPY_ADRESS 0x9F000 /* La ou on va copier le
085 bootsecteur (lineaire) */
086 #define COPY_SEG (COPY_ADRESS>>4) /* Segment de la ou on va
087 copier le bootsecteur */
088 #define LOAD_ADRESS 0x01000 /* 1er chargement du systeme */
089 #define LOAD_SEG (LOAD_ADRESS>>4) /* Segment du 1er chargement du */
090 #define MAX_KERN_LEN (COPY_ADRESS-LOAD_ADRESS) /* Taille noyau maxi */
091 #define MAX_KERN_SECTS ((MAX_KERN_LEN + 511) >> 9) /* Nbre de secteurs maxi */
092
093 /* IMPORTANT : Cette valeur DOIT etre identique a l'adresse presente
094 dans sos.lds ! */
095 #define FINAL_ADDRESS 0x200000 /* Adresse finale (physique de 0 a 4G)
096 ou est charge le noyau */
097
098 #define OP16 .byte 0x66 ;
099 #define OP32 .byte 0x66 ;
100
101 /*
102 * Procedure qui vide le buffer clavier.
103 */
104 #define WAITKB \
105 1: ;\
106 .word 0xeb ;\
107 .word 0xeb ;\
108 inb $0x64, %al ;\
109 andb $0x2, %al ;\
110 jnz 1b
111
112 /* Le point d'entree dans le bootsect */
113 .globl _bsect
114 _bsect:
115
116 /*
117 * La portion qui suit est situee a un offset 0x7c00 en
118 * memoire. Attention donc aux references memoire dans cette
119 * partie. On choisit de rester en offset 0 (.org 0), mais on
120 * charge correctement les segments a 0x7c0.
121 */
122
123 movw $BOOT_SEG, %ax /* le bootsecteur est a 0x7C00 en lineaire */
124 movw %ax, %ds /* on le copie a l'adresse COPY_ADRESS */
125 xorw %si, %si /* comme cette adresse est la plus haute de la mem */
126 xorw %di, %di /* on pourra charger un kernel + gros */
127 movw $(BOOT_SIZE>>1), %cx
128 movw $COPY_SEG, %ax
129 movw %ax, %es
130 cld
131 rep ; movsw
132
133 /* on continue a executer le bootsecteur, mais maintenant a
134 partir de 0x9F000, qu'on represente sous la forme
135 0x9f00:offset */
136 ljmp $COPY_SEG, $here
137
138 /*
139 * A partir de maintenant, on est a un offset 0 en memoire
140 * (segment 0x9f00), conformement a ce que veut le compilo.
141 */
142 here:
143 movw %ax, %ds
144
145 /* Petite pile temporaire (1k - 3.84k en RAM ; les adresses 0-1k
146 correspondent au vecteur d'interruptions). */
147 movw %ax, %ss
148 movw $(LOAD_ADRESS - 0x10), %sp
149
150 /* Efface l'ecran */
151 movb $0x0, %ah
152 movb $0x3, %al
153 int $0x10
154
155 /* Verifie que le noyau n'est pas trop gros a charger */
156 cmpw $(MAX_KERN_SECTS), (load_size)
157 jb sizeOk
158 movw $toobig, %si
159 call message
160 call halt
161
162 sizeOk:
163 /* Recupere la taille de la RAM */
164 mov $0xE801, %ax
165 int $0x15
166 movw %ax, (memsize1)
167 movw %bx, (memsize2)
168
169 /* Affiche les messages d'attente */
170 movw $loadkern, %si
171 call message
172 movw $check, %si
173 call message
174
175 check386:
176 /*
177 * la attention, plus complexe : on teste si le proc est un
178 * 386+ pour cela, on va essayer de modifier les bits 12 ? 14
179 * du registre E-flag si la modification reste, alors le proc
180 * est un 386+, sinon, c'est =< 286
181 *
182 * Merci a Emmanuel Marty pour la compatibilite avec les 386
183 * "pre-jurassique"
184 */
185
186 pushf /* on sauvegarde le E-Flag */
187 movb $0x70, %ah
188 pushw %ax
189 popf
190 pushf
191 popw %ax
192 orb %ah, %ah
193 je no386 /* si la modif n'est pas valable, alors on saute a
194 no386 */
195 popf /* on les restaure ? la fin ... */
196
197 /* Message de confirmation de 386+ et d'attente */
198 movw $found386, %si
199 call message
200 movw $loading, %si
201 call message
202
203 /* Copie du noyau disquette => RAM a partir de 0x1000
204 L'adresse de destination est définie par es:0, où es vaut
205 initialement 0x100 (ie correspond alors à l'adresse 256*16, soit 4
206 ko). Chaque itération incrémente ce registre es de 32, ce qui
207 correspond à un bond de 32*16 en mémoire, soit la taille d'un
208 secteur. De cette façon, puisqu'on joue sur les segments plutôt que
209 sur les offsets, la taille du noyau n'est pas limitée à 64 ko. Elle
210 est limitée par contre à la taille de la mémoire disponible sous
211 les 1Mo, \ie 640 ko (0x9f000 - 0x1000). */
212 copyKernel:
213 /* Chargement du noyau en LOAD_SEG:0 */
214 /* 3 iterateurs :
215 - load_size : le nbre de secteurs a charger
216 - cl : le secteur ou on en est pour le
217 cylindre en cours (<= SECTS)
218 - dh : la tete en cours (0/1)
219 */
220 movb $0, %dl
221 movw $LOAD_SEG, %ax
222 movw %ax, %es
223
224 xorw %bx, %bx
225 xorw %dx, %dx
226 movw $1, %cx /* premier secteur */
227
228 .nextsector: /* prochain secteur */
229 incb %cl /* en incrementant CL */
230 cmpb $SECTS, %cl /* si CL =< SECTS (=nbre de secteurs/pistes)
231 alors on charge */
232 jbe .sector
233 movb $1, %cl /* sinon on revient au secteur 1 */
234 incb %dh /* mais sur l'autre tete */
235 cmpb $1, %dh /* on recompare, si DH =< 1 */
236 je .sector /* on charge */
237 movb $0, %dh /* sinon on repasse a la tete 0 */
238 incb %ch /* mais on change de cylindre */
239
240 .sector:
241 pushw %es
242 movw $0x0201, %ax /* service 0x2, chargement 0x1 seecteur */
243 int $0x13 /* Go ! */
244 jc halt /* erreur */
245 popw %ax
246 addw $32, %ax /* on a charge un secteur, donc on doit
247 charger 512 bytes plus loin */
248 movw %ax, %es /* on avance donc le segment du buffer de
249 32bytes, ie 1 secteur en RAM (car 32*16=512) */
250
251 movw $(0x0E*256+'.'), %ax /* affiche un point */
252 int $0x10
253
254 decw (load_size) /* et on repart pour le prochain secteur
255 tant qu'on n'a pas fini ! */
256 jnz .nextsector
257
258 after:
259 movw $0x03f2, %dx
260 inb %dx, %al /* stoppe le moteur */
261 andb $0x0f, %al
262 outb %al, %dx
263
264 cli /* on interdit les interruptions */
265
266 fincopie:
267 pushw %cs
268 popw %ds
269
270 /* on ouvre la porte A20 */
271 WAITKB /* on vide le buffer */
272 movb $0xd1, %al /* on met a jour le port */
273 outb %al, $0x64
274 WAITKB
275 movb $0xdf, %al /* bit 2 = ouverture/fermeture */
276 outb %al, $0x60
277
278 /*
279 * init gdt
280 */
281 InitGDT:
282 /* Préparation du flat mode */
283 lgdt gdtr
284
285 GoPMode:
286 /* Passage en mode protégé */
287 movl %cr0, %eax
288 orb $1, %al /* set PE bit to 1 */
289 movl %eax, %cr0
290
291 /* we are not yet in Pmode jump 'in' pmode clearing prefetch
292 * queue and loading a new selector */
293 movw $0x10, %ax
294 movw %ax, %ds
295 movw %ax, %es
296 movw %ax, %fs
297 movw %ax, %gs
298
299 /*
300 * Code 32 bits ============================================================
301 */
302 .code32
303
304 JumpToHere32: /* Se deplace a l'endroit actuel, en passant en 32bits
305 et en utilisant la gdt, et vide la prefetch queue */
306 .byte 0x66 /* Prefixe 32bits : en realite, jusqu'au jmp, on est
307 encore en 16 bits */
308 ljmp $0x8, $(COPY_ADRESS+(Here32))
309 Here32:
310 /* Et voila : On est en 32 bits vrai */
311
312 MoveKernelToFinalAddr: /* Deplace le noyau (en LOAD_ADDRESS) vers sa
313 destination finale (FINAL_ADDRESS) */
314 movl $0x10, %eax
315 movl %eax, %ds /* Seg Src = DSeg */
316 movl %eax, %es /* Sed Dest = DSeg */
317 cld
318 movl $LOAD_ADRESS, %esi /* On commence la copie au debut du noyau */
319 movl $FINAL_ADDRESS, %edi /* On copie vers cette adresse */
320 movl $MAX_KERN_LEN, %ecx /* Taille recopie */
321 shrl $2, %ecx
322 rep
323 movsl
324
325 LaunchKernel:
326 /* Met en place une pile au niveau du symbole "stack" */
327 movl %eax, %ss
328 movl $(stack + BOOT_STACK_SIZE), %ebp
329 movl %ebp, %esp
330
331 /* passe les arguments a sos */
332 xor %eax, %eax
333 xor %ebx, %ebx
334 movw (COPY_ADRESS+(memsize2)), %ax /*eax = num de block de 64KB apres 16MB*/
335 movw (COPY_ADRESS+(memsize1)), %bx /*ebx = num de block de 1KB entre 1MB et 16MB*/
336 movl $0x40, %ecx /*ecx=64 */
337 mul %ecx
338 add %ebx, %eax
339 pushl %eax /* valeur de addr */
340 pushl $0x42244224 /* valeur de magic pour indiquer qu'on a pousse
341 la taille de la RAM sur la pile */
342 pushl $0 /* normalement call fait un push eip, mais la on a un jmp*/
343
344 /* Saut vers le noyau. La GDT est en place (flat mode), les
345 * selecteurs aussi, a20 est ouverte, et les interruptions sont
346 * cli + pas de idt. Le PIC n'est pas programme */
347 ljmp $0x8, $sos_main
348
349 /*
350 * Utilities ============================================================
351 */
352 .code16
353
354 message:
355 lodsb /* charge ds:si dans al et incremente si */
356 orb %al, %al /* si al = 0 */
357 jz 1f
358 movb $0x0e, %ah /* service 0Eh (affichage d'un caractere) */
359 movw $0x0007, %bx /* Parametres : blanc sur fond noir */
360 int $0x10 /* Appel de l'interruption 10h */
361 jmp message /* On repart au début ... */
362 1: ret /* si la chaine est finie alors on retourne
363 dans la fonction appelante */
364
365 halt:
366 pushw %cs
367 popw %es
368 movw $haltmsg, %si
369 call message
370 cli
371 1: jmp 1b
372 ret
373
374 no386:
375 movw $need386, %si
376 call message
377 call halt
378
379 /*
380 * GDT
381 */
382
383 gdt:
384 gdtr:
385 NULL_Desc:
386 .word (EndGDT)-(gdt)-1 /* Taille GDT */
387 .long (gdt)+COPY_ADRESS
388 unused:
389 .word 0
390
391 CS_Desc: /* 0x8 */
392 .word 0xFFFF, 0
393 .byte 0, 0x9B, 0xCF, 0
394
395 DS_Desc: /* 0x10 */
396 .word 0xFFFF, 0
397 .byte 0, 0x93, 0xCF, 0
398
399 EndGDT:
400
401 /* quelques messages */
402
403 loadkern: .string "This is SOS\r\n"
404 toobig: .string "Image too big\r\n"
405 check: .string "Checking 386+ processor... "
406 found386: .string " [OK]\r\n"
407 need386: .string " [FAILED]\r\n"
408 diskerror: .string "Disk Error\r\n"
409 loading: .string "Loading... "
410 haltmsg: .string "System Halted\r\n"
411
412 /* Variables pour stocker la taille de la RAM (int 0x15) */
413 memsize1: .long 0
414 memsize2: .long 0
415
416 /*** Les code/données du boot secteur se terminent ICI. le marqueur de
417 * fin (aa55) est ajouté automatiquement par le script ld
418 * sos_bsect.lds ***/
419
420 /* La pile de 16k qu'on utilise au niveau de LaunchKernel se trouve
421 declaree avec le noyau, dans sa section ".init_stack", cad HORS du boot
422 secteur ! (sinon ca depasserait 512B, forcément). On aurait pu la
423 définir directement dans le sos_bsect.lds, ou dans un fichier .c
424 auxiliaire pour plus de clarté */
425 /* Here is the stack */
426 .section ".init_stack", "aw", @nobits
427 .p2align 4
428 .size stack, BOOT_STACK_SIZE
429 stack:
430 .space BOOT_STACK_SIZE
431
432 /* Some data characterizing the stack addresses */
433 .data
434 .globl bootstrap_stack_bottom
435 bootstrap_stack_bottom: .long stack
436
437 .globl bootstrap_stack_size
438 bootstrap_stack_size: .long BOOT_STACK_SIZE