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