main kernel startup, libc, console tty, asmops, build flags fixes
[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         # load the whole program into memory starting at 1MB
38         call load_main
39
40         #mov $0x13, %ax
41         #int $0x10
42
43         # load initial GDT
44         lgdt (gdt_lim)
45         # load initial IDT
46         lidt (idt_lim)
47
48         # enter protected mode for the first time
49         mov %cr0, %eax
50         or $1, %eax
51         mov %eax, %cr0
52         # inter-segment jump to set cs selector to segment 1
53         ljmp $0x8,$0f
54
55         .code32
56         # set all data selectors to segment 2
57 0:      mov $0x10, %ax
58         mov %ax, %ds
59         mov %ax, %ss
60         mov %ax, %es
61         mov %ax, %gs
62         mov %ax, %fs
63
64         jmp main_load_addr
65
66         cli
67 0:      hlt
68         jmp 0b
69
70
71
72         .align 4
73 gdt_lim: .word 23
74 gdt_base:.long gdt
75
76         .align 4
77 idt_lim: .word 111
78 idt_base:.long idt
79
80
81         .align 8
82 gdt:    # 0: null segment
83         .long 0
84         .long 0
85         # 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
86         .long 0x0000ffff
87         .long 0x00cf9a00
88         # 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
89         .long 0x0000ffff
90         .long 0x00cf9200
91
92
93         .align 8
94 idt:    .space 104
95         # trap gate 13: general protection fault
96         .short prot_fault
97         .short 0x8
98         # type: trap, present, default
99         .short 0x8f00
100         .short 0
101
102 gpf_msg: .asciz "GP fault "
103
104 prot_fault:
105         mov (%esp), %eax
106         shr $3, %eax
107         call print_num
108         mov $64, %al
109         call putchar
110         mov 4(%esp), %eax
111         call print_num
112         mov $10, %al
113         call putchar
114         hlt
115
116         .code16
117 unreal:
118         # use the same GDT above, will use data segment: 2
119         lgdt (gdt_lim)
120
121         mov %cr0, %eax
122         or $1, %ax
123         mov %eax, %cr0
124         jmp 0f
125
126 0:      mov $0x10, %ax
127         mov %ax, %ds
128         mov %ax, %es
129         mov %ax, %fs
130         mov %ax, %gs
131         mov %ax, %ss
132
133         mov %cr0, %eax
134         and $0xfffe, %ax
135         mov %eax, %cr0
136
137         xor %ax, %ax
138         mov %ax, %ds
139         mov %ax, %es
140         mov %ax, %fs
141         mov %ax, %gs
142         mov %ax, %ss
143         ret
144
145 mainsz_msg: .asciz "Main program size: "
146 mainsz_msg2: .asciz " ("
147 mainsz_msg3: .asciz " sectors)\n"
148
149 first_sect: .long 0
150 sect_left: .long 0
151 cur_track: .long 0
152 trk_sect: .long 0
153 dest_ptr: .long 0
154
155 load_main:
156         movl $main_load_addr, dest_ptr
157
158         # calculate first sector
159         mov $_boot2_size, %eax
160         add $511, %eax
161         shr $9, %eax
162         # add 1 to account for the boot sector
163         inc %eax
164         mov %eax, first_sect
165
166         # calculate the first track (first_sect / sect_per_track)
167         movzxw sect_per_track, %ecx
168         xor %edx, %edx
169         div %ecx
170         mov %eax, cur_track
171         # remainder is sector within track
172         mov %edx, trk_sect
173
174         mov $mainsz_msg, %esi
175         call putstr
176         mov $_main_size, %eax
177         mov %eax, %ecx
178         call print_num
179
180         mov $mainsz_msg2, %esi
181         call putstr
182
183         # calculate sector count
184         add $511, %eax
185         shr $9, %eax
186         mov %eax, sect_left
187
188         call print_num
189         mov $mainsz_msg3, %esi
190         call putstr
191
192         # read a whole track into the buffer (or partial first track)
193 ldloop:
194         movzxw sect_per_track, %ecx
195         sub trk_sect, %ecx
196         push %ecx
197         call read_track
198
199         # debug: print the first 32bits of the track
200         #mov buffer, %eax
201         #call print_num
202         #mov $10, %al
203         #call putchar
204
205         # copy to high memory
206         mov $buffer, %esi
207         mov dest_ptr, %edi
208         mov (%esp), %ecx
209         shl $9, %ecx
210         add %ecx, dest_ptr
211         shr $2, %ecx
212         addr32 rep movsl
213
214         incl cur_track
215         # other than the first track which might be partial, all the rest start from 0
216         movl $0, trk_sect
217
218         pop %ecx
219         sub %ecx, sect_left
220         ja ldloop
221
222         # the BIOS might have enabled interrupts
223         cli
224
225         # just in case we were loaded from floppy, turn all floppy motors off
226         mov $0x3f2, %dx
227         in %dx, %al
228         and $0xf0, %al
229         out %al, %dx
230
231         mov $10, %ax
232         call putchar
233
234         ret
235
236 rdtrk_msg: .asciz "Reading track: "
237 rdcyl_msg: .asciz " - cyl: "
238 rdhead_msg: .asciz " head: "
239 rdsect_msg: .asciz " start sect: "
240 rdlast_msg: .asciz " ... "
241 rdok_msg: .asciz "OK\n"
242 rdfail_msg: .asciz "failed\n"
243
244 read_retries: .short 0
245
246         .set drive_number, 0x7bec
247 read_track:
248         # set es to the start of the destination buffer to allow reading in
249         # full 64k chunks if necessary
250         mov $buffer, %bx
251         shr $4, %bx
252         mov %bx, %es
253         xor %ebx, %ebx
254
255         movw $3, read_retries
256
257 read_try:
258         # print track
259         mov $rdtrk_msg, %esi
260         call putstr
261         mov cur_track, %eax
262         call print_num
263         mov $rdcyl_msg, %esi
264         call putstr
265
266         # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
267         mov cur_track, %eax
268         movzxw num_heads, %ecx
269         xor %edx, %edx
270         div %ecx
271
272         # print cylinder
273         push %eax
274         call print_num
275         # print head
276         mov $rdhead_msg, %esi
277         call putstr
278         movzx %dx, %eax
279         call print_num
280         pop %eax
281
282         # head in dh
283         mov %dl, %dh
284
285         # cylinder low byte at ch and high bits at cl[7, 6]
286         mov %al, %ch
287         mov %ah, %cl
288         and $3, %cl
289         ror $2, %cl
290
291         # print start sector
292         mov $rdsect_msg, %esi
293         call putstr
294         mov trk_sect, %eax
295         call print_num
296         mov $rdlast_msg, %esi
297         call putstr
298
299         # start sector (1-based) in cl[0, 5]
300         mov trk_sect, %al
301         inc %al
302         and $0x3f, %al
303         or %al, %cl
304
305         # number of sectors in al
306         mov 2(%esp), %ax
307         # call number (2) in ah
308         mov $2, %ah
309         # drive number in dl
310         movb drive_number, %dl
311         int $0x13
312         jnc read_ok
313
314         # abort after 3 attempts
315         decw read_retries
316         jz read_fail
317
318         # error, reset controller and retry
319         xor %ah, %ah
320         int $0x13
321         jmp read_try
322
323 read_fail:
324         mov $rdfail_msg, %esi
325         call putstr
326         jmp abort_read
327
328 read_ok:
329         mov $rdok_msg, %esi
330         call putstr
331
332         # reset es to 0 before returning
333         xor %ax, %ax
334         mov %ax, %es
335         ret
336
337 str_read_error: .asciz "Read error while reading track: "
338
339 abort_read:
340         mov $str_read_error, %esi
341         call putstr
342         mov cur_track, %eax
343         call print_num
344         mov $10, %al
345         call putchar
346
347         cli
348 0:      hlt
349         jmp 0b
350
351
352         # better print routines, since we're not constrainted by the 512b of
353         # the boot sector.
354         .global cursor_x
355         .global cursor_y
356 cursor_x: .long 0
357 cursor_y: .long 0
358
359 putchar:
360         pushal
361         call ser_putchar
362
363         cmp $10, %al
364         jnz 0f
365         call video_newline
366         jmp 1f
367
368 0:      push %eax
369         mov cursor_y, %eax
370         mov $80, %ecx
371         mul %ecx
372         add cursor_x, %eax
373         mov %eax, %ebx
374         pop %eax
375
376         mov $0xb8000, %edx
377
378         # this looks retarded. in nasm: [ebx * 2 + edx]
379         mov %al, (%edx, %ebx, 2)
380         movb $7, 1(%edx, %ebx, 2)
381         incl cursor_x
382         cmpl $80, cursor_x
383         jnz 1f
384         call video_newline
385
386 1:      popal
387         ret
388         
389         # expects string pointer in esi
390 putstr:
391         mov (%esi), %al
392         cmp $0, %al
393         jz 0f
394         call putchar
395         inc %esi
396         jmp putstr
397 0:      ret
398
399         # expects number in eax
400 print_num:
401         # save registers
402         pushal
403
404         mov $numbuf + 16, %esi
405         movb $0, (%esi)
406         mov $10, %ebx
407 convloop:
408         xor %edx, %edx
409         div %ebx
410         add $48, %dl
411         dec %esi
412         mov %dl, (%esi)
413         cmp $0, %eax
414         jnz convloop
415
416         call putstr
417
418         # restore regs
419         popal
420         ret
421
422
423 video_newline:
424         movl $0, cursor_x
425         incl cursor_y
426         cmpl $25, cursor_y
427         jnz 0f
428         call scrollup
429         decl cursor_y
430 0:      ret
431
432 scrollup:
433         pushal
434         # move 80 * 24 lines from b80a0 -> b8000
435         mov $0xb8000, %edi
436         mov $0xb80a0, %esi
437         mov $960, %ecx
438         addr32 rep movsl
439         # clear last line (b8f00)
440         mov $0xb8f00, %edi
441         xor %eax, %eax
442         mov $40, %ecx
443         addr32 rep stosl
444         popal
445         ret
446
447 clearscr:
448         mov $0xb8000, %edi
449         xor %eax, %eax
450         mov $1000, %ecx
451         addr32 rep stosl
452         ret
453
454         .set UART_DATA, 0x3f8
455         .set UART_LSTAT, 0x3fd
456         .set LST_TREG_EMPTY, 0x20
457
458 ser_putchar:
459         push %dx
460
461         cmp $10, %al
462         jnz 0f
463         push %ax
464         mov $13, %al
465         call ser_putchar
466         pop %ax
467
468 0:      mov %al, %ah
469         # wait until the transmit register is empty
470         mov $UART_LSTAT, %dx
471 wait:   in %dx, %al
472         and $LST_TREG_EMPTY, %al
473         jz wait
474         mov $UART_DATA, %dx
475         mov %ah, %al
476         out %al, %dx
477
478         pop %dx
479         ret
480
481
482
483 ena20_msg: .asciz "A20 line enabled\n"
484
485 enable_a20:
486         call test_a20
487         jnc a20done
488         call enable_a20_kbd
489         call test_a20
490         jnc a20done
491         call enable_a20_fast
492         call test_a20
493         jnc a20done
494         # keep trying ... we can't do anything useful without A20 anyway
495         jmp enable_a20
496 a20done:
497         mov $ena20_msg, %esi
498         call putstr
499         ret
500
501         # CF = 1 if A20 test fails (not enabled)
502 test_a20:
503         mov $0x07c000, %ebx
504         mov $0x17c000, %edx
505         movl $0xbaadf00d, (%ebx)
506         movl $0xaabbcc42, (%edx)
507         subl $0xbaadf00d, (%ebx)
508         ret
509
510         # enable A20 line through port 0x92 (fast A20)
511 enable_a20_fast:
512         mov $ena20_fast_msg, %esi
513         call putstr
514
515         in $0x92, %al
516         or $2, %al
517         out %al, $0x92
518         ret
519
520 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
521
522
523         # enable A20 line through the keyboard controller
524         .set KBC_DATA_PORT, 0x60
525         .set KBC_CMD_PORT, 0x64
526         .set KBC_STATUS_PORT, 0x64
527         .set KBC_CMD_RD_OUTPORT, 0xd0
528         .set KBC_CMD_WR_OUTPORT, 0xd1
529
530         .set KBC_STAT_OUT_RDY, 0x01
531         .set KBC_STAT_IN_FULL, 0x02
532
533 enable_a20_kbd:
534         mov $ena20_kbd_msg, %esi
535         call putstr
536
537         call kbc_wait_write
538         mov $KBC_CMD_WR_OUTPORT, %al
539         out %al, $KBC_CMD_PORT
540         call kbc_wait_write
541         mov $0xdf, %al
542         out %al, $KBC_DATA_PORT
543         ret
544
545 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
546
547         # wait until the keyboard controller is ready to accept another byte
548 kbc_wait_write:
549         in $KBC_STATUS_PORT, %al
550         and $KBC_STAT_IN_FULL, %al
551         jnz kbc_wait_write
552         ret
553
554 numbuf: .space 16
555
556         .align 16
557 buffer: