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