debugging loading full program in unreal mode
[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,"a"
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         mov buffer, %eax
200         call print_num
201         mov $10, %al
202         call putchar
203
204         # copy to high memory
205         mov $buffer, %esi
206         mov dest_ptr, %edi
207         mov (%esp), %ecx
208         shl $9, %ecx
209         add %ecx, dest_ptr
210         shr $2, %ecx
211         addr32 rep movsl
212
213         incl cur_track
214         # other than the first track which might be partial, all the rest start from 0
215         movl $0, trk_sect
216
217         pop %ecx
218         sub %ecx, sect_left
219         ja ldloop
220
221         # the BIOS might have enabled interrupts
222         cli
223
224         # just in case we were loaded from floppy, turn all floppy motors off
225         mov $0x3f2, %dx
226         in %dx, %al
227         and $0xf0, %al
228         out %al, %dx
229
230         mov $10, %ax
231         call putchar
232
233         # DBG
234         hlt
235
236         ret
237
238 rdtrk_msg: .asciz "Reading track: "
239 rdcyl_msg: .asciz " - cyl: "
240 rdhead_msg: .asciz " head: "
241 rdsect_msg: .asciz " start sect: "
242
243 read_retries: .short 0
244
245         .set drive_number, 0x7bec
246 read_track:
247         # set es to the start of the destination buffer to allow reading in
248         # full 64k chunks if necessary
249         mov $buffer, %bx
250         shr $4, %bx
251         mov %bx, %es
252         xor %ebx, %ebx
253
254         movw $3, read_retries
255
256 read_try:
257         # print track
258         mov $rdtrk_msg, %esi
259         call putstr
260         mov cur_track, %eax
261         call print_num
262         mov $rdcyl_msg, %esi
263         call putstr
264
265         # calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
266         mov cur_track, %eax
267         movzxw num_heads, %ecx
268         xor %edx, %edx
269         div %ecx
270
271         # print cylinder
272         push %eax
273         call print_num
274         # print head
275         mov $rdhead_msg, %esi
276         call putstr
277         movzx %dx, %eax
278         call print_num
279         pop %eax
280
281         # head in dh
282         mov %dl, %dh
283
284         # cylinder low byte at ch and high bits at cl[7, 6]
285         mov %al, %ch
286         mov %ah, %cl
287         and $3, %cl
288         ror $2, %cl
289
290         # print start sector
291         mov $rdsect_msg, %esi
292         call putstr
293         mov trk_sect, %eax
294         call print_num
295         mov $10, %al
296         call putchar
297
298         # start sector (1-based) in cl[0, 5]
299         mov trk_sect, %al
300         inc %al
301         and $0x3f, %al
302         or %al, %cl
303
304         # number of sectors in al
305         mov 2(%esp), %ax
306         # call number (2) in ah
307         mov $2, %ah
308         # drive number in dl
309         movb drive_number, %dl
310         int $0x13
311         jnc read_ok
312
313         # abort after 3 attempts
314         decw read_retries
315         jz read_fail
316
317         # error, reset controller and retry
318         xor %ah, %ah
319         int $0x13
320         jmp read_try
321
322 read_fail:
323         jmp abort_read
324
325 read_ok:
326         mov $35, %ax
327         call putchar
328
329         # reset es to 0 before returning
330         xor %ax, %ax
331         mov %ax, %es
332         ret
333
334 str_read_error: .asciz "Read error while reading track: "
335
336 abort_read:
337         mov $str_read_error, %esi
338         call putstr
339         mov cur_track, %eax
340         call print_num
341         mov $10, %al
342         call putchar
343
344         cli
345 0:      hlt
346         jmp 0b
347
348
349
350         # better print routines, since we're not constrainted by the 512b of
351         # the boot sector.
352 cursor_x: .long 0
353 cursor_y: .long 0
354
355 putchar:
356         pusha
357         call ser_putchar
358
359         cmp $10, %al
360         jnz 0f
361         call video_newline
362         jmp 1f
363
364 0:      push %eax
365         mov cursor_y, %eax
366         mov $80, %ecx
367         mul %ecx
368         add cursor_x, %eax
369         mov %eax, %ebx
370         pop %eax
371
372         mov $0xb8000, %edx
373
374         # this looks retarded. in nasm: [ebx * 2 + edx]
375         mov %al, (%edx, %ebx, 2)
376         movb $7, 1(%edx, %ebx, 2)
377         incl cursor_x
378         cmpl $80, cursor_x
379         jnz 1f
380         call video_newline
381
382 1:      popa
383         ret
384         
385         # expects string pointer in esi
386 putstr:
387         mov (%esi), %al
388         cmp $0, %al
389         jz 0f
390         call putchar
391         inc %esi
392         jmp putstr
393 0:      ret
394
395         # expects number in eax
396 print_num:
397         # save registers
398         pusha
399
400         mov $numbuf + 16, %esi
401         movb $0, (%esi)
402         mov $10, %ebx
403 convloop:
404         xor %edx, %edx
405         div %ebx
406         add $48, %dl
407         dec %esi
408         mov %dl, (%esi)
409         cmp $0, %eax
410         jnz convloop
411
412         call putstr
413
414         # restore regs
415         popa
416         ret
417
418
419 video_newline:
420         movl $0, cursor_x
421         incl cursor_y
422         cmpl $25, cursor_y
423         jnz 0f
424         call scrollup
425         decl cursor_y
426 0:      ret
427
428 scrollup:
429         pusha
430         # move 80 * 24 lines from b80a0 -> b8000
431         mov $0xb8000, %edi
432         mov $0xb80a0, %esi
433         mov $960, %ecx
434         addr32 rep movsl
435         # clear last line (b8f00)
436         mov $0xb8f00, %edi
437         xor %eax, %eax
438         mov $40, %ecx
439         addr32 rep stosl
440         popa
441         ret
442
443 clearscr:
444         mov $0xb8000, %edi
445         xor %eax, %eax
446         mov $1000, %ecx
447         addr32 rep stosl
448         ret
449
450         .set UART_DATA, 0x3f8
451         .set UART_LSTAT, 0x3fd
452         .set LST_TREG_EMPTY, 0x20
453
454 ser_putchar:
455         push %dx
456
457         cmp $10, %al
458         jnz 0f
459         push %ax
460         mov $13, %al
461         call ser_putchar
462         pop %ax
463
464 0:      mov %al, %ah
465         # wait until the transmit register is empty
466         mov $UART_LSTAT, %dx
467 wait:   in %dx, %al
468         and $LST_TREG_EMPTY, %al
469         jz wait
470         mov $UART_DATA, %dx
471         mov %ah, %al
472         out %al, %dx
473
474         pop %dx
475         ret
476
477
478
479 ena20_msg: .asciz "A20 line enabled\n"
480
481 enable_a20:
482         call test_a20
483         jnc a20done
484         call enable_a20_kbd
485         call test_a20
486         jnc a20done
487         call enable_a20_fast
488         call test_a20
489         jnc a20done
490         # keep trying ... we can't do anything useful without A20 anyway
491         jmp enable_a20
492 a20done:
493         mov $ena20_msg, %esi
494         call putstr
495         ret
496
497         # CF = 1 if A20 test fails (not enabled)
498 test_a20:
499         mov $0x07c000, %ebx
500         mov $0x17c000, %edx
501         movl $0xbaadf00d, (%ebx)
502         movl $0xaabbcc42, (%edx)
503         subl $0xbaadf00d, (%ebx)
504         ret
505
506         # enable A20 line through port 0x92 (fast A20)
507 enable_a20_fast:
508         mov $ena20_fast_msg, %esi
509         call putstr
510
511         in $0x92, %al
512         or $2, %al
513         out %al, $0x92
514         ret
515
516 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
517
518
519         # enable A20 line through the keyboard controller
520         .set KBC_DATA_PORT, 0x60
521         .set KBC_CMD_PORT, 0x64
522         .set KBC_STATUS_PORT, 0x64
523         .set KBC_CMD_RD_OUTPORT, 0xd0
524         .set KBC_CMD_WR_OUTPORT, 0xd1
525
526         .set KBC_STAT_OUT_RDY, 0x01
527         .set KBC_STAT_IN_FULL, 0x02
528
529 enable_a20_kbd:
530         mov $ena20_kbd_msg, %esi
531         call putstr
532
533         call kbc_wait_write
534         mov $KBC_CMD_WR_OUTPORT, %al
535         out %al, $KBC_CMD_PORT
536         call kbc_wait_write
537         mov $0xdf, %al
538         out %al, $KBC_DATA_PORT
539         ret
540
541 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
542
543         # wait until the keyboard controller is ready to accept another byte
544 kbc_wait_write:
545         in $KBC_STATUS_PORT, %al
546         and $KBC_STAT_IN_FULL, %al
547         jnz kbc_wait_write
548         ret
549
550 numbuf: .space 16
551
552         .align 16
553 buffer: