- memory detection with BIOS 0x15/0xe820
[bootcensus] / src / boot / boot2.s
1 # pcboot - bootable PC demo/game kernel
2 # Copyright (C) 2018  John Tsiombikas <nuclear@member.fsf.org>
3
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY, without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17 # this is the second-stage boot loader
18         .code16
19         .section .boot2,"ax"
20
21         .set main_load_addr, 0x100000
22
23         # make sure any BIOS call didn't re-enable interrupts
24         cli
25
26         # enter unreal mode
27         call unreal
28
29         movb $10, %al
30         call ser_putchar
31
32         call clearscr
33
34         # enable A20 address line
35         call enable_a20
36
37         # detect available memory
38         call detect_memory
39
40         # load the whole program into memory starting at 1MB
41         call load_main
42
43         #mov $0x13, %ax
44         #int $0x10
45
46         # load initial GDT
47         lgdt (gdt_lim)
48         # load initial IDT
49         lidt (idt_lim)
50
51         # enter protected mode for the first time
52         mov %cr0, %eax
53         or $1, %eax
54         mov %eax, %cr0
55         # inter-segment jump to set cs selector to segment 1
56         ljmp $0x8,$0f
57
58         .code32
59         # set all data selectors to segment 2
60 0:      mov $0x10, %ax
61         mov %ax, %ds
62         mov %ax, %ss
63         mov %ax, %es
64         mov %ax, %gs
65         mov %ax, %fs
66
67         jmp main_load_addr
68
69         cli
70 0:      hlt
71         jmp 0b
72
73
74
75         .align 4
76 gdt_lim: .word 23
77 gdt_base:.long gdt
78
79         .align 4
80 idt_lim: .word 111
81 idt_base:.long idt
82
83
84         .align 8
85 gdt:    # 0: null segment
86         .long 0
87         .long 0
88         # 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
89         .long 0x0000ffff
90         .long 0x00cf9a00
91         # 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
92         .long 0x0000ffff
93         .long 0x00cf9200
94
95
96         .align 8
97 idt:    .space 104
98         # trap gate 13: general protection fault
99         .short prot_fault
100         .short 0x8
101         # type: trap, present, default
102         .short 0x8f00
103         .short 0
104
105 gpf_msg: .asciz "GP fault "
106
107 prot_fault:
108         mov (%esp), %eax
109         shr $3, %eax
110         call print_num
111         mov $64, %al
112         call putchar
113         mov 4(%esp), %eax
114         call print_num
115         mov $10, %al
116         call putchar
117         hlt
118
119         .code16
120 unreal:
121         # use the same GDT above, will use data segment: 2
122         lgdt (gdt_lim)
123
124         mov %cr0, %eax
125         or $1, %ax
126         mov %eax, %cr0
127         jmp 0f
128
129 0:      mov $0x10, %ax
130         mov %ax, %ds
131         mov %ax, %es
132         mov %ax, %fs
133         mov %ax, %gs
134         mov %ax, %ss
135
136         mov %cr0, %eax
137         and $0xfffe, %ax
138         mov %eax, %cr0
139
140         xor %ax, %ax
141         mov %ax, %ds
142         mov %ax, %es
143         mov %ax, %fs
144         mov %ax, %gs
145         mov %ax, %ss
146         ret
147
148 mainsz_msg: .asciz "Main program size: "
149 mainsz_msg2: .asciz " ("
150 mainsz_msg3: .asciz " sectors)\n"
151
152 first_sect: .long 0
153 sect_left: .long 0
154 cur_track: .long 0
155 trk_sect: .long 0
156 dest_ptr: .long 0
157
158 load_main:
159         movl $main_load_addr, dest_ptr
160
161         # calculate first sector
162         mov $_boot2_size, %eax
163         add $511, %eax
164         shr $9, %eax
165         # add 1 to account for the boot sector
166         inc %eax
167         mov %eax, first_sect
168
169         # calculate the first track (first_sect / sect_per_track)
170         movzxw sect_per_track, %ecx
171         xor %edx, %edx
172         div %ecx
173         mov %eax, cur_track
174         # remainder is sector within track
175         mov %edx, trk_sect
176
177         mov $mainsz_msg, %esi
178         call putstr
179         mov $_main_size, %eax
180         mov %eax, %ecx
181         call print_num
182
183         mov $mainsz_msg2, %esi
184         call putstr
185
186         # calculate sector count
187         add $511, %eax
188         shr $9, %eax
189         mov %eax, sect_left
190
191         call print_num
192         mov $mainsz_msg3, %esi
193         call putstr
194
195         # read a whole track into the buffer (or partial first track)
196 ldloop:
197         movzxw sect_per_track, %ecx
198         sub trk_sect, %ecx
199         push %ecx
200         call read_track
201
202         # debug: print the first 32bits of the track
203         #mov buffer, %eax
204         #call print_num
205         #mov $10, %al
206         #call putchar
207
208         # copy to high memory
209         mov $buffer, %esi
210         mov dest_ptr, %edi
211         mov (%esp), %ecx
212         shl $9, %ecx
213         add %ecx, dest_ptr
214         shr $2, %ecx
215         addr32 rep movsl
216
217         incl cur_track
218         # other than the first track which might be partial, all the rest start from 0
219         movl $0, trk_sect
220
221         pop %ecx
222         sub %ecx, sect_left
223         ja ldloop
224
225         # the BIOS might have enabled interrupts
226         cli
227
228         # just in case we were loaded from floppy, turn all floppy motors off
229         mov $0x3f2, %dx
230         in %dx, %al
231         and $0xf0, %al
232         out %al, %dx
233
234         mov $10, %ax
235         call putchar
236
237         ret
238
239 rdtrk_msg: .asciz "Reading track: "
240 rdcyl_msg: .asciz " - cyl: "
241 rdhead_msg: .asciz " head: "
242 rdsect_msg: .asciz " start sect: "
243 rdlast_msg: .asciz " ... "
244 rdok_msg: .asciz "OK\n"
245 rdfail_msg: .asciz "failed\n"
246
247 read_retries: .short 0
248
249         .set drive_number, 0x7bec
250 read_track:
251         # set es to the start of the destination buffer to allow reading in
252         # full 64k chunks if necessary
253         mov $buffer, %bx
254         shr $4, %bx
255         mov %bx, %es
256         xor %ebx, %ebx
257
258         movw $3, read_retries
259
260 read_try:
261         # print track
262         mov $rdtrk_msg, %esi
263         call putstr
264         mov cur_track, %eax
265         call print_num
266         mov $rdcyl_msg, %esi
267         call putstr
268
269         # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
270         mov cur_track, %eax
271         movzxw num_heads, %ecx
272         xor %edx, %edx
273         div %ecx
274
275         # print cylinder
276         push %eax
277         call print_num
278         # print head
279         mov $rdhead_msg, %esi
280         call putstr
281         movzx %dx, %eax
282         call print_num
283         pop %eax
284
285         # head in dh
286         mov %dl, %dh
287
288         # cylinder low byte at ch and high bits at cl[7, 6]
289         mov %al, %ch
290         mov %ah, %cl
291         and $3, %cl
292         ror $2, %cl
293
294         # print start sector
295         mov $rdsect_msg, %esi
296         call putstr
297         mov trk_sect, %eax
298         call print_num
299         mov $rdlast_msg, %esi
300         call putstr
301
302         # start sector (1-based) in cl[0, 5]
303         mov trk_sect, %al
304         inc %al
305         and $0x3f, %al
306         or %al, %cl
307
308         # number of sectors in al
309         mov 2(%esp), %ax
310         # call number (2) in ah
311         mov $2, %ah
312         # drive number in dl
313         movb drive_number, %dl
314         int $0x13
315         jnc read_ok
316
317         # abort after 3 attempts
318         decw read_retries
319         jz read_fail
320
321         # error, reset controller and retry
322         xor %ah, %ah
323         int $0x13
324         jmp read_try
325
326 read_fail:
327         mov $rdfail_msg, %esi
328         call putstr
329         jmp abort_read
330
331 read_ok:
332         mov $rdok_msg, %esi
333         call putstr
334
335         # reset es to 0 before returning
336         xor %ax, %ax
337         mov %ax, %es
338         ret
339
340 str_read_error: .asciz "Read error while reading track: "
341
342 abort_read:
343         mov $str_read_error, %esi
344         call putstr
345         mov cur_track, %eax
346         call print_num
347         mov $10, %al
348         call putchar
349
350         cli
351 0:      hlt
352         jmp 0b
353
354
355         # better print routines, since we're not constrainted by the 512b of
356         # the boot sector.
357         .global cursor_x
358         .global cursor_y
359 cursor_x: .long 0
360 cursor_y: .long 0
361
362 putchar:
363         pushal
364         call ser_putchar
365
366         cmp $10, %al
367         jnz 0f
368         call video_newline
369         jmp 1f
370
371 0:      push %eax
372         mov cursor_y, %eax
373         mov $80, %ecx
374         mul %ecx
375         add cursor_x, %eax
376         mov %eax, %ebx
377         pop %eax
378
379         mov $0xb8000, %edx
380
381         # this looks retarded. in nasm: [ebx * 2 + edx]
382         mov %al, (%edx, %ebx, 2)
383         movb $7, 1(%edx, %ebx, 2)
384         incl cursor_x
385         cmpl $80, cursor_x
386         jnz 1f
387         call video_newline
388
389 1:      popal
390         ret
391         
392         # expects string pointer in esi
393 putstr:
394         mov (%esi), %al
395         cmp $0, %al
396         jz 0f
397         call putchar
398         inc %esi
399         jmp putstr
400 0:      ret
401
402         # expects number in eax
403 print_num:
404         # save registers
405         pushal
406
407         mov $numbuf + 16, %esi
408         movb $0, (%esi)
409         mov $10, %ebx
410 convloop:
411         xor %edx, %edx
412         div %ebx
413         add $48, %dl
414         dec %esi
415         mov %dl, (%esi)
416         cmp $0, %eax
417         jnz convloop
418
419         call putstr
420
421         # restore regs
422         popal
423         ret
424
425
426 video_newline:
427         movl $0, cursor_x
428         incl cursor_y
429         cmpl $25, cursor_y
430         jnz 0f
431         call scrollup
432         decl cursor_y
433 0:      ret
434
435 scrollup:
436         pushal
437         # move 80 * 24 lines from b80a0 -> b8000
438         mov $0xb8000, %edi
439         mov $0xb80a0, %esi
440         mov $960, %ecx
441         addr32 rep movsl
442         # clear last line (b8f00)
443         mov $0xb8f00, %edi
444         xor %eax, %eax
445         mov $40, %ecx
446         addr32 rep stosl
447         popal
448         ret
449
450 clearscr:
451         mov $0xb8000, %edi
452         xor %eax, %eax
453         mov $1000, %ecx
454         addr32 rep stosl
455         ret
456
457         .set UART_DATA, 0x3f8
458         .set UART_LSTAT, 0x3fd
459         .set LST_TREG_EMPTY, 0x20
460
461 ser_putchar:
462         push %dx
463
464         cmp $10, %al
465         jnz 0f
466         push %ax
467         mov $13, %al
468         call ser_putchar
469         pop %ax
470
471 0:      mov %al, %ah
472         # wait until the transmit register is empty
473         mov $UART_LSTAT, %dx
474 wait:   in %dx, %al
475         and $LST_TREG_EMPTY, %al
476         jz wait
477         mov $UART_DATA, %dx
478         mov %ah, %al
479         out %al, %dx
480
481         pop %dx
482         ret
483
484
485
486 ena20_msg: .asciz "A20 line enabled\n"
487
488 enable_a20:
489         call test_a20
490         jnc a20done
491         call enable_a20_kbd
492         call test_a20
493         jnc a20done
494         call enable_a20_fast
495         call test_a20
496         jnc a20done
497         # keep trying ... we can't do anything useful without A20 anyway
498         jmp enable_a20
499 a20done:
500         mov $ena20_msg, %esi
501         call putstr
502         ret
503
504         # CF = 1 if A20 test fails (not enabled)
505 test_a20:
506         mov $0x07c000, %ebx
507         mov $0x17c000, %edx
508         movl $0xbaadf00d, (%ebx)
509         movl $0xaabbcc42, (%edx)
510         subl $0xbaadf00d, (%ebx)
511         ret
512
513         # enable A20 line through port 0x92 (fast A20)
514 enable_a20_fast:
515         mov $ena20_fast_msg, %esi
516         call putstr
517
518         in $0x92, %al
519         or $2, %al
520         out %al, $0x92
521         ret
522
523 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
524
525
526         # enable A20 line through the keyboard controller
527         .set KBC_DATA_PORT, 0x60
528         .set KBC_CMD_PORT, 0x64
529         .set KBC_STATUS_PORT, 0x64
530         .set KBC_CMD_RD_OUTPORT, 0xd0
531         .set KBC_CMD_WR_OUTPORT, 0xd1
532
533         .set KBC_STAT_OUT_RDY, 0x01
534         .set KBC_STAT_IN_FULL, 0x02
535
536 enable_a20_kbd:
537         mov $ena20_kbd_msg, %esi
538         call putstr
539
540         call kbc_wait_write
541         mov $KBC_CMD_WR_OUTPORT, %al
542         out %al, $KBC_CMD_PORT
543         call kbc_wait_write
544         mov $0xdf, %al
545         out %al, $KBC_DATA_PORT
546         ret
547
548 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
549
550         # wait until the keyboard controller is ready to accept another byte
551 kbc_wait_write:
552         in $KBC_STATUS_PORT, %al
553         and $KBC_STAT_IN_FULL, %al
554         jnz kbc_wait_write
555         ret
556
557 numbuf: .space 16
558
559
560         # sets the carry flag on failure
561 detect_memory:
562         mov $buffer, %edi
563         xor %ebx, %ebx
564         mov $0x534d4150, %edx
565
566 memdet_looptop:
567         mov $0xe820, %eax
568         mov $24, %ecx
569         int $0x15
570         jc memdet_fail
571         cmp $0x534d4150, %eax
572         jnz memdet_fail
573
574         mov buffer, %eax
575         mov $boot_mem_map, %esi
576         mov boot_mem_map_size, %ebp
577         # again, that's [ebp * 8 + esi]
578         mov %eax, (%esi,%ebp,8)
579
580         # only care for type 1 (usable ram), otherwise ignore
581         cmpl $1, 16(%edi)
582         jnz memdet_skip
583
584         # skip areas with 0 size (also clamp size to 4gb)
585         # test high 32bits
586         cmpl $0, 12(%edi)
587         jz memdet_highzero
588         # high part is non-zero, make low part ffffffff
589         xor %eax, %eax
590         not %eax
591         jmp 0f
592
593 memdet_highzero:
594         # if both high and low parts are zero, ignore
595         mov 8(%edi), %eax
596         cmpl $0, %eax
597         jz memdet_skip
598
599 0:      mov %eax, 4(%esi,%ebp,8)
600         incl boot_mem_map_size
601
602 memdet_skip:
603         # terminate the loop if ebx was reset to 0
604         cmp $0, %ebx
605         jz memdet_done
606         jmp memdet_looptop
607
608 memdet_done:
609         ret
610
611 memdet_fail:
612         # if size > 0, then it's not a failure, just the end
613         cmpl $0, boot_mem_map_size
614         jnz memdet_done
615
616         # just panic...
617         mov $memdet_fail_msg, %esi
618         call putstr
619 0:      hlt
620         jmp 0b
621
622 memdet_fail_msg: .asciz "Failed to detect available memory!\n"
623
624         .global boot_mem_map_size
625 boot_mem_map_size: .long 0
626         .global boot_mem_map
627 boot_mem_map: .space 128
628
629
630 # this is not boot loader code. It's called later on by the main kernel
631 # code in 32bit protected mode. It's placed here because it needs to be
632 # located in base memory as it returns and runs in real mode.
633         .code32
634         .align 4
635         # place to save the protected mode IDTR pseudo-descriptor
636         # with sidt, so that it can be restored before returning
637         .short 0
638 saved_idtr:
639 idtlim: .short 0
640 idtaddr:.long 0
641         # real mode IDTR pseudo-descriptor pointing to the IVT at addr 0
642         .short 0
643 rmidt:  .short 0x3ff
644         .long 0
645
646 saved_esp: .long 0
647 saved_ebp: .long 0
648
649         # drop back to unreal mode to call 16bit interrupt
650         .global int86
651 int86:
652         push %ebp
653         mov %esp, %ebp
654         pushal
655         cli
656         # save protected mode IDTR and replace it with the real mode vectors
657         sidt (saved_idtr)
658         lidt (rmidt)
659
660         # modify the int instruction do this here before the
661         # cs-load jumps, to let them flush the instruction cache
662         mov $int_op, %ebx
663         movb 8(%ebp), %al
664         movb %al, 1(%ebx)
665
666         # long jump to load code selector for 16bit code (6)
667         ljmp $0x30,$0f
668 0:
669         .code16
670         # disable protection
671         mov %cr0, %eax
672         and $0xfffe, %ax
673         mov %eax, %cr0
674         # load cs <- 0
675         ljmp $0,$0f
676 0:      # zero data segments
677         xor %ax, %ax
678         mov %ax, %ds
679         mov %ax, %es
680         mov %ax, %ss
681         nop
682
683         # load registers from the int86regs struct
684         mov %esp, saved_esp
685         mov %ebp, saved_ebp
686         mov 12(%ebp), %esp
687         popal
688         mov saved_esp, %esp
689
690         # call 16bit interrupt
691 int_op: int $0
692
693         mov saved_ebp, %ebp
694         mov 12(%ebp), %esp
695         add $34, %esp
696         pushfw
697         pushal
698         mov saved_esp, %esp
699
700         # re-enable protection
701         mov %cr0, %eax
702         or $1, %ax
703         mov %eax, %cr0
704         # long jump to load code selector for 32bit code (1)
705         ljmp $0x8,$0f
706 0:
707         .code32
708         # set data selector (2) to all segment regs
709         mov $0x10, %ax
710         mov %ax, %ds
711         mov %ax, %es
712         mov %ax, %ss
713         nop
714
715         # restore 32bit interrupt descriptor table
716         lidt (saved_idtr)
717         sti
718         popal
719         pop %ebp
720         ret
721
722
723         # buffer used by the track loader ... to load tracks.
724         .align 16
725 buffer: