67901e45b5ebc3177088843760ef5378dfbea816
[bootcensus] / src / lowcode.s
1 # pcboot - bootable PC demo/game kernel
2 # Copyright (C) 2018-2019  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         .section .lowtext,"ax"
18
19         .code32
20         .align 4
21         # place to save the protected mode IDTR pseudo-descriptor
22         # with sidt, so that it can be restored before returning
23         .short 0
24 saved_idtr:
25 idtlim: .short 0
26 idtaddr:.long 0
27         # real mode IDTR pseudo-descriptor pointing to the IVT at addr 0
28         .short 0
29 rmidt:  .short 0x3ff
30         .long 0
31
32 saved_esp: .long 0
33 saved_ebp: .long 0
34 saved_eax: .long 0
35 saved_es: .word 0
36 saved_ds: .word 0
37 saved_flags: .word 0
38 saved_pic1_mask: .byte 0
39 saved_pic2_mask: .byte 0
40
41         # drop back to unreal mode to call 16bit interrupt
42         .global int86
43 int86:
44         push %ebp
45         mov %esp, %ebp
46         pushal
47         cli
48         # save protected mode IDTR and replace it with the real mode vectors
49         sidt (saved_idtr)
50         lidt (rmidt)
51
52         # save PIC masks
53         pushl $0
54         call get_pic_mask
55         add $4, %esp
56         mov %al, saved_pic1_mask
57         pushl $1
58         call get_pic_mask
59         add $4, %esp
60         mov %al, saved_pic2_mask
61
62         # modify the int instruction. do this here before the
63         # cs-load jumps, to let them flush the instruction cache
64         mov $int_op, %ebx
65         movb 8(%ebp), %al
66         movb %al, 1(%ebx)
67
68         # long jump to load code selector for 16bit code (6)
69         ljmp $0x30,$0f
70 0:
71         .code16
72         # disable protection
73         mov %cr0, %eax
74         and $0xfffe, %ax
75         mov %eax, %cr0
76         # load cs <- 0
77         ljmp $0,$0f
78 0:      # zero data segments
79         xor %ax, %ax
80         mov %ax, %ds
81         mov %ax, %es
82         mov %ax, %ss
83         nop
84
85         # load registers from the int86regs struct
86         # point esp to the regs struct to load registers with popa/popf
87         mov %esp, saved_esp
88         mov %ebp, saved_ebp
89         mov 12(%ebp), %esp
90         popal
91         popfw
92         pop %es
93         pop %ds
94         # ignore fs and gs for now, don't think I'm going to need them
95
96         # move to the real-mode stack, accessible from ss=0
97         # just in case the BIOS call screws up our unreal mode
98         mov $0x7be0, %esp
99
100         # call 16bit interrupt
101 int_op: int $0
102         # BIOS call might have enabled interrupts, cli for good measure
103         cli
104
105         # save all registers that we'll clobber before having the
106         # chance to populate the int86regs structure
107         mov %eax, saved_eax
108         mov %ds, saved_ds
109         mov %es, saved_es
110         pushfw
111         popw %ax
112         mov %ax, saved_flags
113
114         # re-enable protection
115         mov %cr0, %eax
116         or $1, %ax
117         mov %eax, %cr0
118         # long jump to load code selector for 32bit code (1)
119         ljmp $0x8,$0f
120 0:
121         .code32
122         # set data selector (2) to all segment regs
123         mov $0x10, %ax
124         mov %ax, %ds
125         mov %ax, %es
126         mov %ax, %ss
127         nop
128
129         # point the esp to our regs struct, to fill it with pusha/pushf
130         mov saved_ebp, %ebp
131         mov 12(%ebp), %esp
132         add $38, %esp
133         mov saved_ds, %ax
134         pushw %ax
135         mov saved_es, %ax
136         pushw %ax
137         # grab the flags and replace the carry bit from the saved flags
138         pushfw
139         popw %ax
140         and $0xfffe, %ax
141         or saved_flags, %ax
142         pushw %ax
143         mov saved_eax, %eax
144         pushal
145         mov saved_esp, %esp
146
147         # restore 32bit interrupt descriptor table
148         lidt (saved_idtr)
149
150         # restore PIC configuration
151         call init_pic
152
153         # restore IRQ masks
154         movzbl saved_pic1_mask, %eax
155         push %eax
156         pushl $0
157         call set_pic_mask
158         add $8, %esp
159
160         movzbl saved_pic2_mask, %eax
161         push %eax
162         pushl $1
163         call set_pic_mask
164         add $8, %esp
165
166         # keyboard voodoo: with some BIOS implementations, after returning from
167         # int13, there's (I guess) leftover data in the keyboard port and we
168         # can't receive any more keyboard interrupts afterwards. Reading from
169         # the keyboard data port (60h) once, seems to resolve this. And it's
170         # cheap enough, so why not... I give up.
171         push %eax
172         in $0x60, %al
173         pop %eax
174
175         sti
176         popal
177         pop %ebp
178         ret