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/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,8 @@
1
+ """
2
+ pyOS Interrupt Handling
3
+ """
4
+
5
+ from .handler import Interrupts, InterruptType
6
+ from .idt import IDT
7
+
8
+ __all__ = ["Interrupts", "InterruptType", "IDT"]
@@ -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()