-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtext86.asm
463 lines (391 loc) · 8.86 KB
/
text86.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
top:
bits 16
org 0x7c00
; WHAT: constants hard-coding known-available addresses or values
%define default_attribute 0x07
%define kf_shift_on 0x01
%define kf_shift_off ~kf_shift_on
%define kf_ctrl_on 0x02
%define kf_ctrl_off ~kf_ctrl_on
%define max_sector 0x500
%define max_head 0x502
%define max_cylinder 0x504
%define boot_dev 0x506
%define write_buffer 0x600
%define end_of_buffer 0x7bff
; no need to protect parts of the initialization routine, but want
; to make sure code stays out of the data
%define vga_io_port 0x463
%define video_memory 0xb800
; this is how many ascii characters for the scan code range check -- dumb lol
%define scan_codes 58
%define scroll_window 0x0c00
boot_code:
cli ; we are getting ready don't let anyone interrupt us
; WHAT: reset the disk subsystem with int13/ah=0
; right off the bat we need zeros in ax, dh, es, ds, and di
xor ax, ax
push ax
push ax
mov dh, ah
pop ds
pop es
mov di, ax
mov [boot_dev], dx ; save boot device
; later, word [boot_dev] will make dh 0 and dl=boot_dev
; byte [boot_dev] will make dl=boot_dev
; costs two bytes with xor dh, dh
; reset the disk subsystem
int 0x13
jc short int13_error
; WHAT: read the drive geometry with int13/ah=8
mov ah, 8
;mov dl, [boot_dev]
; es:di should still be 0:0
; dl should still be the drive
int 0x13
jc short int13_error
; WHAT: save the drive geometry in memory
; now we have good drive info!
mov [max_head], dh
mov dx, cx
and dl, 0x3f
mov [max_sector], dl
and cl, 0xc0 ; preserve the high two bits
; now ch is the low 8 bits of max cyl and cl high bits are the high bits
mov dh, cl
shr dh, 6
mov dl, ch
mov [max_cylinder], dx
; WHAT: load the config sector; hard-coded register values for CHS 0/0/2
; statically set registers for fixed sector 1
; ch = low 8 bits of cyl #
; cl bits 0-5 = sector # (needs to be 2 since CHS sectors are 1-indexed)
; dh = heads, should be 0 here
push dword 2
pop cx
pop dx
mov dl, [boot_dev]
mov ax, 0x0201 ; int13/ah=2, al=#sectors to read
mov bx, config_sector
int 0x13
jc short int13_error
; WHAT: restore the last sector and reset si
mov cx, [cx_int13]
mov dh, [dh_int13]
mov ax, 0x201 ; ah=2 read sector, al=01 # of sectors
mov bx, write_buffer
int 0x13
jc short int13_error
mov si, write_buffer
; WHAT: draw the contents of the buffer
; this also gets both si and di to the correct positions
push video_memory
pop es
mov al, default_attribute
mov cx, [si_at_last_save]
draw_buffer:
movsb ; copy the character
stosb ; set the attribute
loopnz draw_buffer
push di
mov cx, 2000
sub cx, di
mov ax, 0x0720
rep stosw
pop di
; WHAT: setup the keyboard interrupt handler, write buffer, and video pointer
mov eax, keyboard_handler
mov [36], eax
; WHAT: start the main loop
main_loop:
xor bp, bp
sti
do_not_overwrite_the_following_code_with_data:
jmp short $
; WHAT: subroutine int13_error: print the int13 al status followed by a red-highlighted 'e'
int13_error:
push word video_memory
pop es
xor di, di
call small_hexprint_al
mov ax, 0x4065
; mov al, 'e'
; stosb
; mov al, 0x40
; stosb
stosw
hlt
;hexprint_eax:
; push eax
; call hexprint_ax
; pop eax
; shr eax, 16
; call hexprint_ax
; ret
;
;hexprint_ax:
; push ax
; call hexprint_al
; pop ax
; shr ax, 8
; call hexprint_al
; ret
; WHAT: subroutine hexprint_al: write the value of al as a human-readable 2-character hex byte to es:di
;hexprint_al:
; push ax
; shr al, 4
; call hexprint_low_nibble_of_al
; pop ax
; call hexprint_low_nibble_of_al
; ret
;
;; WHAT: subroutine hexprint_low_nibble_of_al: write the single-digit value of al & 0xf in hex to es:di
;hexprint_low_nibble_of_al:
; mov ah, default_attribute
; and al, 0x0f
; cmp al, 0x09
; jle short .num
; add al, ah
;.num:
; add al, 0x30
; stosw
;
; ret
small_hexprint_al:
push word 0
pop ds
mov bx, al_conv_ascii
mov ah, default_attribute
push ax
and al, 0xf0
xlatb
stosw
pop ax
shr al, 4
xlatb
stosw
ret
; WHAT: keyboard interrupt handler
keyboard_handler:
cli
.spin: ; ahhahahahaha dizzy loop
in al, 0x64
and al, 0x01
jz short .spin
in al, 0x60
; now al has the char(mander)
; 'in' is how we BASICally say peek(achu)
; 'out' used to be called poke(emon)
; is this a control character?
push ax
call control_check
pop ax
cmp al, scan_codes + 1
jae short .post_draw
.translate:
mov bx, ascii_table + scan_codes
test bp, kf_shift_on
jnz short .upper
mov bx, ascii_table
.upper:
xlatb
test bp, kf_ctrl_on
jz short .draw
; control characters
cmp al, 's'
jne short .post_draw
push es
call save
pop es
jmp short .post_draw
.draw:
test al, al
jz short .next
mov [si], al
inc si
mov ah, default_attribute
stosw
.post_draw:
.scroll_check: ; scroll down one line, copying [160,di] to [0,di-160]
cmp di, scroll_window ; only do this if we are about 3/4ths down the screen
jl short .end_scroll_check
push di
xor di, di
.scroll_loop:
mov ax, [es:di+160]
stosw
cmp di, scroll_window
jl .scroll_loop
pop di
sub di, 160
.end_scroll_check:
.reset_cursor_to_di: ; can be replaced with a call to int10?
; cursor position is row+(col*80), which happens to be half of di
mov bx, di
shr bx, 1
mov dx, [vga_io_port]
mov al, 0x0f ; selects the low word of the cursor position
out dx, al
inc dx
mov ax, bx
out dx, al
dec dx
mov al, 0x0e ; selects the high word of the cursor position
out dx, al
inc dx
mov al, bh
out dx, al
.next:
; don't be lame and leave the brogrammable interrupt controller hangin'
mov al, 0x20
out 0x20, al
sti
iret
control_check:
cmp al, 0x0e
je .backspace
cmp al, 0x2a
je short .shift_down
cmp al, 0x36
je short .shift_down
cmp al, 0xaa
je short .shift_up
cmp al, 0xb6
je short .shift_up
cmp al, 0x1c
je short .crlf
cmp al, 0x1d
je short .ctrl_down
cmp al, 0x9d
je short .ctrl_up
ret ; didn't find a match, return
.shift_down:
or bp, kf_shift_on
ret
.shift_up:
and bp, kf_shift_off
ret
.ctrl_down:
or bp, kf_ctrl_on
ret
.ctrl_up:
and bp, kf_ctrl_off
ret
.backspace:
test di, di
jz short .no_bksp
cmp si, write_buffer
je short .no_bksp
mov ax, 0x0700
dec si
dec di
dec di ; two decs is less than a sub
mov [si], al
mov [es:di], ax
.no_bksp:
ret
.crlf:
push bp
mov [si], al
inc si
mov bp, 160
mov ax, di
cwd
idiv bp
sub di, dx
add di, bp
pop bp
ret
save:
; writing [write_buffer,si) to the disk, one sector at a time
; caller should save es
push word 0
pop es
mov bx, write_buffer
mov cx, [cx_int13]
mov dh, [dh_int13]
mov dl, [boot_dev]
.loop:
;push cx
;push dx ; does int13/ah=3 trash registers cx&dh?
call write_sector
;pop dx ; we save and restore these so the increment works proper
;pop cx
add bx, 0x200
cmp bx, si
jge short .done_writing
;increment_chs_sector: ; increment one sector in a int13 CHS cx+dh
mov al, cl
and al, 0x3f ; test the low six bits only
cmp al, [max_sector]
jl short .increment_sector
; sector wrapped, reset to minimum value of 1
and cl, 0xc0
inc cl
; try to increment the head
cmp dh, [max_head]
jl short .increment_head
; head wrapped, reset to minimum value of 0
xor dh, dh
; try to increment the 10-bit cylinder number
inc ch
jno short .no_overflow
; here, the cylinder number overflowed 8 bits
; cmp ax, [max_cylinder]
; jge int13_error ; jge disk_full ; LOL
add cl, 0x40
.no_overflow:
jmp short .chs_inc_bottom
.increment_head:
inc dh
jmp short .chs_inc_bottom
.increment_sector:
inc cl
.chs_inc_bottom:
; this saves the changes we made to the config
; it is committed to disk below when save_config is called
mov [cx_int13], cx
mov [dh_int13], dh
jmp short .loop
.done_writing:
; move the current sector back to the starting sector
mov cx, si
and cx, 0x01ff
and si, 0xfe00
mov bx, write_buffer
.buffer_scootch:
lodsb
mov [bx], al
inc bx
loopnz .buffer_scootch
mov si, bx
and bx, 0x1ff
mov [si_at_last_save], bx
save_config:
;call lba_to_chs ; static lba sector 1, chs sector 2
; need dh=0,ch=0,cl=2
mov cx, 2
mov dx, [boot_dev]
mov bx, config_sector
; call write_sector
; ret
; no need to call/ret when the thing is right below us
write_sector: ; always goes to the same sector
; already called lba_to_chs, and bx is loaded
mov ax, 0x301
int 0x13
jc int13_error
ret
%ifndef _NOSIZE
times 510 - ($ - $$) db 0x21 ; position the bootsector marker 0xaa55
%endif
; using indicator such as ! visualizes remaining space with xxd -l 512
dw 0xaa55
config_sector:
; last sector written to. the default value corresponds to a "blank slate"
cx_int13: dw 3 ; i guess this assumes there are at least 3 sectors/track :-/
dh_int13: db 0
si_at_last_save: dw 0
al_conv_ascii: db '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
ascii_table: ; keymap-specific bytes, 58 unshifted followed by 58 shifted