print_num clobbers eax, we need to reload _boot2_size after it returns
[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 "Drive: "
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         pushw $0xb800
242         pop %es
243         movw cursor_x, %di
244         shl $1, %di
245
246         mov %al, %es:(%di)
247         movb $7, %es:1(%di)
248         incw cursor_x
249
250         call ser_putchar
251         pop %es
252         ret
253
254         # expects number in eax
255         .global print_num
256 print_num:
257         # save registers
258         pusha
259
260         movw $scratchbuf + scratchbuf_size, %si
261         movb $0, (%si)
262         mov $10, %ebx
263 .Lconvloop:
264         xor %edx, %edx
265         div %ebx
266         add $48, %dl
267         dec %si
268         mov %dl, (%si)
269         cmp $0, %eax
270         jnz .Lconvloop
271
272         call print_str
273
274         # restore regs
275         popa
276         ret
277
278         .set UART_DATA, 0x3f8
279         .set UART_DIVLO, 0x3f8
280         .set UART_DIVHI, 0x3f9
281         .set UART_FIFO, 0x3fa
282         .set UART_LCTL, 0x3fb
283         .set UART_MCTL, 0x3fc
284         .set UART_LSTAT, 0x3fd
285         .set DIV_9600, 115200 / 9600
286         .set LCTL_8N1, 0x03
287         .set LCTL_DLAB, 0x80
288         .set FIFO_ENABLE, 0x01
289         .set FIFO_SEND_CLEAR, 0x04
290         .set FIFO_RECV_CLEAR, 0x02
291         .set MCTL_DTR, 0x01
292         .set MCTL_RTS, 0x02
293         .set MCTL_OUT2, 0x08
294         .set LST_TREG_EMPTY, 0x20
295
296 setup_serial:
297         # set clock divisor
298         mov $LCTL_DLAB, %al
299         mov $UART_LCTL, %dx
300         out %al, %dx
301         mov $DIV_9600, %ax
302         mov $UART_DIVLO, %dx
303         out %al, %dx
304         shr $8, %ax
305         mov $UART_DIVHI, %dx
306         out %al, %dx
307         # set format 8n1
308         mov $LCTL_8N1, %al
309         mov $UART_LCTL, %dx
310         out %al, %dx
311         # clear and enable fifo
312         mov $FIFO_ENABLE, %al
313         or $FIFO_SEND_CLEAR, %al
314         or $FIFO_RECV_CLEAR, %al
315         mov $UART_FIFO, %dx
316         out %al, %dx
317         # assert RTS and DTR
318         mov $MCTL_DTR, %al
319         or $MCTL_RTS, %al
320         or $MCTL_OUT2, %al
321         mov $UART_MCTL, %dx
322         out %al, %dx
323         ret
324
325         # expects a character in al
326         .global ser_putchar
327 ser_putchar:
328         push %dx
329
330         cmp $10, %al
331         jnz 0f
332         push %ax
333         mov $13, %al
334         call ser_putchar
335         pop %ax
336
337 0:      mov %al, %ah
338         # wait until the transmit register is empty
339         mov $UART_LSTAT, %dx
340 .Lwait: in %dx, %al
341         and $LST_TREG_EMPTY, %al
342         jz .Lwait
343         mov $UART_DATA, %dx
344         mov %ah, %al
345         out %al, %dx
346
347         pop %dx
348         ret
349         
350         .org 510
351         .byte 0x55
352         .byte 0xaa
353 boot2_addr: