summaryrefslogtreecommitdiffstats
path: root/sos-code-article4/extra/bootsect.S
blob: f01ca2055484a3777aad0041008e8b955faf0c6e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393


/*
 * @(#) $Id: bootsect.S,v 1.6 2004/06/18 07:43:51 d2 Exp $
 * Description : Bootsecteur en syntaxe AT&T
 * Auteurs : Thomas Petazzoni & Fabrice Gautier & Emmanuel Marty
 *       Jerome Petazzoni & Bernard Cassagne & coffeeman
 *       David Decotigny
 * Bug reports to kos-misc@enix.org
 */

/*
 * But global de ce bootsecteur :
 *
 *      - Initialiser la becane
 *      - Charger le kernel
 *      - Passer en mode protege
 *      - Executer le kernel
 *
 * Taille restante : Je vous rappelle qu'un bootsecteur ne peut faire
 * qu'au maximum 512 octets dont 2 octets obligatoires 0xAA55.  Sur
 * les 510 octets reellement utilisables, il reste 3 octets dispo (60
 * si on decide d'enlever le BPB un jour) !!!
 *
 * thomas_petazzoni :  - detection des codes d'erreurs de chargement
 * David_Decotigny  :  - Passage en GNU as
 * David_Decotigny  :  - Chargement du noyau au-dela du 1er Mega (taille
 *                       max = 0x9e000 octets = 632ko), pour avoir le
 *                       meme noyau sous grub et avec le bootsecteur
 */

 /*
  * Sequence d'operations :
  * - Le BIOS charge le bootsect en 0x7c00 (BOOT_ADRESS). On choisit
  *   la representation 0x7c0:0000 pour que le .org 0 reste valide
  * - Le bootsect se deplace de lui-meme en 0x9f000 (COPY_ADRESS). On
  *   choisit la representation 0x9f00:0000 pour que le .org 0 reste
  *   valide
  * - Le bootsect verifie que le processeur est du type 386+
  * - Il charge le noyau depuis la disquette en memoire a partir de
  *   0x1000 (LOAD_ADRESS). Le noyau peut au max tenir sur
  *   SECTORS_TO_LOAD secteurs
  * - Il passe en pmode flat (apres ouverture a20)
  * - Il recopie le noyau (situe en LOAD_ADRESS) vers son adresse
  *   finale (FINAL_ADDRESS = 2Mo). La recopie se fait sur tout l'espace
  *   LOAD_ADRESS ---> COPY_ADRESS, c'est a dire sur 0x9e000 octets =
  *   632ko. Le noyau peut donc au max faire 632ko. Le nombre max de
  *   secteurs de disquette qu'on peut charger est donc 1264
  */


/* La taille de la pile */
#define BOOT_STACK_SIZE         0x4000

    .file   "bootsect.S"

    /* Tout est place dans une seule section */
    .section ".bootsect"

    /* L'essentiel du bootsector (sauf les 1eres instructions)
       sont a un offset 0. On fait en sorte que le compilo soit
       d'accord la-dessus. Quand on a des adresse realm exotiques
       (0x7c00, 0x9f000, ...), on s'arrange toujours pour avoir un
       offset de 0 => on choisira le segment adapte (0x7c0,
       0x9f00, ...). Il ne faut pas oublier le ld -Ttext 0 */
    .org 0

    /* Pour que gas genere du 16bits, afin que ca marche en realm */
    .code16

#define SECTORS_TO_LOAD 128 /* 64 ko */ /* MAX=1264 */

/*
 * Parametres de la disquette. Comme c'est chiant de faire une
 * procedure de detection auto, et que ca prend de la place, on fait
 * ca "a la main". Par exemple, une DD 720 Ko a 9 secteurs/piste, une
 * 1.44 Mo a 18 secteurs/pistes
 */
#define CYLS    80
#define HEADS   1
#define SECTS   18

#define BOOT_ADRESS 0x07C00            /* Adresse de demarrage (lineaire) */
#define BOOT_SEG (BOOT_ADRESS>>4)      /* Segment de Boot */
#define BOOT_SIZE 512              /* Taille bu bootsecteur */
#define COPY_ADRESS 0x9F000            /* La ou on va copier le
                      bootsecteur (lineaire) */
#define COPY_SEG (COPY_ADRESS>>4)      /* Segment de la ou on va
                      copier le bootsecteur */
#define LOAD_ADRESS 0x01000            /* 1er chargement du systeme */
#define LOAD_SEG (LOAD_ADRESS>>4)      /* Segment du 1er chargement du */
#define MAX_KERN_LEN COPY_ADRESS-LOAD_ADRESS /* Taille noyau maxi */

/* IMPORTANT : Cette valeur DOIT etre identique a l'adresse presente
           dans sos.lds ! */
#define FINAL_ADDRESS 0x200000         /* Adresse finale (physique de 0 a 4G)
                                      ou est charge le noyau */

#define OP16 .byte 0x66 ;
#define OP32 .byte 0x66 ;

/*
 * Procedure qui vide le buffer clavier.
 */
#define WAITKB     \
  1:               ;\
    .word 0xeb     ;\
    .word 0xeb     ;\
    inb $0x64, %al ;\
    andb $0x2, %al ;\
    jnz 1b

    /* Le point d'entree dans le bootsect */
.globl _bsect
_bsect:

    /*
     * La portion qui suit est situee a un offset 0x7c00 en
     * memoire. Attention donc aux references memoire dans cette
     * partie. On choisit de rester en offset 0 (.org 0), mais on
         * charge correctement les segments a 0x7c0.
         */

    movw $BOOT_SEG, %ax /* le bootsecteur est a 0x7C00 en lineaire */
    movw %ax, %ds      /* on le copie a l'adresse COPY_ADRESS */
    xorw %si, %si      /* comme cette adresse est la plus haute de la mem */
    xorw %di, %di      /* on pourra charger un kernel + gros */
    movw $(BOOT_SIZE>>1), %cx
    movw $COPY_SEG, %ax
    movw %ax, %es
    cld
    rep ; movsw
            
    /* on continue a executer le bootsecteur, mais maintenant a
           partir de 0x9F000, qu'on represente sous la forme
           0x9f00:offset */
    ljmp $COPY_SEG, $here

    /*
     * A partir de maintenant, on est a un offset 0 en memoire
         * (segment 0x9f00), conformement a ce que veut le compilo.
     */
here:
    movw %ax, %ds

    /* Petite pile temporaire (1k - 3.84k en RAM ; les adresses 0-1k
       correspondent au vecteur d'interruptions). */
    movw %ax, %ss
        movw $(LOAD_ADRESS - 0x10), %sp
        
    /* Efface l'ecran */
    movb $0x0, %ah
    movb $0x3, %al
    int     $0x10

    /* Affiche les messages d'attente */
    movw $loadkern, %si
    call message
    movw $check, %si
    call message

check386:
    /*
     * la attention, plus complexe : on teste si le proc est un
     * 386+ pour cela, on va essayer de modifier les bits 12 ? 14
     * du registre E-flag si la modification reste, alors le proc
     * est un 386+, sinon, c'est =< 286
     *
     * Merci a Emmanuel Marty pour la compatibilite avec les 386
     * "pre-jurassique"
     */

    pushf /* on sauvegarde le E-Flag */
    movb $0x70, %ah
    pushw %ax
    popf
    pushf
    popw %ax
    orb %ah, %ah
    je no386  /* si la modif n'est pas valable, alors on saute a
                     no386 */
    popf      /* on les restaure ? la fin ... */

    /* Message de confirmation de 386+ et d'attente */
        movw $found386, %si
    call message
    movw $loading, %si
    call message

/* Copie du noyau disquette => RAM a partir de 0x1000
   L'adresse de destination est définie par es:0, où es vaut
   initialement 0x100 (ie correspond alors à l'adresse 256*16, soit 4
   ko). Chaque itération incrémente ce registre es de 32, ce qui
   correspond à un bond de 32*16 en mémoire, soit la taille d'un
   secteur. De cette façon, puisqu'on joue sur les segments plutôt que
   sur les offsets, la taille du noyau n'est pas limitée à 64 ko. Elle
   est limitée par contre à la taille de la mémoire disponible sous
   les 1Mo, \ie 640 ko (0x9f000 - 0x1000).  */
copyKernel:
    /* Chargement du noyau en LOAD_SEG:0 */
    /* 3 iterateurs :
        - load_size : le nbre de secteurs a charger
        - cl : le secteur ou on en est pour le
          cylindre en cours (<= SECTS)
        - dh : la tete en cours (0/1)
    */
    movb $0, %dl
    movw $LOAD_SEG, %ax
    movw %ax, %es

    xorw %bx, %bx
    xorw %dx, %dx
    movw $1, %cx     /*  premier secteur */

.nextsector:             /* prochain secteur */
    incb %cl         /* en incrementant CL */
    cmpb $SECTS, %cl /* si CL =< SECTS (=nbre de secteurs/pistes)
                alors on charge */
    jbe .sector
    movb $1, %cl     /* sinon on revient au secteur 1 */
    incb %dh         /* mais sur l'autre tete */
    cmpb $1, %dh     /* on recompare, si DH =< 1 */
    je .sector   /* on charge */
    movb $0, %dh     /* sinon on repasse a la tete 0 */
    incb %ch         /* mais on change de cylindre */

.sector:
    pushw %es
    movw $0x0201, %ax /* service 0x2, chargement 0x1 seecteur */
    int $0x13          /* Go ! */
    jc halt           /* erreur */
    popw %ax
    addw $32, %ax     /* on a charge un secteur, donc on doit
                 charger 512 bytes plus loin */
    movw %ax, %es     /* on avance donc le segment du buffer de
                             32bytes, ie 1 secteur en RAM (car 32*16=512) */

    movw $(0x0E*256+'.'), %ax /* affiche un point */
    int $0x10

    decw (load_size)     /* et on repart pour le prochain secteur
                             tant qu'on n'a pas fini ! */
    jnz .nextsector

after:
    movw $0x03f2, %dx
    inb  %dx, %al  /* stoppe le moteur */
        andb $0x0f, %al
        outb %al, %dx

        cli               /* on interdit les interruptions */

fincopie:        
        pushw %cs
        popw  %ds

    /* on ouvre la porte A20 */
        WAITKB           /* on vide le buffer */
    movb $0xd1, %al /* on met a jour le port */
    outb %al, $0x64
    WAITKB
    movb $0xdf, %al /* bit 2 = ouverture/fermeture */
    outb %al, $0x60

    /*
     * init gdt
     */
InitGDT:
    /* Préparation du flat mode */
        lgdt gdtr

GoPMode:
    /* Passage en mode protégé */
    movl %cr0, %eax
    orb  $1, %al /* set PE bit to 1 */
    movl %eax, %cr0

    /* we are not yet in Pmode jump 'in' pmode clearing prefetch
     * queue and loading a new selector */
    movw $0x10, %ax
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %fs
    movw %ax, %gs

/*
 * Code 32 bits ============================================================
 */
    .code32 

JumpToHere32: /* Se deplace a l'endroit actuel, en passant en 32bits
             et en utilisant la gdt, et vide la prefetch queue */
    .byte 0x66 /* Prefixe 32bits : en realite, jusqu'au jmp, on est
                      encore en 16 bits */
    ljmp $0x8, $(COPY_ADRESS+(Here32))
Here32:
    /* Et voila : On est en 32 bits vrai */

MoveKernelToFinalAddr: /* Deplace le noyau (en LOAD_ADDRESS) vers sa
                      destination finale (FINAL_ADDRESS) */
    movl $0x10, %eax
    movl %eax, %ds   /* Seg Src  = DSeg */
    movl %eax, %es   /* Sed Dest = DSeg */
    cld
    movl $LOAD_ADRESS, %esi    /* On commence la copie au debut du noyau */
    movl $FINAL_ADDRESS, %edi  /* On copie vers cette adresse */
    movl $MAX_KERN_LEN, %ecx   /* Taille recopie */
    shrl $2, %ecx
    rep
    movsl

LaunchKernel:
    /* Met en place une pile au niveau du symbole "stack" */
    movl %eax, %ss
    movl $(stack + BOOT_STACK_SIZE), %ebp
    movl %ebp, %esp
    
    /* Saut vers le noyau. La GDT est en place (flat mode), les
     * selecteurs aussi, a20 est ouverte, et les interruptions sont
     * cli + pas de idt. Le PIC n'est pas programme */
    ljmp $0x8, $sos_main

/*
 * Utilities ============================================================
 */
    .code16

message:
        lodsb             /* charge ds:si dans al et incremente si */
    orb %al, %al      /* si al = 0 */
    jz 1f
    movb $0x0e, %ah   /* service 0Eh (affichage d'un caractere) */
    movw $0x0007, %bx /* Parametres : blanc sur fond noir */
        int $0x10          /* Appel de l'interruption 10h */
        jmp message       /* On repart au début ... */
     1: ret               /* si la chaine est finie alors on retourne
                             dans la fonction appelante */

halt:
    pushw %cs
    popw %es
    movw $haltmsg, %si
        call message
    cli
     1: jmp 1b
        ret

no386:
    movw $need386, %si
    call message
    call halt

    /*
     * GDT
     */

gdt:
gdtr:
NULL_Desc:
    .word (EndGDT)-(gdt)-1 /* Taille GDT */
    .long (gdt)+COPY_ADRESS
unused: 
    .word   0

CS_Desc: /* 0x8 */
    .word   0xFFFF, 0
    .byte   0, 0x9B, 0xCF, 0

DS_Desc: /* 0x10 */
    .word   0xFFFF, 0
    .byte   0, 0x93, 0xCF, 0

EndGDT:

     /* quelques messages */

loadkern:  .string      "-= S O S =- : The Simple Operating System \r\n"
check:     .string      "Checking for a 386+ processor... "
found386:  .string      " [OK]\r\n"
need386:   .string      " [FAILED]\r\n"
diskerror: .string  "Disk Error\r\n"
loading:   .string  "Loading... "
haltmsg:   .string  "System Halted\r\n"

/*** Les code/données du boot secteur se terminent ICI. le marqueur de
 * fin (aa55) est ajouté automatiquement par le script ld
 * sos_bsect.lds ***/

/* La pile de 16k qu'on utilise au niveau de LaunchKernel se trouve
   declaree avec le noyau, dans sa section ".bss", cad HORS du boot
   secteur ! (sinon ca depasserait 512B, forcément). On aurait pu la
   définir directement dans le sos_bsect.lds, ou dans un fichier .c
   auxiliaire pour plus de clarté */
.comm   stack, BOOT_STACK_SIZE