pyOS-kernel 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyos/__init__.py +22 -0
- pyos/boot/__init__.py +3 -0
- pyos/boot/bootloader.asm +174 -0
- pyos/builder.py +177 -0
- pyos/cli.py +286 -0
- pyos/compiler/__init__.py +8 -0
- pyos/compiler/assembler.py +180 -0
- pyos/compiler/codegen.py +238 -0
- pyos/drivers/__init__.py +8 -0
- pyos/drivers/keyboard.py +319 -0
- pyos/drivers/screen.py +310 -0
- pyos/emulator.py +195 -0
- pyos/interrupts/__init__.py +8 -0
- pyos/interrupts/handler.py +237 -0
- pyos/interrupts/idt.py +165 -0
- pyos/kernel.py +288 -0
- pyos/memory/__init__.py +8 -0
- pyos/memory/gdt.py +295 -0
- pyos/memory/manager.py +397 -0
- pyos/syscalls/__init__.py +7 -0
- pyos/syscalls/handler.py +179 -0
- pyos_kernel-0.1.0.dist-info/METADATA +323 -0
- pyos_kernel-0.1.0.dist-info/RECORD +27 -0
- pyos_kernel-0.1.0.dist-info/WHEEL +5 -0
- pyos_kernel-0.1.0.dist-info/entry_points.txt +2 -0
- pyos_kernel-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyos_kernel-0.1.0.dist-info/top_level.txt +1 -0
pyos/emulator.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS QEMU Emulator Integration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import shutil
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QEMUError(Exception):
|
|
12
|
+
"""Raised when QEMU fails."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class QEMURunner:
|
|
17
|
+
"""
|
|
18
|
+
QEMU emulator integration for running pyOS.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, arch: str = "x86"):
|
|
22
|
+
"""
|
|
23
|
+
Initialize QEMU runner.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
arch: Target architecture ("x86" or "x86_64")
|
|
27
|
+
"""
|
|
28
|
+
self.arch = arch
|
|
29
|
+
self.qemu_path = self._find_qemu()
|
|
30
|
+
|
|
31
|
+
def _find_qemu(self) -> str:
|
|
32
|
+
"""Find QEMU executable."""
|
|
33
|
+
if self.arch == "x86_64":
|
|
34
|
+
names = ["qemu-system-x86_64", "qemu-system-x86_64.exe"]
|
|
35
|
+
else:
|
|
36
|
+
names = ["qemu-system-i386", "qemu-system-x86_64",
|
|
37
|
+
"qemu-system-i386.exe", "qemu-system-x86_64.exe"]
|
|
38
|
+
|
|
39
|
+
for name in names:
|
|
40
|
+
path = shutil.which(name)
|
|
41
|
+
if path:
|
|
42
|
+
return path
|
|
43
|
+
|
|
44
|
+
# Return default and hope it works
|
|
45
|
+
return "qemu-system-i386" if self.arch == "x86" else "qemu-system-x86_64"
|
|
46
|
+
|
|
47
|
+
def run(
|
|
48
|
+
self,
|
|
49
|
+
image_path: str,
|
|
50
|
+
memory: int = 128,
|
|
51
|
+
debug: bool = False,
|
|
52
|
+
extra_args: Optional[List[str]] = None,
|
|
53
|
+
) -> subprocess.Popen:
|
|
54
|
+
"""
|
|
55
|
+
Run OS image in QEMU.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
image_path: Path to OS image (ISO or BIN)
|
|
59
|
+
memory: Memory in MB (default: 128)
|
|
60
|
+
debug: Enable debug mode
|
|
61
|
+
extra_args: Additional QEMU arguments
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
QEMU process handle.
|
|
65
|
+
"""
|
|
66
|
+
cmd = [self.qemu_path]
|
|
67
|
+
|
|
68
|
+
# Boot as floppy disk (works best for small OS images)
|
|
69
|
+
cmd.extend(["-fda", image_path])
|
|
70
|
+
|
|
71
|
+
# Memory
|
|
72
|
+
cmd.extend(["-m", str(memory)])
|
|
73
|
+
|
|
74
|
+
# Debug mode
|
|
75
|
+
if debug:
|
|
76
|
+
cmd.extend([
|
|
77
|
+
"-s", # GDB server on port 1234
|
|
78
|
+
"-S", # Pause at startup
|
|
79
|
+
"-d", "int,cpu_reset",
|
|
80
|
+
"-no-reboot",
|
|
81
|
+
"-no-shutdown",
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
# Extra arguments
|
|
85
|
+
if extra_args:
|
|
86
|
+
cmd.extend(extra_args)
|
|
87
|
+
|
|
88
|
+
# Run QEMU
|
|
89
|
+
try:
|
|
90
|
+
process = subprocess.Popen(
|
|
91
|
+
cmd,
|
|
92
|
+
stdout=subprocess.PIPE,
|
|
93
|
+
stderr=subprocess.PIPE,
|
|
94
|
+
)
|
|
95
|
+
return process
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
raise QEMUError(
|
|
98
|
+
f"QEMU not found. Please install QEMU:\n"
|
|
99
|
+
f" Windows: Download from https://www.qemu.org/download/#windows\n"
|
|
100
|
+
f" Linux: sudo apt install qemu-system-x86\n"
|
|
101
|
+
f" macOS: brew install qemu"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def run_and_wait(
|
|
105
|
+
self,
|
|
106
|
+
image_path: str,
|
|
107
|
+
memory: int = 128,
|
|
108
|
+
timeout: Optional[int] = None,
|
|
109
|
+
) -> int:
|
|
110
|
+
"""
|
|
111
|
+
Run OS and wait for completion.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
image_path: Path to OS image
|
|
115
|
+
memory: Memory in MB
|
|
116
|
+
timeout: Timeout in seconds
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
QEMU exit code.
|
|
120
|
+
"""
|
|
121
|
+
process = self.run(image_path, memory)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
return process.wait(timeout=timeout)
|
|
125
|
+
except subprocess.TimeoutExpired:
|
|
126
|
+
process.kill()
|
|
127
|
+
return -1
|
|
128
|
+
|
|
129
|
+
def run_with_serial(
|
|
130
|
+
self,
|
|
131
|
+
image_path: str,
|
|
132
|
+
memory: int = 128,
|
|
133
|
+
) -> subprocess.Popen:
|
|
134
|
+
"""
|
|
135
|
+
Run OS with serial output to console.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
image_path: Path to OS image
|
|
139
|
+
memory: Memory in MB
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
QEMU process handle.
|
|
143
|
+
"""
|
|
144
|
+
return self.run(
|
|
145
|
+
image_path,
|
|
146
|
+
memory,
|
|
147
|
+
extra_args=["-serial", "stdio", "-display", "none"],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def run_headless(
|
|
151
|
+
self,
|
|
152
|
+
image_path: str,
|
|
153
|
+
memory: int = 128,
|
|
154
|
+
) -> subprocess.Popen:
|
|
155
|
+
"""
|
|
156
|
+
Run OS without display (headless).
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
image_path: Path to OS image
|
|
160
|
+
memory: Memory in MB
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
QEMU process handle.
|
|
164
|
+
"""
|
|
165
|
+
return self.run(
|
|
166
|
+
image_path,
|
|
167
|
+
memory,
|
|
168
|
+
extra_args=["-display", "none", "-serial", "stdio"],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def is_available() -> bool:
|
|
173
|
+
"""Check if QEMU is available."""
|
|
174
|
+
for name in ["qemu-system-i386", "qemu-system-x86_64"]:
|
|
175
|
+
if shutil.which(name):
|
|
176
|
+
return True
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def get_version() -> Optional[str]:
|
|
181
|
+
"""Get QEMU version string."""
|
|
182
|
+
for name in ["qemu-system-i386", "qemu-system-x86_64"]:
|
|
183
|
+
path = shutil.which(name)
|
|
184
|
+
if path:
|
|
185
|
+
try:
|
|
186
|
+
result = subprocess.run(
|
|
187
|
+
[path, "--version"],
|
|
188
|
+
capture_output=True,
|
|
189
|
+
text=True,
|
|
190
|
+
)
|
|
191
|
+
if result.returncode == 0:
|
|
192
|
+
return result.stdout.split('\n')[0]
|
|
193
|
+
except:
|
|
194
|
+
pass
|
|
195
|
+
return None
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Interrupt Handler
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Dict, Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InterruptType(Enum):
|
|
11
|
+
"""Common interrupt types"""
|
|
12
|
+
# CPU Exceptions (0-31)
|
|
13
|
+
DIVIDE_ERROR = 0
|
|
14
|
+
DEBUG = 1
|
|
15
|
+
NMI = 2
|
|
16
|
+
BREAKPOINT = 3
|
|
17
|
+
OVERFLOW = 4
|
|
18
|
+
BOUND_RANGE = 5
|
|
19
|
+
INVALID_OPCODE = 6
|
|
20
|
+
DEVICE_NOT_AVAILABLE = 7
|
|
21
|
+
DOUBLE_FAULT = 8
|
|
22
|
+
COPROCESSOR_SEGMENT = 9
|
|
23
|
+
INVALID_TSS = 10
|
|
24
|
+
SEGMENT_NOT_PRESENT = 11
|
|
25
|
+
STACK_FAULT = 12
|
|
26
|
+
GENERAL_PROTECTION = 13
|
|
27
|
+
PAGE_FAULT = 14
|
|
28
|
+
X87_FPU_ERROR = 16
|
|
29
|
+
ALIGNMENT_CHECK = 17
|
|
30
|
+
MACHINE_CHECK = 18
|
|
31
|
+
SIMD_FPU_ERROR = 19
|
|
32
|
+
|
|
33
|
+
# Hardware IRQs (32-47)
|
|
34
|
+
IRQ_TIMER = 32 # IRQ0 - PIT Timer
|
|
35
|
+
IRQ_KEYBOARD = 33 # IRQ1 - Keyboard
|
|
36
|
+
IRQ_CASCADE = 34 # IRQ2 - Cascade
|
|
37
|
+
IRQ_COM2 = 35 # IRQ3 - COM2
|
|
38
|
+
IRQ_COM1 = 36 # IRQ4 - COM1
|
|
39
|
+
IRQ_LPT2 = 37 # IRQ5 - LPT2
|
|
40
|
+
IRQ_FLOPPY = 38 # IRQ6 - Floppy
|
|
41
|
+
IRQ_LPT1 = 39 # IRQ7 - LPT1
|
|
42
|
+
IRQ_RTC = 40 # IRQ8 - RTC
|
|
43
|
+
IRQ_FREE1 = 41 # IRQ9
|
|
44
|
+
IRQ_FREE2 = 42 # IRQ10
|
|
45
|
+
IRQ_FREE3 = 43 # IRQ11
|
|
46
|
+
IRQ_MOUSE = 44 # IRQ12 - PS/2 Mouse
|
|
47
|
+
IRQ_FPU = 45 # IRQ13 - FPU
|
|
48
|
+
IRQ_PRIMARY_ATA = 46 # IRQ14 - Primary ATA
|
|
49
|
+
IRQ_SECONDARY_ATA = 47 # IRQ15 - Secondary ATA
|
|
50
|
+
|
|
51
|
+
# System Call
|
|
52
|
+
SYSCALL = 0x80
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class InterruptFrame:
|
|
57
|
+
"""CPU state when interrupt occurred"""
|
|
58
|
+
eip: int = 0
|
|
59
|
+
cs: int = 0
|
|
60
|
+
eflags: int = 0
|
|
61
|
+
esp: int = 0
|
|
62
|
+
ss: int = 0
|
|
63
|
+
error_code: int = 0
|
|
64
|
+
interrupt_number: int = 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Interrupts:
|
|
68
|
+
"""
|
|
69
|
+
Interrupt Handler Manager.
|
|
70
|
+
|
|
71
|
+
Provides methods to register and handle interrupts.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
@Interrupts.handler(InterruptType.IRQ_KEYBOARD)
|
|
75
|
+
def keyboard_handler(frame):
|
|
76
|
+
key = Keyboard.read_key()
|
|
77
|
+
Screen.print(f"Key: {key}")
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_handlers: Dict[int, Callable] = {}
|
|
81
|
+
_enabled: bool = False
|
|
82
|
+
_operations: list = []
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def handler(cls, interrupt: InterruptType):
|
|
86
|
+
"""
|
|
87
|
+
Decorator to register an interrupt handler.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
interrupt: The interrupt type to handle
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
@Interrupts.handler(InterruptType.IRQ_TIMER)
|
|
94
|
+
def timer_handler(frame):
|
|
95
|
+
pass
|
|
96
|
+
"""
|
|
97
|
+
def decorator(func: Callable) -> Callable:
|
|
98
|
+
cls._handlers[interrupt.value] = func
|
|
99
|
+
cls._operations.append({
|
|
100
|
+
"type": "register_handler",
|
|
101
|
+
"interrupt": interrupt.value,
|
|
102
|
+
"name": func.__name__,
|
|
103
|
+
})
|
|
104
|
+
return func
|
|
105
|
+
return decorator
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def register(cls, interrupt_number: int, handler: Callable) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Register an interrupt handler by number.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
interrupt_number: Interrupt number (0-255)
|
|
114
|
+
handler: Handler function
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
def my_handler(frame):
|
|
118
|
+
pass
|
|
119
|
+
Interrupts.register(0x21, my_handler)
|
|
120
|
+
"""
|
|
121
|
+
cls._handlers[interrupt_number] = handler
|
|
122
|
+
cls._operations.append({
|
|
123
|
+
"type": "register_handler",
|
|
124
|
+
"interrupt": interrupt_number,
|
|
125
|
+
"name": handler.__name__,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def unregister(cls, interrupt_number: int) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Unregister an interrupt handler.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
interrupt_number: Interrupt number to unregister
|
|
135
|
+
"""
|
|
136
|
+
if interrupt_number in cls._handlers:
|
|
137
|
+
del cls._handlers[interrupt_number]
|
|
138
|
+
cls._operations.append({
|
|
139
|
+
"type": "unregister_handler",
|
|
140
|
+
"interrupt": interrupt_number,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def enable(cls) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Enable interrupts (STI instruction).
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
Interrupts.enable()
|
|
150
|
+
"""
|
|
151
|
+
cls._enabled = True
|
|
152
|
+
cls._operations.append({"type": "enable"})
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def disable(cls) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Disable interrupts (CLI instruction).
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
Interrupts.disable()
|
|
161
|
+
"""
|
|
162
|
+
cls._enabled = False
|
|
163
|
+
cls._operations.append({"type": "disable"})
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def is_enabled(cls) -> bool:
|
|
167
|
+
"""Check if interrupts are enabled."""
|
|
168
|
+
return cls._enabled
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def send_eoi(cls, irq: int) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Send End of Interrupt to PIC.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
irq: IRQ number (0-15)
|
|
177
|
+
"""
|
|
178
|
+
cls._operations.append({
|
|
179
|
+
"type": "send_eoi",
|
|
180
|
+
"irq": irq,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def mask_irq(cls, irq: int) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Mask (disable) a specific IRQ.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
irq: IRQ number (0-15)
|
|
190
|
+
"""
|
|
191
|
+
cls._operations.append({
|
|
192
|
+
"type": "mask_irq",
|
|
193
|
+
"irq": irq,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def unmask_irq(cls, irq: int) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Unmask (enable) a specific IRQ.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
irq: IRQ number (0-15)
|
|
203
|
+
"""
|
|
204
|
+
cls._operations.append({
|
|
205
|
+
"type": "unmask_irq",
|
|
206
|
+
"irq": irq,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def trigger_software_interrupt(cls, interrupt_number: int) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Trigger a software interrupt.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
interrupt_number: Interrupt number to trigger
|
|
216
|
+
"""
|
|
217
|
+
cls._operations.append({
|
|
218
|
+
"type": "trigger",
|
|
219
|
+
"interrupt": interrupt_number,
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def get_handler(cls, interrupt_number: int) -> Optional[Callable]:
|
|
224
|
+
"""Get the handler for an interrupt."""
|
|
225
|
+
return cls._handlers.get(interrupt_number)
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def _reset(cls) -> None:
|
|
229
|
+
"""Reset interrupt state (used internally)."""
|
|
230
|
+
cls._handlers = {}
|
|
231
|
+
cls._enabled = False
|
|
232
|
+
cls._operations = []
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def _get_operations(cls) -> list:
|
|
236
|
+
"""Get all recorded operations (used by compiler)."""
|
|
237
|
+
return cls._operations.copy()
|
pyos/interrupts/idt.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS IDT (Interrupt Descriptor Table)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GateType(Enum):
|
|
11
|
+
"""IDT Gate Types"""
|
|
12
|
+
TASK_GATE = 0x5
|
|
13
|
+
INTERRUPT_GATE_16 = 0x6
|
|
14
|
+
TRAP_GATE_16 = 0x7
|
|
15
|
+
INTERRUPT_GATE_32 = 0xE
|
|
16
|
+
TRAP_GATE_32 = 0xF
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class IDTEntry:
|
|
21
|
+
"""
|
|
22
|
+
Represents a single IDT entry.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
offset: Handler function address
|
|
26
|
+
selector: Code segment selector
|
|
27
|
+
gate_type: Type of gate
|
|
28
|
+
privilege: Required privilege level to call
|
|
29
|
+
present: Is entry present
|
|
30
|
+
"""
|
|
31
|
+
offset: int = 0
|
|
32
|
+
selector: int = 0x08 # Kernel code segment
|
|
33
|
+
gate_type: GateType = GateType.INTERRUPT_GATE_32
|
|
34
|
+
privilege: int = 0
|
|
35
|
+
present: bool = True
|
|
36
|
+
|
|
37
|
+
def to_bytes(self) -> bytes:
|
|
38
|
+
"""Convert IDT entry to 8-byte descriptor."""
|
|
39
|
+
offset_low = self.offset & 0xFFFF
|
|
40
|
+
offset_high = (self.offset >> 16) & 0xFFFF
|
|
41
|
+
|
|
42
|
+
# Type and attributes byte
|
|
43
|
+
type_attr = self.gate_type.value
|
|
44
|
+
if self.present:
|
|
45
|
+
type_attr |= 0x80
|
|
46
|
+
type_attr |= (self.privilege << 5)
|
|
47
|
+
|
|
48
|
+
return bytes([
|
|
49
|
+
offset_low & 0xFF,
|
|
50
|
+
(offset_low >> 8) & 0xFF,
|
|
51
|
+
self.selector & 0xFF,
|
|
52
|
+
(self.selector >> 8) & 0xFF,
|
|
53
|
+
0, # Reserved
|
|
54
|
+
type_attr,
|
|
55
|
+
offset_high & 0xFF,
|
|
56
|
+
(offset_high >> 8) & 0xFF,
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IDT:
|
|
61
|
+
"""
|
|
62
|
+
Interrupt Descriptor Table Manager.
|
|
63
|
+
|
|
64
|
+
The IDT maps interrupt numbers to handler functions.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
idt = IDT()
|
|
68
|
+
idt.set_gate(0, divide_error_handler, GateType.TRAP_GATE_32)
|
|
69
|
+
idt.set_gate(33, keyboard_handler, GateType.INTERRUPT_GATE_32)
|
|
70
|
+
idt.install()
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
MAX_ENTRIES = 256
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
"""Initialize IDT with empty entries."""
|
|
77
|
+
self._entries: List[Optional[IDTEntry]] = [None] * self.MAX_ENTRIES
|
|
78
|
+
self._operations: list = []
|
|
79
|
+
|
|
80
|
+
def set_gate(
|
|
81
|
+
self,
|
|
82
|
+
index: int,
|
|
83
|
+
handler_address: int,
|
|
84
|
+
gate_type: GateType = GateType.INTERRUPT_GATE_32,
|
|
85
|
+
selector: int = 0x08,
|
|
86
|
+
privilege: int = 0,
|
|
87
|
+
) -> 'IDT':
|
|
88
|
+
"""
|
|
89
|
+
Set an IDT gate entry.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
index: Interrupt number (0-255)
|
|
93
|
+
handler_address: Address of handler function
|
|
94
|
+
gate_type: Type of gate (default: interrupt gate)
|
|
95
|
+
selector: Code segment selector (default: kernel code)
|
|
96
|
+
privilege: Required privilege level (default: 0)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Self for chaining.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
idt.set_gate(0, 0x1000, GateType.TRAP_GATE_32)
|
|
103
|
+
"""
|
|
104
|
+
if 0 <= index < self.MAX_ENTRIES:
|
|
105
|
+
self._entries[index] = IDTEntry(
|
|
106
|
+
offset=handler_address,
|
|
107
|
+
selector=selector,
|
|
108
|
+
gate_type=gate_type,
|
|
109
|
+
privilege=privilege,
|
|
110
|
+
present=True,
|
|
111
|
+
)
|
|
112
|
+
self._operations.append({
|
|
113
|
+
"type": "set_gate",
|
|
114
|
+
"index": index,
|
|
115
|
+
"handler": handler_address,
|
|
116
|
+
"gate_type": gate_type.name,
|
|
117
|
+
})
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def clear_gate(self, index: int) -> 'IDT':
|
|
121
|
+
"""
|
|
122
|
+
Clear an IDT gate entry.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
index: Interrupt number to clear
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Self for chaining.
|
|
129
|
+
"""
|
|
130
|
+
if 0 <= index < self.MAX_ENTRIES:
|
|
131
|
+
self._entries[index] = None
|
|
132
|
+
self._operations.append({
|
|
133
|
+
"type": "clear_gate",
|
|
134
|
+
"index": index,
|
|
135
|
+
})
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def install(self) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Install the IDT using LIDT instruction.
|
|
141
|
+
|
|
142
|
+
This must be called after setting up all gates.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
idt.install()
|
|
146
|
+
"""
|
|
147
|
+
self._operations.append({"type": "install"})
|
|
148
|
+
|
|
149
|
+
def to_bytes(self) -> bytes:
|
|
150
|
+
"""Convert entire IDT to bytes."""
|
|
151
|
+
result = b''
|
|
152
|
+
for entry in self._entries:
|
|
153
|
+
if entry:
|
|
154
|
+
result += entry.to_bytes()
|
|
155
|
+
else:
|
|
156
|
+
result += bytes(8) # Empty entry
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
def get_size(self) -> int:
|
|
160
|
+
"""Get IDT size in bytes."""
|
|
161
|
+
return self.MAX_ENTRIES * 8
|
|
162
|
+
|
|
163
|
+
def _get_operations(self) -> list:
|
|
164
|
+
"""Get all recorded operations (used by compiler)."""
|
|
165
|
+
return self._operations.copy()
|