protected mode debugging
[bootcensus] / src / boot / boot.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         .code16
18         .section .boot,"a"
19
20         .set stack_top, 0x7be0
21         .set read_retries, 0x7be8
22         .set drive_number, 0x7bec
23         .set cursor_x, 0x7bee
24         .set scratchbuf, 0x7bf0
25         .set scratchbuf_size, 16
26
27 boot:
28         cli
29         # move stack to just below the code
30         xor %ax, %ax
31         mov %ax, %ss
32         mov $stack_top, %sp
33         # use the code segment for data access
34         mov %cs, %ax
35         mov %ax, %ds
36         mov %ax, %es
37
38         mov %dl, drive_number
39
40         call setup_serial
41
42         call get_drive_chs
43
44         mov $loading_msg, %si
45         call print_str
46
47         # load the second stage boot loader and jump to it
48         mov $_boot2_size, %eax
49         call print_num
50
51         mov $_boot2_size, %eax
52         mov %eax, %ebx
53         shr $9, %eax
54         and $0x1ff, %ebx
55         jz 0f
56         inc %ax
57 0:      pushw %ax
58         pushw $1
59         # set es to the start of the destination buffer to allow reading in
60         # full 64k chunks
61         mov $boot2_addr, %bx
62         shr $4, %bx
63         mov %bx, %es
64         xor %bx, %bx
65         call read_sectors
66         jmp boot2_addr
67
68         .set ARG_NSECT, 6
69         .set ARG_SIDX, 4
70
71 loading_msg: .asciz "\nLoad "
72 driveno_msg: .asciz "Drv:"
73
74 sect_per_track: .short 18
75 num_cylinders: .short 80
76 num_heads: .short 2
77 heads_mask: .byte 1
78
79 get_drive_chs:
80         mov $driveno_msg, %si
81         call print_str
82         xor %eax, %eax
83         movb drive_number, %dl
84         mov %dl, %al
85         call print_num
86         mov $10, %al
87         call print_char
88
89         mov $8, %ah
90         int $0x13
91         jnc .Lok
92         ret
93
94 .Lok:   xor %eax, %eax
95         mov %ch, %al
96         mov %cl, %ah
97         rol $2, %ah
98         inc %ax
99         and $0x3ff, %ax
100         mov %ax, num_cylinders
101
102         and $0x3f, %cx
103         mov %cx, sect_per_track
104
105         shr $8, %dx
106         mov %dl, heads_mask
107         inc %dx
108         mov %dx, num_heads
109
110         call print_num
111         mov $47, %al
112         call print_char
113         mov %dx, %ax
114         call print_num
115         mov $47, %al
116         call print_char
117         mov %cx, %ax
118         call print_num
119         ret
120
121 # read_sectors(first, num)
122 read_sectors:
123         push %bp
124         mov %sp, %bp
125
126         mov ARG_SIDX(%bp), %ax
127         xor %cx, %cx
128
129         jmp 1f
130 0:      push %ax
131         call read_sector
132         pop %ax
133         inc %ax
134         inc %cx
135 1:      cmp ARG_NSECT(%bp), %cx
136         jnz 0b
137
138         pop %bp
139         ret
140
141 # read_sector(sidx)
142 read_sector:
143         push %bp
144         mov %sp, %bp
145         push %cx
146         push %dx
147
148         movw $3, read_retries
149
150 .Lread_try:
151         # calculate the track (sidx / sectors_per_track)
152         mov 4(%bp), %ax
153
154         xor %dx, %dx
155         mov sect_per_track, %cx
156         div %cx
157         mov %ax, %cx
158         # save the remainder
159         push %dx
160         # head in dh
161         mov %cl, %dh
162         and heads_mask, %dh
163         # cylinder (track/heads) in ch [0-7] and cl[6,7]<-[8,9]
164         push %dx
165         xor %dx, %dx
166         movw num_heads, %cx
167         div %cx
168         pop %dx
169         mov %ax, %cx
170         rol $8, %cx
171         ror $2, %cl
172         and $0xc0, %cl
173         # sector num cl[0-5] is sidx % sectors_per_track + 1
174         pop %ax
175         inc %al
176         or %al, %cl
177
178         # ah = 2 (read), al = 1 sectors
179         mov $0x0201, %ax
180         movb drive_number, %dl
181         int $0x13
182         jnc .Lread_ok
183
184         # abort after 3 attempts
185         decw read_retries
186         jz .Lread_fail
187
188         # error detected, reset controller and retry
189         xor %ah, %ah
190         int $0x13
191         jmp .Lread_try
192
193 .Lread_fail:
194         mov 4(%bp), %ax
195         jmp abort_read
196
197 .Lread_ok:
198         mov $46, %ax
199         call print_char
200
201         # increment es:bx accordingly (advance es if bx overflows)
202         add $512, %bx
203         jnc 0f
204         mov %es, %ax
205         add $4096, %ax
206         mov %ax, %es
207
208 0:      pop %dx
209         pop %cx
210         pop %bp
211         ret
212
213 str_read_error: .asciz "rderr:"
214
215 abort_read:
216         mov $str_read_error, %si
217         call print_str
218         and $0xffff, %eax
219         call print_num
220         mov $10, %al
221         call print_char
222         hlt
223
224         # expects string pointer in ds:si
225 print_str:
226         pusha
227
228 0:      mov (%si), %al
229         cmp $0, %al
230         jz .Lend
231         call print_char
232         inc %si
233         jmp 0b
234
235 .Lend:  popa
236         ret
237
238         # expects character in al
239 print_char:
240         push %es
241
242         push %ax
243         cmp $10, %ax
244         jnz 0f
245         mov $32, %ax
246
247 0:      pushw $0xb800
248         pop %es
249         movw cursor_x, %di
250         shl $1, %di
251
252         mov %al, %es:(%di)
253         movb $7, %es:1(%di)
254         incw cursor_x
255
256         pop %ax
257         call ser_putchar
258         pop %es
259         ret
260
261         # expects number in eax
262         .global print_num
263 print_num:
264         # save registers
265         pusha
266
267         movw $scratchbuf + scratchbuf_size, %si
268         movb $0, (%si)
269         mov $10, %ebx
270 .Lconvloop:
271         xor %edx, %edx
272         div %ebx
273         add $48, %dl
274         dec %si
275         mov %dl, (%si)
276         cmp $0, %eax
277         jnz .Lconvloop
278
279         call print_str
280
281         # restore regs
282         popa
283         ret
284
285         .set UART_DATA, 0x3f8
286         .set UART_DIVLO, 0x3f8
287         .set UART_DIVHI, 0x3f9
288         .set UART_FIFO, 0x3fa
289         .set UART_LCTL, 0x3fb
290         .set UART_MCTL, 0x3fc
291         .set UART_LSTAT, 0x3fd
292         .set DIV_9600, 115200 / 9600
293         .set LCTL_8N1, 0x03
294         .set LCTL_DLAB, 0x80
295         .set FIFO_ENABLE_CLEAR, 0x07
296         .set MCTL_DTR_RTS_OUT2, 0x0b
297         .set LST_TREG_EMPTY, 0x20
298
299 setup_serial:
300         # set clock divisor
301         mov $LCTL_DLAB, %al
302         mov $UART_LCTL, %dx
303         out %al, %dx
304         mov $DIV_9600, %ax
305         mov $UART_DIVLO, %dx
306         out %al, %dx
307         shr $8, %ax
308         mov $UART_DIVHI, %dx
309         out %al, %dx
310         # set format 8n1
311         mov $LCTL_8N1, %al
312         mov $UART_LCTL, %dx
313         out %al, %dx
314         # clear and enable fifo
315         mov $FIFO_ENABLE_CLEAR, %al
316         mov $UART_FIFO, %dx
317         out %al, %dx
318         # assert RTS and DTR
319         mov $MCTL_DTR_RTS_OUT2, %al
320         mov $UART_MCTL, %dx
321         out %al, %dx
322         ret
323
324         # expects a character in al
325 ser_putchar:
326         push %dx
327
328         cmp $10, %al
329         jnz 0f
330         push %ax
331         mov $13, %al
332         call ser_putchar
333         pop %ax
334
335 0:      mov %al, %ah
336         # wait until the transmit register is empty
337         mov $UART_LSTAT, %dx
338 .Lwait: in %dx, %al
339         and $LST_TREG_EMPTY, %al
340         jz .Lwait
341         mov $UART_DATA, %dx
342         mov %ah, %al
343         out %al, %dx
344
345         pop %dx
346         ret
347         
348         .org 510
349         .byte 0x55
350         .byte 0xaa
351 boot2_addr: