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