second stage boot loader now loads the main program
[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         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         ret
234
235 rdtrk_msg: .asciz "Reading track: "
236 rdcyl_msg: .asciz " - cyl: "
237 rdhead_msg: .asciz " head: "
238 rdsect_msg: .asciz " start sect: "
239 rdlast_msg: .asciz " ... "
240 rdok_msg: .asciz "OK\n"
241 rdfail_msg: .asciz "failed\n"
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 $rdlast_msg, %esi
296         call putstr
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         mov $rdfail_msg, %esi
324         call putstr
325         jmp abort_read
326
327 read_ok:
328         mov $rdok_msg, %esi
329         call putstr
330
331         # reset es to 0 before returning
332         xor %ax, %ax
333         mov %ax, %es
334         ret
335
336 str_read_error: .asciz "Read error while reading track: "
337
338 abort_read:
339         mov $str_read_error, %esi
340         call putstr
341         mov cur_track, %eax
342         call print_num
343         mov $10, %al
344         call putchar
345
346         cli
347 0:      hlt
348         jmp 0b
349
350
351
352         # better print routines, since we're not constrainted by the 512b of
353         # the boot sector.
354 cursor_x: .long 0
355 cursor_y: .long 0
356
357 putchar:
358         pusha
359         call ser_putchar
360
361         cmp $10, %al
362         jnz 0f
363         call video_newline
364         jmp 1f
365
366 0:      push %eax
367         mov cursor_y, %eax
368         mov $80, %ecx
369         mul %ecx
370         add cursor_x, %eax
371         mov %eax, %ebx
372         pop %eax
373
374         mov $0xb8000, %edx
375
376         # this looks retarded. in nasm: [ebx * 2 + edx]
377         mov %al, (%edx, %ebx, 2)
378         movb $7, 1(%edx, %ebx, 2)
379         incl cursor_x
380         cmpl $80, cursor_x
381         jnz 1f
382         call video_newline
383
384 1:      popa
385         ret
386         
387         # expects string pointer in esi
388 putstr:
389         mov (%esi), %al
390         cmp $0, %al
391         jz 0f
392         call putchar
393         inc %esi
394         jmp putstr
395 0:      ret
396
397         # expects number in eax
398 print_num:
399         # save registers
400         pusha
401
402         mov $numbuf + 16, %esi
403         movb $0, (%esi)
404         mov $10, %ebx
405 convloop:
406         xor %edx, %edx
407         div %ebx
408         add $48, %dl
409         dec %esi
410         mov %dl, (%esi)
411         cmp $0, %eax
412         jnz convloop
413
414         call putstr
415
416         # restore regs
417         popa
418         ret
419
420
421 video_newline:
422         movl $0, cursor_x
423         incl cursor_y
424         cmpl $25, cursor_y
425         jnz 0f
426         call scrollup
427         decl cursor_y
428 0:      ret
429
430 scrollup:
431         pusha
432         # move 80 * 24 lines from b80a0 -> b8000
433         mov $0xb8000, %edi
434         mov $0xb80a0, %esi
435         mov $960, %ecx
436         addr32 rep movsl
437         # clear last line (b8f00)
438         mov $0xb8f00, %edi
439         xor %eax, %eax
440         mov $40, %ecx
441         addr32 rep stosl
442         popa
443         ret
444
445 clearscr:
446         mov $0xb8000, %edi
447         xor %eax, %eax
448         mov $1000, %ecx
449         addr32 rep stosl
450         ret
451
452         .set UART_DATA, 0x3f8
453         .set UART_LSTAT, 0x3fd
454         .set LST_TREG_EMPTY, 0x20
455
456 ser_putchar:
457         push %dx
458
459         cmp $10, %al
460         jnz 0f
461         push %ax
462         mov $13, %al
463         call ser_putchar
464         pop %ax
465
466 0:      mov %al, %ah
467         # wait until the transmit register is empty
468         mov $UART_LSTAT, %dx
469 wait:   in %dx, %al
470         and $LST_TREG_EMPTY, %al
471         jz wait
472         mov $UART_DATA, %dx
473         mov %ah, %al
474         out %al, %dx
475
476         pop %dx
477         ret
478
479
480
481 ena20_msg: .asciz "A20 line enabled\n"
482
483 enable_a20:
484         call test_a20
485         jnc a20done
486         call enable_a20_kbd
487         call test_a20
488         jnc a20done
489         call enable_a20_fast
490         call test_a20
491         jnc a20done
492         # keep trying ... we can't do anything useful without A20 anyway
493         jmp enable_a20
494 a20done:
495         mov $ena20_msg, %esi
496         call putstr
497         ret
498
499         # CF = 1 if A20 test fails (not enabled)
500 test_a20:
501         mov $0x07c000, %ebx
502         mov $0x17c000, %edx
503         movl $0xbaadf00d, (%ebx)
504         movl $0xaabbcc42, (%edx)
505         subl $0xbaadf00d, (%ebx)
506         ret
507
508         # enable A20 line through port 0x92 (fast A20)
509 enable_a20_fast:
510         mov $ena20_fast_msg, %esi
511         call putstr
512
513         in $0x92, %al
514         or $2, %al
515         out %al, $0x92
516         ret
517
518 ena20_fast_msg: .asciz "Attempting fast A20 enable\n"
519
520
521         # enable A20 line through the keyboard controller
522         .set KBC_DATA_PORT, 0x60
523         .set KBC_CMD_PORT, 0x64
524         .set KBC_STATUS_PORT, 0x64
525         .set KBC_CMD_RD_OUTPORT, 0xd0
526         .set KBC_CMD_WR_OUTPORT, 0xd1
527
528         .set KBC_STAT_OUT_RDY, 0x01
529         .set KBC_STAT_IN_FULL, 0x02
530
531 enable_a20_kbd:
532         mov $ena20_kbd_msg, %esi
533         call putstr
534
535         call kbc_wait_write
536         mov $KBC_CMD_WR_OUTPORT, %al
537         out %al, $KBC_CMD_PORT
538         call kbc_wait_write
539         mov $0xdf, %al
540         out %al, $KBC_DATA_PORT
541         ret
542
543 ena20_kbd_msg: .asciz "Attempting KBD A20 enable\n"
544
545         # wait until the keyboard controller is ready to accept another byte
546 kbc_wait_write:
547         in $KBC_STATUS_PORT, %al
548         and $KBC_STAT_IN_FULL, %al
549         jnz kbc_wait_write
550         ret
551
552 numbuf: .space 16
553
554         .align 16
555 buffer: