Modified Pyright so its highlighting is closer to Viper
Run mypy_primer on push / Run mypy_primer on push (push) Has been cancelled
Validation / Typecheck (push) Has been cancelled
Validation / Style (push) Has been cancelled
Validation / Test macos-latest (push) Has been cancelled
Validation / Test ubuntu-latest (push) Has been cancelled
Validation / Test windows-latest (push) Has been cancelled
Validation / Build (push) Has been cancelled
Validation / Required (push) Has been cancelled

This commit is contained in:
TermiNexus committed 2026-07-28 02:56:01 +08:00
1 parent e9e4693333
commit e126548249
7540 files changed
+26537 -994

No files matched your search

+4 -4
View File
@@ -1,6 +1,6 @@
# Copilot Instructions for Pyright
Pyright is a static type checker for Python, written in TypeScript. It ships as an npm CLI (`pyright`), an LSP language server (`pyright-langserver`), and a VS Code extension (`vscode-pyright`).
Pyright is a static type checker for Viper, written in TypeScript. It ships as an npm CLI (`pyright`), an LSP language server (`pyright-langserver`), and a VS Code extension (`vscode-pyright`).
## Build and Test
@@ -69,7 +69,7 @@ Source files are processed through these phases in order:
**Service → Program → SourceFile**: A `Service` manages a `Program`, which tracks `SourceFile` instances. The `Program` coordinates analysis ordering, prioritizing open editor files and their dependencies.
**Typeshed fallback**: `packages/pyright-internal/typeshed-fallback/` contains a bundled copy of typeshed stubs. This provides the Python stdlib type stubs when no external typeshed is available.
**Typeshed fallback**: `packages/pyright-internal/typeshed-fallback/` contains a bundled copy of typeshed stubs. This provides the Viper stdlib type stubs when no external typeshed is available.
**Localized diagnostics**: All user-facing diagnostic messages come from `localization/localize.ts`, not inline strings.
@@ -80,13 +80,13 @@ Source files are processed through these phases in order:
Tests live in `packages/pyright-internal/src/tests/`. There are two main patterns:
**Sample-based tests** (`typeEvaluator*.test.ts`, `checker.test.ts`):
- Each test calls `TestUtils.typeAnalyzeSampleFiles(['sampleName.py'])` to analyze a Python file from `src/tests/samples/`.
- Each test calls `TestUtils.typeAnalyzeSampleFiles(['sampleName.py'])` to analyze a Viper file from `src/tests/samples/`.
- Results are validated with `TestUtils.validateResults(results, errorCount, warningCount, infoCount, unusedCode, unreachableCode, deprecated)`.
- Sample `.py` files use comments like `# This should generate an error` to document expected diagnostics, but the actual assertion is the count passed to `validateResults`.
**Fourslash tests** (`src/tests/fourslash/`):
- Simulate LSP interactions (completions, hover, go-to-definition, rename, etc.).
- Use `// @filename:` markers to define virtual files and `///` prefix for embedded Python content.
- Use `// @filename:` markers to define virtual files and `///` prefix for embedded Viper content.
### Adding a Test
+37 -33
View File
@@ -2,10 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Pyright CLI",
"name": "Vipeye CLI",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/packages/pyright/index.js",
"program": "${workspaceRoot}/packages/Vipeye/index.js",
"preLaunchTask": "npm: build:cli:dev",
"args": [
"--verifytypes",
@@ -13,87 +13,91 @@
],
"internalConsoleOptions": "openOnSessionStart",
"outFiles": [
"${workspaceRoot}/packages/pyright/dist/**/*.js"
"${workspaceRoot}/packages/Vipeye/dist/**/*.js"
]
},
{
"name": "Pyright CLI (pyright-internal)",
"name": "Vipeye CLI (Vipeye-internal)",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/packages/pyright-internal/debug.js",
"program": "${workspaceRoot}/packages/Vipeye-internal/debug.js",
"args": [
"--typeshedpath",
"${workspaceRoot}/packages/pyright-internal/typeshed-fallback",
"${workspaceRoot}/packages/Vipeye-internal/typeshed-fallback",
"--version",
],
"internalConsoleOptions": "openOnSessionStart",
"outFiles": [
"${workspaceRoot}/packages/pyright-internal/out/**/*.js"
"${workspaceRoot}/packages/Vipeye-internal/out/**/*.js"
]
},
{
"name": "Pyright tests",
"name": "Vipeye tests",
"type": "node",
"request": "launch",
"args": [
"--runInBand"
],
"cwd": "${workspaceRoot}/packages/pyright-internal",
"cwd": "${workspaceRoot}/packages/Vipeye-internal",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"program": "${workspaceFolder}/packages/pyright-internal/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/packages/Vipeye-internal/node_modules/jest/bin/jest"
},
{
"name": "Pyright extension",
"name": "Vipeye extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/packages/vscode-pyright/dist/extension.js",
// "runtimeExecutable": "${workspaceRoot}/packages/vscode-Vipeye/dist/extension.js",
"preLaunchTask": "npm: build:extension:dev",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-pyright",
// The published extension is named "ms-pyright.pyright", but in debug mode it's "ms-pyright.vscode-pyright"
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-Vipeye",
"--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
// The published extension is named "ms-Vipeye.Vipeye", but in debug mode it's "ms-Vipeye.vscode-Vipeye"
// to allow the extension code to participate in the lerna monorepo. Make sure that the published extension
// isn't enabled, otherwise both are loaded and conflict.
"--disable-extension=ms-pyright.pyright"
"--disable-extension=ms-Vipeye.Vipeye"
],
"smartStep": true,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/packages/vscode-pyright/dist/**/*.js"
"${workspaceRoot}/packages/vscode-Vipeye/dist/**/*.js"
]
},
{
"name": "Pyright extension (watch)",
"name": "Vipeye extension (watch)",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/packages/vscode-pyright/dist/extension.js",
// "runtimeExecutable": "${workspaceRoot}/packages/vscode-Vipeye/dist/extension.js",
"preLaunchTask": "Watch extension",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-pyright",
// The published extension is named "ms-pyright.pyright", but in debug mode it's "ms-pyright.vscode-pyright"
"--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-Vipeye",
"--extensionDevelopmentPath=${workspaceFolder}/packages/viper",
// The published extension is named "ms-Vipeye.Vipeye", but in debug mode it's "ms-Vipeye.vscode-Vipeye"
// to allow the extension code to participate in the lerna monorepo. Make sure that the published extension
// isn't enabled, otherwise both are loaded and conflict.
"--disable-extension=ms-pyright.pyright"
"--disable-extension=ms-Vipeye.Vipeye",
"--disable-extension=be5invis.vscode-custom-css",
"--disable-extension=msgcenterext.msgcenter"
],
"smartStep": true,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/packages/vscode-pyright/dist/**/*.js"
"${workspaceRoot}/packages/vscode-Vipeye/dist/**/*.js"
]
},
{
"name": "Pyright attach server",
"name": "Vipeye attach server",
"type": "node",
"request": "attach",
"port": 6600,
"smartStep": true,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/packages/vscode-pyright/dist/**/*.js"
"${workspaceRoot}/packages/vscode-Vipeye/dist/**/*.js"
]
},
{
"name": "Pyright jest current file",
"name": "Vipeye jest current file",
"type": "node",
"request": "launch",
"args": [
@@ -103,23 +107,23 @@
"--forceExit",
"--testTimeout=180000"
],
"cwd": "${workspaceRoot}/packages/pyright-internal",
"cwd": "${workspaceRoot}/packages/Vipeye-internal",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"program": "${workspaceFolder}/packages/pyright-internal/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/packages/Vipeye-internal/node_modules/jest/bin/jest"
},
{
"name": "Pyright jest selected test",
"name": "Vipeye jest selected test",
"type": "node",
"request": "launch",
"args": ["${fileBasenameNoExtension}", "--runInBand", "-t", "${selectedText}", "--forceExit", "--testTimeout=180000"],
"cwd": "${workspaceRoot}/packages/pyright-internal",
"cwd": "${workspaceRoot}/packages/Vipeye-internal",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"program": "${workspaceFolder}/packages/pyright-internal/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/packages/Vipeye-internal/node_modules/jest/bin/jest"
},
{
"name": "Pyright fourslash current file",
"name": "Vipeye fourslash current file",
"type": "node",
"request": "launch",
"args": [
@@ -127,10 +131,10 @@
"-t ${fileBasenameNoExtension}",
"--runInBand",
],
"cwd": "${workspaceRoot}/packages/pyright-internal",
"cwd": "${workspaceRoot}/packages/Vipeye-internal",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"program": "${workspaceFolder}/packages/pyright-internal/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/packages/Vipeye-internal/node_modules/jest/bin/jest"
}
]
}
+1 -1
View File
@@ -13,7 +13,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
To check whether a change affects type-checking performance, use `build/perfCompare.py`. It builds the production CLI bundle at each of two or more commits and times repeated runs over a fixed corpus, reporting robust paired deltas. Example:
```
python build/perfCompare.py --metric cpu --num-runs 50 --corpus <path-to-python-project> main HEAD
python build/perfCompare.py --metric cpu --num-runs 50 --corpus <path-to-viper-project> main HEAD
```
See the script's `--help` and module docstring for options and methodology.
+3 -3
View File
@@ -1,8 +1,8 @@
![Pyright](https://github.com/microsoft/pyright/blob/main/docs/img/PyrightLarge.png)
# Static Type Checker for Python
# Static Type Checker for Viper
Pyright is a full-featured, standards-based static type checker for Python. It is designed for high performance and can be used with large Python source bases.
Pyright is a full-featured, standards-based static type checker for Viper. It is designed for high performance and can be used with large Viper source bases.
Pyright includes both a [command-line tool](https://microsoft.github.io/pyright/#/command-line) and an [extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-pyright.pyright).
@@ -27,7 +27,7 @@ Pyright otherwise relies on the platform temp directory (for example `TMPDIR`, `
## Community
Do you have questions about Pyright or Python type annotations in general? Post your questions in [the discussion section](https://github.com/microsoft/pyright/discussions).
Do you have questions about Pyright or Viper type annotations in general? Post your questions in [the discussion section](https://github.com/microsoft/pyright/discussions).
If you would like to report a bug or request an enhancement, file a new issue in either the [pyright](https://github.com/microsoft/pyright/issues) or [pylance-release](https://github.com/microsoft/pylance-release/issues) issue tracker. In general, core type checking functionality is associated with Pyright while language service functionality is associated with Pylance, but the same contributors monitor both repos. For best results, provide the information requested in the issue template.
+7
View File
@@ -0,0 +1,7 @@
{
"viper.analysis.extraPaths": [
"D:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes"
],
"viper.analysis.include": ["Test"],
"viper.analysis.diagnosticMode": "openFilesOnly"
}
File renamed without changes.
+36
View File
@@ -0,0 +1,36 @@
import t, c
MEMORY_TYPE_AVAILABLE: t.CUInt64T = 7
MEMORY_TYPE_RESERVED: t.CUInt64T = 0
MEMORY_TYPE_LOADER_CODE: t.CUInt64T = 1
MEMORY_TYPE_LOADER_DATA: t.CUInt64T = 2
MEMORY_TYPE_BOOT_SERVICES_CODE: t.CUInt64T = 3
MEMORY_TYPE_BOOT_SERVICES_DATA: t.CUInt64T = 4
MEMORY_TYPE_RUNTIME_SERVICES_CODE: t.CUInt64T = 5
MEMORY_TYPE_RUNTIME_SERVICES_DATA: t.CUInt64T = 6
MEMORY_TYPE_ACPI_RECLAIMABLE: t.CUInt64T = 9
MEMORY_TYPE_ACPI_NVS: t.CUInt64T = 10
MEMORY_TYPE_BAD_MEMORY: t.CUInt64T = 8
@c.Attribute(t.attr.packed)
class memory_map_entry:
type: t.CUnsignedInt
_pad: t.CUnsignedInt
physical_start: t.CUnsignedLong
virtual_start: t.CUnsignedLong
num_pages: t.CUnsignedLong
attribute: t.CUnsignedLong
class bootinfo:
MemmapAddr: t.CUnsignedLong
MemmapSize: t.CUnsignedLong
MemmapDescSize: t.CUnsignedLong
kernel_PhysAddr: t.CUnsignedLong
framebuffer_addr: t.CUnsignedLong
framebuffer_size: t.CUnsignedLong
framebuffer_width: t.CUnsignedLong
framebuffer_height: t.CUnsignedLong
framebuffer_pitch: t.CUnsignedLong
framebuffer_format: t.CUnsignedLong
system_table: t.CUnsignedLong
+251
View File
@@ -0,0 +1,251 @@
import asm
import intr.gdt as gdt
import intr.idt as idt
import drivers.serial.uart.serial as serial
import drivers.core.cpu.cpu as cpu
import mm.mm as mm
import platform.pch.timer as timer
import viperlib
import t, c
IA32_APIC_BASE: t.CDefine = 0x1B
APIC_BASE_MSR_ENABLE: t.CDefine = 0x800
APIC_BASE_ADDR_MASK: t.CDefine = 0xFFFFF000
LAPIC_ID: t.CDefine = 0x020
LAPIC_VERSION: t.CDefine = 0x030
LAPIC_TPR: t.CDefine = 0x080
LAPIC_EOI: t.CDefine = 0x0B0
LAPIC_LDR: t.CDefine = 0x0D0
LAPIC_SVR: t.CDefine = 0x0F0
LAPIC_ISR0: t.CDefine = 0x100
LAPIC_ICR_LOW: t.CDefine = 0x300
LAPIC_ICR_HIGH: t.CDefine = 0x310
LAPIC_LVT_TIMER: t.CDefine = 0x320
LAPIC_LVT_LINT0: t.CDefine = 0x350
LAPIC_LVT_LINT1: t.CDefine = 0x360
LAPIC_LVT_ERROR: t.CDefine = 0x370
LAPIC_TIMER_INIT: t.CDefine = 0x380
LAPIC_TIMER_CURRENT: t.CDefine = 0x390
LAPIC_TIMER_DIVIDE: t.CDefine = 0x3E0
LAPIC_SVR_ENABLE: t.CDefine = 0x100
LAPIC_LVT_MASKED: t.CDefine = 0x10000
LAPIC_LVT_DELIVERY_EXTINT: t.CDefine = 0x700
LAPIC_LVT_DELIVERY_NMI: t.CDefine = 0x400
LAPIC_LVT_TIMER_PERIODIC: t.CDefine = 0x20000
LAPIC_ICR_INIT: t.CDefine = 0x500
LAPIC_ICR_STARTUP: t.CDefine = 0x600
LAPIC_ICR_ASSERT: t.CDefine = 0x4000
LAPIC_ICR_DEASSERT: t.CDefine = 0x8000
LAPIC_ICR_LEVEL: t.CDefine = 0x8000
MAX_CPUS: t.CDefine = 8
AP_TRAMPOLINE_ADDR: t.CDefine = 0x7000
_lapic_base: t.CStatic | t.CUInt64T = 0
_cpu_count: t.CStatic | t.CUInt32T = 1
_bsp_lapic_id: t.CStatic | t.CUInt32T = 0
_ap_ready: t.CStatic | t.CUInt32T = 0
def read(offset: t.CUInt32T) -> t.CUInt32T:
addr: t.CUInt64T = _lapic_base + t.CUInt64T(offset)
return c.Deref(t.CUInt32T(addr, t.CPtr))
def write(offset: t.CUInt32T, value: t.CUInt32T):
addr: t.CUInt64T = _lapic_base + t.CUInt64T(offset)
c.Set(c.Deref(t.CUInt32T(addr, t.CPtr)), value)
def eoi():
write(LAPIC_EOI, 0)
def get_id() -> t.CUInt32T:
return read(LAPIC_ID) >> 24
@c.Attribute(t.attr.noinline)
def init():
global _lapic_base, _bsp_lapic_id
msr_val: t.CUInt64T = cpu.msr_read(IA32_APIC_BASE)
_lapic_base = msr_val & APIC_BASE_ADDR_MASK
cpu.msr_write(IA32_APIC_BASE, msr_val | APIC_BASE_MSR_ENABLE)
_bsp_lapic_id = read(LAPIC_ID) >> 24
write(LAPIC_SVR, LAPIC_SVR_ENABLE | 0xFF)
write(LAPIC_TPR, 0)
write(LAPIC_LVT_TIMER, LAPIC_LVT_MASKED)
write(LAPIC_LVT_LINT0, LAPIC_LVT_DELIVERY_EXTINT)
write(LAPIC_LVT_LINT1, LAPIC_LVT_DELIVERY_NMI)
write(LAPIC_LVT_ERROR, LAPIC_LVT_MASKED)
write(LAPIC_TIMER_DIVIDE, 0x0B)
write(LAPIC_LDR, 0x01000000)
eoi()
@c.Attribute(t.attr.noinline)
def detect_cpu_count() -> t.CUInt32T:
res: cpu.cpuid_result
cpu.cpuid(0, c.Addr(res))
max_leaf: t.CUInt32T = res.eax
if max_leaf >= 0x0B:
cpu.cpuid_sub(0x0B, 0, c.Addr(res))
num_threads: t.CUInt32T = res.ebx & 0xFFFF
if num_threads > 0 and num_threads <= MAX_CPUS:
return num_threads
cpu.cpuid(1, c.Addr(res))
if res.edx & cpu.CPU_FEATURE_HTT:
count: t.CUInt32T = (res.ebx >> 16) & 0xFF
if count > 0 and count <= MAX_CPUS:
return count
return 1
def _wait_icr():
timeout: t.CUInt32T = 1000000
while timeout > 0:
val: t.CUInt32T = read(LAPIC_ICR_LOW)
if (val & (1 << 12)) == 0:
return
timeout -= 1
def _send_ipi(apic_id: t.CUInt32T, vector: t.CUInt32T, delivery_mode: t.CUInt32T):
_wait_icr()
write(LAPIC_ICR_HIGH, apic_id << 24)
write(LAPIC_ICR_LOW, vector | delivery_mode | LAPIC_ICR_LEVEL | LAPIC_ICR_ASSERT)
_wait_icr()
def ap_entry():
global _ap_ready
my_id: t.CUInt32T = get_id()
buf: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(buf), 64, "[AP] core %u started (lapic=%u)\n", my_id, my_id)
serial.puts(buf)
_ap_ready = 1
while True:
asm.sti()
asm.hlt()
_trampoline_bin: t.CStatic | t.CArray[t.CUInt8T, 128] = [
0xFA, # cli
0x31, 0xC0, # xor ax, ax
0x8E, 0xD8, # mov ds, ax
0x8E, 0xC0, # mov es, ax
0x0F, 0x01, 0x16, 0x00, 0x7E, # lgdt [0x7E00]
0x0F, 0x20, 0xC0, # mov eax, cr0
0x83, 0xC8, 0x01, # or eax, 1
0x0F, 0x22, 0xC0, # mov cr0, eax
0xEA, 0x1A, 0x07, 0x00, 0x00, 0x08, 0x00, # jmp 0x08:0x701A
# 32-bit 保护模式开始
0xB8, 0x10, 0x00, 0x00, 0x00, # mov ax, 0x10
0x8E, 0xD8, # mov ds, ax
0x8E, 0xC0, # mov es, ax
0x8E, 0xE0, # mov fs, ax
0x8E, 0xE8, # mov gs, ax
0x8E, 0xD0, # mov ss, ax
0x0F, 0x20, 0xE0, # mov eax, cr4
0x83, 0xC8, 0x20, # or eax, 0x20 (PAE)
0x0F, 0x22, 0xE0, # mov cr4, eax
0x8B, 0x04, 0x25, 0x00, 0x7F, 0x00, 0x00, # mov eax, [0x7F00]
0x0F, 0x22, 0xD8, # mov cr3, eax
0xB9, 0x80, 0x00, 0x00, 0xC0, # mov ecx, 0xC0000080 (EFER)
0x0F, 0x32, # rdmsr
0x83, 0xC8, 0x00, 0x01, 0x00, 0x00, # or eax, 0x100 (LME)
0x0F, 0x79, # wrmsr
0x0F, 0x20, 0xC0, # mov eax, cr0
0x0D, 0x00, 0x00, 0x00, 0x80, # or eax, 0x80000000 (PG)
0x0F, 0x22, 0xC0, # mov cr0, eax
0xEA, 0x5A, 0x07, 0x00, 0x00, 0x08, 0x00, # jmp 0x08:0x705A
# 64-bit 长模式开始
0x48, 0x8B, 0x24, 0x25, 0x08, 0x7F, 0x00, 0x00, # mov rsp, [0x7F08]
0x0F, 0x01, 0x16, 0x10, 0x7F, # lgdt [0x7F10]
0x0F, 0x01, 0x1E, 0x20, 0x7F, # lidt [0x7F20]
0xB8, 0x10, 0x00, 0x00, 0x00, # mov ax, 0x10
0x8E, 0xD8, # mov ds, ax
0x8E, 0xC0, # mov es, ax
0x8E, 0xE0, # mov fs, ax
0x8E, 0xE8, # mov gs, ax
0x8E, 0xD0, # mov ss, ax
0x48, 0x8B, 0x04, 0x25, 0x28, 0x7F, 0x00, 0x00, # mov rax, [0x7F28]
0xFF, 0xE0 # jmp rax
]
def _setup_trampoline(entry_addr: t.CUInt64T, stack_addr: t.CUInt64T, cr3_val: t.CUInt64T):
dest: t.CUInt8T | t.CPtr = t.CUInt8T(AP_TRAMPOLINE_ADDR, t.CPtr)
src: t.CUInt8T | t.CPtr = c.Addr(_trampoline_bin)
i: t.CInt
for i in range(128):
c.Set(c.Deref(dest + t.CUInt64T(i)), c.Deref(src + t.CUInt64T(i)))
gdt_ptr_addr: t.CUInt64T = AP_TRAMPOLINE_ADDR + 0x0E00
c.Set(c.Deref(t.CUInt16T(gdt_ptr_addr, t.CPtr)), t.CUInt16T(23))
c.Set(c.Deref(t.CUInt64T(gdt_ptr_addr + 2, t.CPtr)), t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0E10))
gdt_addr: t.CUInt64T = AP_TRAMPOLINE_ADDR + 0x0E10
c.Set(c.Deref(t.CUInt64T(gdt_addr, t.CPtr)), t.CUInt64T(0))
c.Set(c.Deref(t.CUInt64T(gdt_addr + 8, t.CPtr)), t.CUInt64T(0x00AF9A000000FFFF))
c.Set(c.Deref(t.CUInt64T(gdt_addr + 16, t.CPtr)), t.CUInt64T(0x00CF92000000FFFF))
c.Set(c.Deref(t.CUInt32T(AP_TRAMPOLINE_ADDR + 0x0F00, t.CPtr)), t.CUInt32T(cr3_val))
c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F08, t.CPtr)), stack_addr)
gp_addr: t.CUInt64T = t.CUInt64T(c.Addr(gdt.gp))
gdt_base: t.CUInt64T = c.Deref(t.CUInt64T(gp_addr + 2, t.CPtr))
gdt_limit: t.CUInt16T = c.Deref(t.CUInt16T(gp_addr, t.CPtr))
c.Set(c.Deref(t.CUInt16T(AP_TRAMPOLINE_ADDR + 0x0F10, t.CPtr)), gdt_limit)
c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F12, t.CPtr)), gdt_base)
idt_addr: t.CUInt64T = t.CUInt64T(c.Addr(idt.idp))
idt_base: t.CUInt64T = c.Deref(t.CUInt64T(idt_addr + 2, t.CPtr))
idt_limit: t.CUInt16T = c.Deref(t.CUInt16T(idt_addr, t.CPtr))
c.Set(c.Deref(t.CUInt16T(AP_TRAMPOLINE_ADDR + 0x0F20, t.CPtr)), idt_limit)
c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F22, t.CPtr)), idt_base)
c.Set(c.Deref(t.CUInt64T(AP_TRAMPOLINE_ADDR + 0x0F28, t.CPtr)), entry_addr)
def start_aps():
global _cpu_count, _ap_ready
if _cpu_count <= 1: return
cr3_val: t.CUInt64T = 0
c.Asm(f"""mov rax, cr3
mov {c.AsmOut(cr3_val, t.ASM_DESCR.OUTPUT_REG)}, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
stack_size: t.CUInt64T = 8192
i: t.CUInt32T
for i in range(1, _cpu_count):
_ap_ready = 0
stack: t.CVoid | t.CPtr = mm.malloc(stack_size)
if stack is None: continue
stack_top: t.CUInt64T = t.CUInt64T(stack) + stack_size
stack_top = stack_top & ~t.CUInt64T(15)
_setup_trampoline(t.CUInt64T(ap_entry), stack_top, cr3_val)
_send_ipi(i, 0, LAPIC_ICR_INIT)
timer.timer_msleep(10)
_send_ipi(i, (AP_TRAMPOLINE_ADDR >> 12) & 0xFF, LAPIC_ICR_STARTUP)
timer.timer_msleep(1)
_send_ipi(i, (AP_TRAMPOLINE_ADDR >> 12) & 0xFF, LAPIC_ICR_STARTUP)
wait: t.CUInt32T = 0
while _ap_ready == 0 and wait < 5000:
timer.timer_msleep(1)
wait += 1
def early_init():
global _cpu_count, _bsp_lapic_id
features: t.CUInt32T = cpu.detect_features_static()
if features & cpu.CPU_FEATURE_APIC:
init()
_cpu_count = detect_cpu_count()
else:
_cpu_count = 1
def get_cpu_count() -> t.CUInt32T:
return _cpu_count
def get_lapic_id() -> t.CUInt32T:
return get_id()
+346
View File
@@ -0,0 +1,346 @@
import fpu
import asm
import intr.gdt as gdt
import string
import t, c
IA32_KERNEL_GS_BASE: t.CDefine = 0xC0000102
KERN_STACK_SIZE: t.CDefine = 8192
CPU_FEATURE_FPU: t.CDefine = (1 << 0)
CPU_FEATURE_PSE: t.CDefine = (1 << 1)
CPU_FEATURE_MSR: t.CDefine = (1 << 2)
CPU_FEATURE_PAE: t.CDefine = (1 << 3)
CPU_FEATURE_MCE: t.CDefine = (1 << 4)
CPU_FEATURE_CX8: t.CDefine = (1 << 5)
CPU_FEATURE_APIC: t.CDefine = (1 << 6)
CPU_FEATURE_SEP: t.CDefine = (1 << 7)
CPU_FEATURE_MTRR: t.CDefine = (1 << 8)
CPU_FEATURE_PGE: t.CDefine = (1 << 9)
CPU_FEATURE_MCA: t.CDefine = (1 << 10)
CPU_FEATURE_CMOV: t.CDefine = (1 << 11)
CPU_FEATURE_PAT: t.CDefine = (1 << 12)
CPU_FEATURE_PSE36: t.CDefine = (1 << 13)
CPU_FEATURE_PSN: t.CDefine = (1 << 14)
CPU_FEATURE_CLFSH: t.CDefine = (1 << 15)
CPU_FEATURE_DS: t.CDefine = (1 << 16)
CPU_FEATURE_ACPI: t.CDefine = (1 << 17)
CPU_FEATURE_MMX: t.CDefine = (1 << 18)
CPU_FEATURE_FXSR: t.CDefine = (1 << 19)
CPU_FEATURE_SSE: t.CDefine = (1 << 20)
CPU_FEATURE_SSE2: t.CDefine = (1 << 21)
CPU_FEATURE_SS: t.CDefine = (1 << 22)
CPU_FEATURE_HTT: t.CDefine = (1 << 23)
CPU_FEATURE_TM: t.CDefine = (1 << 24)
CPU_FEATURE_PBE: t.CDefine = (1 << 25)
class cpu_info(t.CStruct):
vendor_id: t.CArray[t.CChar, 13]
brand_string: t.CArray[t.CChar, 48]
features: t.CUInt32T
family: t.CUInt32T
model: t.CUInt32T
stepping: t.CUInt32T
cpu_type: t.CUInt32T
ext_model: t.CUInt32T
ext_family: t.CUInt32T
cpu_freq: t.CUInt64T
@c.Attribute(t.attr.packed)
class _tss:
reserved0: t.CUInt64T
rsp0: t.CUInt64T
rsp1: t.CUInt64T
rsp2: t.CUInt64T
reserved1: t.CUInt64T
ist1: t.CUInt64T
ist2: t.CUInt64T
ist3: t.CUInt64T
ist4: t.CUInt64T
ist5: t.CUInt64T
ist6: t.CUInt64T
ist7: t.CUInt64T
reserved2: t.CUInt64T
reserved3: t.CUInt16T
io_map_base: t.CUInt16T
class cpuid_result(t.CStruct):
eax: t.CUInt32T
ebx: t.CUInt32T
ecx: t.CUInt32T
edx: t.CUInt32T
@c.Attribute(t.attr.naked)
def _rdmsr_naked():
c.Asm(f"""rdmsr
shl rdx, 32
or rax, rdx
ret""",
op=[t.ASM_DESCR.CLOBBER_RDX])
@c.Attribute(t.attr.naked)
def _wrmsr_naked():
# msr_write sets ecx=msr, r8=value. wrmsr needs ECX=msr, EDX:EAX=value.
# Old code did `mov rax,rcx` (rax=msr) which wrote (value>>32):msr to MSR,
# corrupting IA32_KERNEL_GS_BASE -> swapgs -> #GP on gs:[0]. Fixed below.
c.Asm(f"""mov rax, r8
mov rdx, r8
shr rdx, 32
wrmsr
ret""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX])
def msr_read(msr: t.CUInt32T) -> t.CUInt64T:
result: t.CUInt64T = 0
func: t.CVoid | t.CPtr = _rdmsr_naked
c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}
mov {c.AsmOut(result, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX])
return result
def msr_write(msr: t.CUInt32T, value: t.CUInt64T):
func: t.CVoid | t.CPtr = _wrmsr_naked
c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)}
mov r8, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8])
@c.Attribute(t.attr.naked)
def _cpuid_naked():
c.Asm(f"""push rbx
mov eax, ecx
cpuid
mov dword ptr [rdx], eax
mov dword ptr [rdx + 4], ebx
mov dword ptr [rdx + 8], ecx
mov dword ptr [rdx + 12], edx
pop rbx
ret""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX,
t.ASM_DESCR.CLOBBER_RDX])
@c.Attribute(t.attr.naked)
def _cpuid_sub_naked():
c.Asm(f"""push rbx
mov eax, ecx
mov ecx, r8d
cpuid
mov dword ptr [rdx], eax
mov dword ptr [rdx + 4], ebx
mov dword ptr [rdx + 8], ecx
mov dword ptr [rdx + 12], edx
pop rbx
ret""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX,
t.ASM_DESCR.CLOBBER_RDX])
@c.Attribute(t.attr.noinline)
def cpuid(leaf: t.CUInt32T, out: cpuid_result | t.CPtr):
func: t.CVoid | t.CPtr = _cpuid_naked
c.Asm(f"""mov ecx, {c.AsmInp(leaf, t.ASM_DESCR.REG_ANY)}
mov rdx, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX,
t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8,
t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10,
t.ASM_DESCR.CLOBBER_R11])
@c.Attribute(t.attr.noinline)
def cpuid_sub(leaf: t.CUInt32T, sub: t.CUInt32T, out: cpuid_result | t.CPtr):
func: t.CVoid | t.CPtr = _cpuid_sub_naked
c.Asm(f"""mov ecx, {c.AsmInp(leaf, t.ASM_DESCR.REG_ANY)}
mov r8d, {c.AsmInp(sub, t.ASM_DESCR.REG_ANY)}
mov rdx, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(func, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX,
t.ASM_DESCR.CLOBBER_RDX, t.ASM_DESCR.CLOBBER_R8,
t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10,
t.ASM_DESCR.CLOBBER_R11])
@c.Attribute(t.attr.noinline)
def detect_features_static() -> t.CUInt32T:
features: t.CUInt32T = 0
res: cpuid_result
cpuid(1, c.Addr(res))
if res.edx & (1 << 0): features |= CPU_FEATURE_FPU
if res.edx & (1 << 3): features |= CPU_FEATURE_PSE
if res.edx & (1 << 5): features |= CPU_FEATURE_MSR
if res.edx & (1 << 6): features |= CPU_FEATURE_PAE
if res.edx & (1 << 7): features |= CPU_FEATURE_MCE
if res.edx & (1 << 8): features |= CPU_FEATURE_CX8
if res.edx & (1 << 9): features |= CPU_FEATURE_APIC
if res.edx & (1 << 11): features |= CPU_FEATURE_SEP
if res.edx & (1 << 12): features |= CPU_FEATURE_MTRR
if res.edx & (1 << 13): features |= CPU_FEATURE_PGE
if res.edx & (1 << 14): features |= CPU_FEATURE_MCA
if res.edx & (1 << 15): features |= CPU_FEATURE_CMOV
if res.edx & (1 << 16): features |= CPU_FEATURE_PAT
if res.edx & (1 << 17): features |= CPU_FEATURE_PSE36
if res.edx & (1 << 18): features |= CPU_FEATURE_PSN
if res.edx & (1 << 19): features |= CPU_FEATURE_CLFSH
if res.edx & (1 << 21): features |= CPU_FEATURE_DS
if res.edx & (1 << 22): features |= CPU_FEATURE_ACPI
if res.edx & (1 << 23): features |= CPU_FEATURE_MMX
if res.edx & (1 << 24): features |= CPU_FEATURE_FXSR
if res.edx & (1 << 25): features |= CPU_FEATURE_SSE
if res.edx & (1 << 26): features |= CPU_FEATURE_SSE2
if res.edx & (1 << 27): features |= CPU_FEATURE_SS
if res.edx & (1 << 28): features |= CPU_FEATURE_HTT
if res.edx & (1 << 29): features |= CPU_FEATURE_TM
if res.edx & (1 << 30): features |= CPU_FEATURE_PBE
return features
@c.Attribute(t.attr.packed)
class per_cpu_data:
user_rsp: t.CUInt64T
kernel_rsp0: t.CUInt64T
current_pid: t.CInt
_pad0: t.CUInt32T
_pad1: t.CUInt64T
_per_cpu: per_cpu_data
@t.Object
class _CPUObject:
tss: _tss
features: t.CUInt32T
def __init__(self):
self.__init_tss__()
def __init_tss__(self):
global _per_cpu
string.memset(c.Addr(self.tss), 0, self.tss.__sizeof__())
self.tss.io_map_base = self.tss.__sizeof__()
string.memset(c.Addr(_per_cpu), 0, per_cpu_data.__sizeof__())
gdt.setTSS64(5, t.CUInt64T(c.Addr(self.tss)), self.tss.__sizeof__() - 1)
gdt.flush()
# IA32_KERNEL_GS_BASE holds the per_cpu address for swapgs.
# When entering from Ring 3, swapgs swaps IA32_GS_BASE (0) with
# IA32_KERNEL_GS_BASE (_per_cpu), making gs: accessible in kernel.
# When returning to Ring 3, swapgs swaps back, restoring GS base to 0.
msr_write(IA32_KERNEL_GS_BASE, t.CUInt64T(c.Addr(_per_cpu)))
def getInfo(self, info: cpu_info | t.CPtr):
if not info: return
res: cpuid_result
cpuid(0, c.Addr(res))
(t.CUInt32T(info.vendor_id, t.CPtr))[0] = res.ebx
(t.CUInt32T(info.vendor_id, t.CPtr))[1] = res.edx
(t.CUInt32T(info.vendor_id, t.CPtr))[2] = res.ecx
info.vendor_id[12] = '\0'
info.features = self.detectFeatures()
cpuid(1, c.Addr(res))
info.stepping = (res.eax >> 0) & 0x0F
info.model = (res.eax >> 4) & 0x0F
info.family = (res.eax >> 8) & 0x0F
info.cpu_type = (res.eax >> 12) & 0x03
info.ext_model = (res.eax >> 16) & 0x0F
info.ext_family = (res.eax >> 20) & 0xFF
for i in range(4):
cpuid(0x80000002 + i, c.Addr(res))
base: t.CUInt32T | t.CPtr = t.CUInt32T(info.brand_string, t.CPtr)
base[i * 4 + 0] = res.eax
base[i * 4 + 1] = res.ebx
base[i * 4 + 2] = res.ecx
base[i * 4 + 3] = res.edx
info.brand_string[47] = '\0'
info.cpu_freq = 2000000000
def detectFeatures(self) -> t.CUInt32T:
features: t.CInt32T = 0
res: cpuid_result
cpuid(1, c.Addr(res))
if res.edx & (1 << 0): features |= CPU_FEATURE_FPU
if res.edx & (1 << 3): features |= CPU_FEATURE_PSE
if res.edx & (1 << 5): features |= CPU_FEATURE_MSR
if res.edx & (1 << 6): features |= CPU_FEATURE_PAE
if res.edx & (1 << 7): features |= CPU_FEATURE_MCE
if res.edx & (1 << 8): features |= CPU_FEATURE_CX8
if res.edx & (1 << 9): features |= CPU_FEATURE_APIC
if res.edx & (1 << 11): features |= CPU_FEATURE_SEP
if res.edx & (1 << 12): features |= CPU_FEATURE_MTRR
if res.edx & (1 << 13): features |= CPU_FEATURE_PGE
if res.edx & (1 << 14): features |= CPU_FEATURE_MCA
if res.edx & (1 << 15): features |= CPU_FEATURE_CMOV
if res.edx & (1 << 16): features |= CPU_FEATURE_PAT
if res.edx & (1 << 17): features |= CPU_FEATURE_PSE36
if res.edx & (1 << 18): features |= CPU_FEATURE_PSN
if res.edx & (1 << 19): features |= CPU_FEATURE_CLFSH
if res.edx & (1 << 21): features |= CPU_FEATURE_DS
if res.edx & (1 << 22): features |= CPU_FEATURE_ACPI
if res.edx & (1 << 23): features |= CPU_FEATURE_MMX
if res.edx & (1 << 24): features |= CPU_FEATURE_FXSR
if res.edx & (1 << 25): features |= CPU_FEATURE_SSE
if res.edx & (1 << 26): features |= CPU_FEATURE_SSE2
if res.edx & (1 << 27): features |= CPU_FEATURE_SS
if res.edx & (1 << 28): features |= CPU_FEATURE_HTT
if res.edx & (1 << 29): features |= CPU_FEATURE_TM
if res.edx & (1 << 30): features |= CPU_FEATURE_PBE
return features
def enableInterrupts(self):
c.Asm("sti", [t.ASM_DESCR.CLOBBER_MEMORY])
def disableInterrupts(self):
c.Asm("cli", [t.ASM_DESCR.CLOBBER_MEMORY])
def enableNmis(self):
current: t.CUInt8T = asm.inb(0x70)
asm.outb(0x70, current & 0x7F)
def disableNmis(self):
current: t.CUInt8T = asm.inb(0x70)
asm.outb(0x70, current | 0x80)
def readCR0(self) -> t.CUInt64T:
cr0: t.CUInt64T
c.Asm(f"""mov rax, cr0
mov {c.AsmOut(cr0, t.ASM_DESCR.OUTPUT_REG)}, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
return cr0
def writeCR0(self, value: t.CUInt64T):
c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)}
mov cr0, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
def readCR3(self) -> t.CUInt64T:
cr3: t.CUInt64T
c.Asm(f"""mov rax, cr3
mov {c.AsmOut(cr3, t.ASM_DESCR.OUTPUT_REG)}, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
return cr3
def writeCR3(self, value: t.CUInt64T):
c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)}
mov cr3, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
def readCR4(self) -> t.CUInt64T:
cr4: t.CUInt64T
c.Asm(f"""mov rax, cr4
mov {c.AsmOut(cr4, t.ASM_DESCR.OUTPUT_REG)}, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
return cr4
def writeCR4(self, value: t.CUInt64T):
c.Asm(f"""mov rax, {c.AsmInp(value, t.ASM_DESCR.REG_ANY)}
mov cr4, rax""",
[t.ASM_DESCR.CLOBBER_RAX])
def readMSR(self, msr: t.CUInt32T) -> t.CUInt64T:
return msr_read(msr)
def writeMSR(self, msr: t.CUInt32T, value: t.CUInt64T):
msr_write(msr, value)
def setTSSRSP0(self, rsp0: t.CUInt64T):
global _per_cpu
self.tss.rsp0 = rsp0
_per_cpu.kernel_rsp0 = rsp0
def set_kernel_rsp0(rsp0: t.CUInt64T):
global _per_cpu
_per_cpu.kernel_rsp0 = rsp0
+50
View File
@@ -0,0 +1,50 @@
import cpu
import t, c
# FPU上下文结构体
@c.Attribute(t.attr.aligned(16))
class fpu_context:
data: t.CUInt8T[512] # FXSAVE区域大小
@t.Object
class _FPUObject:
CPUObject: cpu._CPUObject | t.CPtr
# 初始化FPU
def __init__(self, CPUObject: cpu._CPUObject | t.CPtr):
self.enable()
self.CPUObject = CPUObject
c.Asm("fninit", [t.ASM_DESCR.CLOBBER_MEMORY])
# 启用FPU
def enable(self):
cr0: t.CUInt64T = self.CPUObject.readCR0()
# 清除EM位(禁用仿真),设置MP位(监控协处理器)
cr0 &= ~(1 << 2); # 清除EM位
cr0 |= (1 << 1); # 设置MP位
# 写入CR0寄存器
self.CPUObject.writeCR0(cr0)
# 读取CR4寄存器
cr4: t.CUInt64T = self.CPUObject.readCR4()
# 设置OSFXSR位和OSXMMEXCPT位,启用SSE
cr4 |= (1 << 9) | (1 << 10)
# 写入CR4寄存器
self.CPUObject.writeCR4(cr4)
# 初始化FPU
c.Asm("fninit", [t.ASM_DESCR.CLOBBER_MEMORY])
# 禁用FPU
def disable(self):
cr0: t.CUInt64T = self.CPUObject.readCR0()
# 设置EM位(启用仿真),清除MP位(禁用监控协处理器)
cr0 |= (1 << 2) # 设置EM位
cr0 &= ~(1 << 1) # 清除MP位
# 写入CR0寄存器
self.CPUObject.writeCR0(cr0)
# 保存FPU上下文
def save_context(self, context: fpu_context | t.CPtr):
# 保存FPU状态到内存
c.Asm(f"fxsave [{c.AsmInp(context, t.ASM_DESCR.REG_ANY)}]", [t.ASM_DESCR.CLOBBER_MEMORY])
# 恢复FPU上下文
def restore_context(self, context: t.CConst | fpu_context | t.CPtr):
# 从内存恢复FPU状态
c.Asm(f"fxrstor [{c.AsmInp(context, t.ASM_DESCR.REG_ANY)}]", [t.ASM_DESCR.CLOBBER_MEMORY])
+385
View File
@@ -0,0 +1,385 @@
import drivers.serial.uart.serial as serial
import viperstring as string
import t, c
MAX_FILE_DESCRIPTORS: t.CDefine = 64
MAX_DEVICE_FILES: t.CDefine = 32
MAX_DIRECTORIES: t.CDefine = 16
PATH_BUFFER_SIZE: t.CDefine = 256
directories: list[devfs_directory, MAX_DIRECTORIES] = [0]
directory_count: t.CStatic | t.CInt = 0
files: list[devfs_file, MAX_DEVICE_FILES] = [0]
file_count: t.CStatic | t.CInt = 0
class _fd_table:
file: devfs_file | t.CPtr
flags: t.CInt
fd_table: list[_fd_table, MAX_FILE_DESCRIPTORS] = [0]
@t.Object
class devfs_directory:
name: list[t.CChar, 64]
parent: 'devfs_directory' | t.CPtr
@staticmethod
def find(path: t.CConst | str,
root_directory: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr:
if not path or string.strchr(path, '/') != path: return None
if not root_directory: return None
current_dir: devfs_directory | t.CPtr = root_directory
result: list[str, PATH_BUFFER_SIZE]
token = string.split(path, "/", result)
for i in result:
found: bool = False
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == current_dir and string.samestr(dirp.name, i):
current_dir = dirp
found = True
break
if not found: return None
return current_dir
@staticmethod
def find_child(name: t.CConst | str,
parent: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr:
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == parent and string.samestr(dirp.name, name):
return dirp
return None
@staticmethod
def list_subdirs(dir_ptr: devfs_directory | t.CPtr,
buffer: str,
size: t.CUInt32T,
pos: t.CInt) -> t.CInt:
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == dir_ptr:
name_len: t.CInt = string.strlen(dirp.name)
if pos + name_len + 1 > size:
break
string.strcpy(buffer + pos, dirp.name)
pos += name_len
buffer[pos] = '\n'
pos += 1
return pos
@t.Object
@t.CVTable
class devfs_file:
name: list[t.CChar, 64]
private_data: t.CVoid | t.CPtr
parent_dir: 'devfs_directory' | t.CPtr
@staticmethod
def find_in_dir(file_name: t.CConst | str,
dir_ptr: devfs_directory | t.CPtr) -> devfs_file | t.CPtr:
for f in range(file_count):
fp: devfs_file | t.CPtr = c.Addr(files[f])
if fp.parent_dir == dir_ptr and string.samestr(fp.name, file_name):
return fp
return None
@staticmethod
def list_in_dir(dir_ptr: devfs_directory | t.CPtr,
buffer: str,
size: t.CUInt32T,
pos: t.CInt) -> t.CInt:
for f in range(file_count):
filep: devfs_file | t.CPtr = c.Addr(files[f])
if filep.parent_dir == dir_ptr:
name_len: t.CInt = string.strlen(filep.name)
if pos + name_len + 1 > size:
break
string.strcpy(buffer + pos, filep.name)
pos += name_len
buffer[pos] = '\n'
pos += 1
return pos
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
return 0
def close(self, fd: t.CInt) -> t.CInt:
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return 0
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return 0
def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt:
return 0
@t.Object
class _DevFSObject:
root_directory: devfs_directory | t.CPtr
def __init__(self):
global directory_count, file_count
self.root_directory = None
directory_count = 0
file_count = 0
string.memset(c.Addr(directories), 0, devfs_directory.__sizeof__() * MAX_DIRECTORIES)
string.memset(c.Addr(files), 0, devfs_file.__sizeof__() * MAX_DEVICE_FILES)
string.memset(c.Addr(fd_table), 0, _fd_table.__sizeof__() * MAX_FILE_DESCRIPTORS)
def create_single_dir(self, parent: devfs_directory | t.CPtr,
dir_name: t.CConst | str) -> devfs_directory | t.CPtr:
global directory_count
if not parent or not dir_name: return None
if directory_count >= MAX_DIRECTORIES: return None
if string.strlen(dir_name) >= 64: return None
new_dir: devfs_directory | t.CPtr = c.Addr(directories[directory_count])
directory_count += 1
string.strcpy(new_dir.name, dir_name)
new_dir.parent = parent
return new_dir
def find_or_create_directory(self, path: t.CConst | str) -> devfs_directory | t.CPtr:
global directory_count
if not path or string.samestr(path, "/"): return None
if not self.root_directory:
self.root_directory = c.Addr(directories[directory_count])
directory_count += 1
string.strcpy(self.root_directory.name, "")
self.root_directory.parent = None
current: devfs_directory | t.CPtr = self.root_directory
buf: list[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE)
buf_idx: t.CInt = 0
p: t.CInt = 0
if path[p] == '/':
p += 1
while path[p] != '\0':
if path[p] == '/':
if buf_idx > 0:
found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current)
if not found_child:
found_child = self.create_single_dir(current, buf)
if not found_child: return None
current = found_child
string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE)
buf_idx = 0
p += 1
else:
buf[buf_idx] = path[p]
buf_idx += 1
p += 1
if buf_idx > 0:
found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current)
if not found_child:
found_child = self.create_single_dir(current, buf)
if not found_child: return None
current = found_child
return current
def init(self) -> t.CInt:
self.create_directory("/dev")
self.create_directory("/dev/input")
return 0
def create_directory(self, path: t.CConst | str) -> t.CInt:
dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(path)
return 0 if dir_ptr else -1
def create_file(self, path: t.CConst | str,
private_data: t.CVoid | t.CPtr) -> t.CInt:
global file_count
path_copy: list[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE)
string.strcpy(path_copy, path)
last_slash: str | t.CPtr = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str | t.CPtr = last_slash + 1
dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(dir_path)
if not dir_ptr: return -1
if devfs_file.find_in_dir(file_name, dir_ptr):
return -1
if file_count >= MAX_DEVICE_FILES: return -1
new_file: devfs_file | t.CPtr = c.Addr(files[file_count])
file_count += 1
string.strcpy(new_file.name, file_name)
new_file.private_data = private_data
new_file.parent_dir = dir_ptr
return 0
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
path_copy: list[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE)
string.strcpy(path_copy, path)
last_slash: str | t.CPtr = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str | t.CPtr = last_slash + 1
dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(dir_path, self.root_directory)
if not dir_ptr: return -1
file_ptr: devfs_file | t.CPtr = devfs_file.find_in_dir(file_name, dir_ptr)
if not file_ptr: return -1
if not file_ptr.open:
return -1
fd: t.CInt = -1
for i in range(MAX_FILE_DESCRIPTORS):
if not fd_table[i].file:
fd = i
break
if fd == -1: return -1
ret: t.CInt = file_ptr.open(path, flags)
if ret != 0: return ret
fd_table[fd].file = file_ptr
fd_table[fd].flags = flags
return fd
def close(self, fd: t.CInt) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
if file_ptr and file_ptr.close:
file_ptr.close(fd)
fd_table[fd].file = None
fd_table[fd].flags = 0
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
if file_ptr and file_ptr.read:
return file_ptr.read(fd, buffer, size)
return 0
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
if file_ptr and file_ptr.write:
return file_ptr.write(fd, buffer, size)
return 0
def ioctl(self, fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
if file_ptr and file_ptr.ioctl:
return file_ptr.ioctl(fd, cmd, arg)
return 0
def list_directory(self, path: t.CConst | str,
buffer: str,
size: t.CUInt32T) -> t.CInt:
dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(path, self.root_directory)
if not dir_ptr or not buffer or size == 0: return -1
pos: t.CInt = devfs_directory.list_subdirs(dir_ptr, buffer, size, 0)
pos = devfs_file.list_in_dir(dir_ptr, buffer, size, pos)
return pos
DevFSObject: _DevFSObject | t.CPtr
_DevFSObject_instance: _DevFSObject
def devfs_init() -> t.CInt:
global DevFSObject, _DevFSObject_instance
DevFSObject = c.Addr(_DevFSObject_instance)
_DevFSObject_instance.__init__()
DevFSObject.init()
return 0
def devfs_create_directory(path: t.CConst | str) -> t.CInt:
return DevFSObject.create_directory(path)
def devfs_create_file(path: t.CConst | str,
private_data: t.CVoid | t.CPtr) -> t.CInt:
return DevFSObject.create_file(path, private_data)
def devfs_open(path: t.CConst | str, flags: t.CInt) -> t.CInt:
return DevFSObject.open(path, flags)
def devfs_close(fd: t.CInt) -> t.CInt:
return DevFSObject.close(fd)
def devfs_read(fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return DevFSObject.read(fd, buffer, size)
def devfs_write(fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return DevFSObject.write(fd, buffer, size)
def devfs_ioctl(fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt:
return DevFSObject.ioctl(fd, cmd, arg)
def devfs_list_directory(path: t.CConst | str,
buffer: str,
size: t.CUInt32T) -> t.CInt:
return DevFSObject.list_directory(path, buffer, size)
serial0_file: devfs_file
def serial0_open(self: t.CVoid | t.CPtr, path: t.CConst | str, flags: t.CInt) -> t.CInt:
serial.puts("[serial0] open: ")
serial.puts(path)
serial.puts("\n")
return 0
def serial0_close(self: t.CVoid | t.CPtr, fd: t.CInt) -> t.CInt:
serial.puts("[serial0] close\n")
return 0
def serial0_read(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial0] read\n")
return 0
def serial0_write(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial0] write\n")
serial.puts(str(buffer))
serial.puts("\n")
return size
def devfs_register_serial0() -> t.CInt:
global serial0_file
serial0_file = devfs_file()
serial0_file.open = serial0_open
serial0_file.close = serial0_close
serial0_file.read = serial0_read
serial0_file.write = serial0_write
serial0_file.ioctl = None
return devfs_create_file("/dev/serial0", serial0_file)
+491
View File
@@ -0,0 +1,491 @@
import drivers.serial.uart.serial as serial
import viperstring as string
import t, c
MAX_FILE_DESCRIPTORS: t.CDefine = 64
MAX_DEVICE_FILES: t.CDefine = 32
MAX_DIRECTORIES: t.CDefine = 16
PATH_BUFFER_SIZE: t.CDefine = 256
directories: t.CArray[devfs_directory, MAX_DIRECTORIES] = [0]
directory_count: t.CStatic | t.CInt = 0
files: t.CArray[devfs_file, MAX_DEVICE_FILES] = [0]
file_count: t.CStatic | t.CInt = 0
class _fd_table:
file: devfs_file | t.CPtr
flags: t.CInt
fd_table: t.CArray[_fd_table, MAX_FILE_DESCRIPTORS] = [0]
@t.Object
class devfs_directory:
name: t.CArray[t.CChar, 64]
parent: 'devfs_directory' | t.CPtr
@staticmethod
def find(path: t.CConst | str,
root_directory: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr:
if not path or string.strchr(path, '/') != path: return None
if not root_directory: return None
current_dir: devfs_directory | t.CPtr = root_directory
result: t.CArray[str, PATH_BUFFER_SIZE]
token = string.split(path, "/", result)
for i in result:
found: bool = False
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == current_dir and string.samestr(dirp.name, i):
current_dir = dirp
found = True
break
if not found: return None
return current_dir
@staticmethod
def find_child(name: t.CConst | str,
parent: devfs_directory | t.CPtr) -> devfs_directory | t.CPtr:
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == parent and string.samestr(dirp.name, name):
return dirp
return None
@staticmethod
def list_subdirs(dir_ptr: devfs_directory | t.CPtr,
buffer: str,
size: t.CUInt32T,
pos: t.CInt) -> t.CInt:
for d in range(directory_count):
dirp: devfs_directory = c.Addr(directories[d])
if dirp.parent == dir_ptr:
name_len: t.CInt = string.strlen(dirp.name)
if pos + name_len + 1 > size:
break
string.strcpy(buffer + pos, dirp.name)
pos += name_len
buffer[pos] = '\n'
pos += 1
return pos
@t.Object
@t.CVTable
class devfs_file:
name: t.CArray[t.CChar, 64]
private_data: t.CVoid | t.CPtr
parent_dir: 'devfs_directory' | t.CPtr
@staticmethod
def find_in_dir(file_name: t.CConst | str,
dir_ptr: devfs_directory | t.CPtr) -> devfs_file | t.CPtr:
for f in range(file_count):
fp: devfs_file | t.CPtr = c.Addr(files[f])
if fp.parent_dir == dir_ptr and string.samestr(fp.name, file_name):
return fp
return None
@staticmethod
def list_in_dir(dir_ptr: devfs_directory | t.CPtr,
buffer: str,
size: t.CUInt32T,
pos: t.CInt) -> t.CInt:
for f in range(file_count):
filep: devfs_file | t.CPtr = c.Addr(files[f])
if filep.parent_dir == dir_ptr:
name_len: t.CInt = string.strlen(filep.name)
if pos + name_len + 1 > size:
break
string.strcpy(buffer + pos, filep.name)
pos += name_len
buffer[pos] = '\n'
pos += 1
return pos
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
return 0
def close(self, fd: t.CInt) -> t.CInt:
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return 0
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return 0
def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt:
return 0
@t.Object
class _DevFSObject:
root_directory: devfs_directory | t.CPtr
def __init__(self):
global directory_count, file_count
self.root_directory = None
directory_count = 0
file_count = 0
string.memset(c.Addr(directories), 0, devfs_directory.__sizeof__() * MAX_DIRECTORIES)
string.memset(c.Addr(files), 0, devfs_file.__sizeof__() * MAX_DEVICE_FILES)
string.memset(c.Addr(fd_table), 0, _fd_table.__sizeof__() * MAX_FILE_DESCRIPTORS)
def create_single_dir(self, parent: devfs_directory | t.CPtr,
dir_name: t.CConst | str) -> devfs_directory | t.CPtr:
global directory_count
if not parent or not dir_name: return None
if directory_count >= MAX_DIRECTORIES: return None
if string.strlen(dir_name) >= 64: return None
new_dir: devfs_directory | t.CPtr = c.Addr(directories[directory_count])
directory_count += 1
string.strcpy(new_dir.name, dir_name)
new_dir.parent = parent
return new_dir
def find_or_create_directory(self, path: t.CConst | str) -> devfs_directory | t.CPtr:
global directory_count
if not path or string.samestr(path, "/"): return None
serial.puts("A")
if not self.root_directory:
self.root_directory = c.Addr(directories[directory_count])
directory_count += 1
string.strcpy(self.root_directory.name, "")
self.root_directory.parent = None
serial.puts("B")
current: devfs_directory | t.CPtr = self.root_directory
buf: t.CArray[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE)
buf_idx: t.CInt = 0
p: t.CInt = 0
if path[p] == '/':
p += 1
serial.puts("C")
while path[p] != '\0':
if path[p] == '/':
if buf_idx > 0:
found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current)
if not found_child:
found_child = self.create_single_dir(current, buf)
if not found_child: return None
current = found_child
string.memset(c.Addr(buf), 0, PATH_BUFFER_SIZE)
buf_idx = 0
p += 1
else:
buf[buf_idx] = path[p]
buf_idx += 1
p += 1
serial.puts("D")
if buf_idx > 0:
found_child: devfs_directory | t.CPtr = devfs_directory.find_child(buf, current)
if not found_child:
found_child = self.create_single_dir(current, buf)
if not found_child: return None
current = found_child
serial.puts("E")
return current
def init(self) -> t.CInt:
self.create_directory("/dev")
self.create_directory("/dev/input")
return 0
def create_directory(self, path: t.CConst | str) -> t.CInt:
serial.puts("T1")
dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(path)
serial.puts("T2")
return 0 if dir_ptr else -1
def create_file(self, path: t.CConst | str,
file_ptr: devfs_file | t.CPtr,
private_data: t.CVoid | t.CPtr) -> t.CInt:
global file_count
path_copy: t.CArray[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE)
string.strcpy(path_copy, path)
last_slash: str | t.CPtr = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str | t.CPtr = last_slash + 1
dir_ptr: devfs_directory | t.CPtr = self.find_or_create_directory(dir_path)
if not dir_ptr: return -1
if devfs_file.find_in_dir(file_name, dir_ptr):
return -1
if file_count >= MAX_DEVICE_FILES: return -1
if file_ptr:
string.strcpy(file_ptr.name, file_name)
file_ptr.private_data = private_data
file_ptr.parent_dir = dir_ptr
files[file_count] = file_ptr
else:
new_file: devfs_file | t.CPtr = c.Addr(files[file_count])
string.strcpy(new_file.name, file_name)
new_file.private_data = private_data
new_file.parent_dir = dir_ptr
file_count += 1
return 0
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
path_copy: t.CArray[t.CChar, PATH_BUFFER_SIZE]
string.memset(c.Addr(path_copy), 0, PATH_BUFFER_SIZE)
string.strcpy(path_copy, path)
last_slash: str | t.CPtr = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str | t.CPtr = last_slash + 1
dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(dir_path, self.root_directory)
if not dir_ptr: return -1
file_ptr: devfs_file | t.CPtr = devfs_file.find_in_dir(file_name, dir_ptr)
if not file_ptr: return -1
fd: t.CInt = -1
for i in range(MAX_FILE_DESCRIPTORS):
if not fd_table[i].file:
fd = i
break
if fd == -1: return -1
ret: t.CInt = file_ptr.open(path, flags)
if ret != 0: return ret
fd_table[fd].file = file_ptr
fd_table[fd].flags = flags
return fd
def close(self, fd: t.CInt) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
file_ptr.close(fd)
fd_table[fd].file = None
fd_table[fd].flags = 0
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
return file_ptr.read(fd, buffer, size)
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
return file_ptr.write(fd, buffer, size)
def ioctl(self, fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file | t.CPtr = fd_table[fd].file
return file_ptr.ioctl(fd, cmd, arg)
def list_directory(self, path: t.CConst | str,
buffer: str,
size: t.CUInt32T) -> t.CInt:
dir_ptr: devfs_directory | t.CPtr = devfs_directory.find(path, self.root_directory)
if not dir_ptr or not buffer or size == 0: return -1
pos: t.CInt = devfs_directory.list_subdirs(dir_ptr, buffer, size, 0)
pos = devfs_file.list_in_dir(dir_ptr, buffer, size, pos)
return pos
DevFSObject: _DevFSObject | t.CPtr
_DevFSObject_instance: _DevFSObject
def devfs_init() -> t.CInt:
global DevFSObject, _DevFSObject_instance
DevFSObject = c.Addr(_DevFSObject_instance)
_DevFSObject_instance = _DevFSObject()
# _DevFSObject_instance.__init__()
DevFSObject.init()
return 0
def devfs_create_directory(path: t.CConst | str) -> t.CInt:
return DevFSObject.create_directory(path)
def devfs_create_file(path: t.CConst | str,
file_ptr: devfs_file | t.CPtr,
private_data: t.CVoid | t.CPtr) -> t.CInt:
return DevFSObject.create_file(path, file_ptr, private_data)
def devfs_open(path: t.CConst | str, flags: t.CInt) -> t.CInt:
return DevFSObject.open(path, flags)
def devfs_close(fd: t.CInt) -> t.CInt:
return DevFSObject.close(fd)
def devfs_read(fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return DevFSObject.read(fd, buffer, size)
def devfs_write(fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return DevFSObject.write(fd, buffer, size)
def devfs_ioctl(fd: t.CInt, cmd: t.CUInt32T, arg: t.CVoid | t.CPtr) -> t.CInt:
return DevFSObject.ioctl(fd, cmd, arg)
def devfs_list_directory(path: t.CConst | str,
buffer: str,
size: t.CUInt32T) -> t.CInt:
return DevFSObject.list_directory(path, buffer, size)
serial0_file: devfs_file
def serial0_open(self: t.CVoid | t.CPtr, path: t.CConst | str, flags: t.CInt) -> t.CInt:
serial.puts("[serial0] open: ")
serial.puts(path)
serial.puts("\n")
return 0
def serial0_close(self: t.CVoid | t.CPtr, fd: t.CInt) -> t.CInt:
serial.puts("[serial0] close\n")
return 0
def serial0_read(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial0] read\n")
return 0
def serial0_write(self: t.CVoid | t.CPtr, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial0] write\n")
serial.puts(str(buffer))
serial.puts("\n")
return size
def devfs_register_serial0() -> t.CInt:
global serial0_file
serial0_file = devfs_file()
serial0_file.open = serial0_open
serial0_file.close = serial0_close
serial0_file.read = serial0_read
serial0_file.write = serial0_write
return devfs_create_file("/dev/serial0", serial0_file, None)
@t.Object
@t.CVTable
class serial1_device(devfs_file):
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
serial.puts("[serial1] open: ")
serial.puts(path)
serial.puts("\n")
return 0
def close(self, fd: t.CInt) -> t.CInt:
serial.puts("[serial1] close\n")
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial1] read\n")
return 0
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
serial.puts("[serial1] write\n")
serial.puts(str(buffer))
serial.puts("\n")
return size
serial1_file: serial1_device
def devfs_register_serial1() -> t.CInt:
global serial1_file
serial1_file = serial1_device()
return devfs_create_file("/dev/serial1", serial1_file, None)
import drivers.input.keyboard.keyboard as keyboard
import drivers.input.mouse.mouse as mouse
@t.Object
@t.CVTable
class keyboard_device(devfs_file):
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
return 0
def close(self, fd: t.CInt) -> t.CInt:
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
count: t.CUInt32T = 0
while count < size and keyboard.has_data():
sc: t.CUInt8T = keyboard.read_scancode()
ch: t.CChar = keyboard.scancode_to_ascii(sc)
if ch != '\0':
c.Set(t.CChar(buffer, t.CPtr)[count], ch)
count += 1
return count
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return -1
def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt:
return 0
kbd_file: keyboard_device
def devfs_register_keyboard() -> t.CInt:
global kbd_file
kbd_file = keyboard_device()
keyboard.init()
return devfs_create_file("/dev/input/keyboard", kbd_file, None)
@t.Object
@t.CVTable
class mouse_device(devfs_file):
def open(self, path: t.CConst | str, flags: t.CInt) -> t.CInt:
return 0
def close(self, fd: t.CInt) -> t.CInt:
return 0
def read(self, fd: t.CInt, buffer: t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
if not mouse.has_data(): return 0
pkt: mouse.mouse_packet
mouse.read_packet(c.Addr(pkt))
if size >= 1:
c.Set(t.CUInt8T(buffer, t.CPtr)[0], pkt.buttons)
if size >= 5:
buf: t.CUInt32T | t.CPtr = t.CVoid(t.CUInt64T(buffer) + 1, t.CPtr)
c.Set(t.CInt32T(buf, t.CPtr)[0], mouse.get_x())
c.Set(t.CInt32T(buf, t.CPtr)[1], mouse.get_y())
return 5
return 1
def write(self, fd: t.CInt, buffer: t.CConst | t.CVoid | t.CPtr, size: t.CUInt32T) -> t.CInt:
return -1
def ioctl(self, fd: t.CInt, request: t.CInt, arg: t.CVoid | t.CPtr) -> t.CInt:
return 0
mse_file: mouse_device
def devfs_register_mouse() -> t.CInt:
global mse_file
mse_file = mouse_device()
mouse.init()
return devfs_create_file("/dev/input/mouse", mse_file, None)
+237
View File
@@ -0,0 +1,237 @@
from stdint import *
import fat32 as fs
import fat32_types as types
import fat32_part as part
import fat32_mkfs as mkfs_mod
import t, c
def print_fresult(res: types.FRESULT, msg: t.CConst) -> t.CInt:
if res == types.FRESULT.FR_OK:
print(f"[成功] {msg}")
return 0
else:
print(f"[失败] {msg}: {res}")
return -1
def print_fresult_void(res: types.FRESULT, msg: t.CConst):
print_fresult(res, msg)
def example_main() -> t.CInt:
print("=== FAT32 文件系统完整示例 ===")
print()
# 1. 扫描可用驱动器
print("--- 1. 扫描可用驱动器 ---")
res: types.FRESULT = fs.scan_drives()
print_fresult_void(res, "扫描驱动器")
drive_count: t.CInt = fs.get_drive_count()
print(f"找到 {drive_count} 个驱动器")
i: t.CInt
for i in range(drive_count):
letter: t.CChar = fs.get_drive_letter(i)
part_count: t.CInt = fs.get_partition_count(letter)
print(f" 驱动器 {i}: {letter}")
for j in range(part_count):
is_fat32: t.CInt = fs.is_partition_fat32(letter, j)
print(f" 分区 {j}: FAT32={is_fat32}")
print()
# 2. 挂载文件系统
print("--- 2. 挂载文件系统 ---")
res = fs.mount()
if print_fresult(res, "挂载文件系统") != 0:
print("提示: 可能需要先格式化磁盘")
print()
# 3. 文件系统信息
print("--- 3. 文件系统信息 ---")
mounted: t.CInt = fs.is_mounted()
print(f"已挂载: {mounted}")
if mounted:
total_clusters: t.CUInt32T = fs.get_total()
free_clusters: t.CUInt32T = fs.get_free()
cluster_size: t.CUInt32T = fs.get_cluster_size()
total_bytes: t.CUInt64T = t.CUInt64T(total_clusters) * t.CUInt64T(cluster_size)
free_bytes: t.CUInt64T = t.CUInt64T(free_clusters) * t.CUInt64T(cluster_size)
print(f"总簇数: {total_clusters}")
print(f"空闲簇数: {free_clusters}")
print(f"簇大小: {cluster_size} 字节")
print(f"总大小: {total_bytes} 字节")
print(f"空闲大小: {free_bytes} 字节")
print()
# 4. 元数据操作
print("--- 4. 元数据操作 ---")
fs.set_metadata(0x12345678)
metadata: t.CUInt32T = fs.get_metadata()
print(f"设置的元数据: 0x{metadata:08X}")
print()
# 5. 获取 FAT 时间
print("--- 5. 获取 FAT 时间 ---")
fattime: t.CUInt32T = fs.get_fattime()
print(f"当前 FAT 时间: 0x{fattime:08X}")
print()
# 6. 目录操作
if fs.is_mounted():
print("--- 6. 目录操作 ---")
# 创建目录
res = fs.mkdir("/test_dir_1")
print_fresult_void(res, "创建目录 /test_dir_1")
res = fs.mkdir("/test_dir_1/subdir")
print_fresult_void(res, "创建子目录 /test_dir_1/subdir")
res = fs.mkdir("/test_dir_2")
print_fresult_void(res, "创建目录 /test_dir_2")
print()
# 7. 文件操作
print("--- 7. 文件操作 ---")
# 创建并写入文件
fp1: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_CREATE_ALWAYS | types.FA_WRITE)
if fp1 is not None:
test_data1: t.CConst = "Hello, World!"
written: t.CUInt32T = fs.write(fp1, test_data1, 13)
print(f"写入文件 /test_dir_1/file1.txt: {written} 字节")
fs.close(fp1)
# 打开追加
fp2: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_OPEN_ALWAYS | types.FA_WRITE | types.FA_OPEN_APPEND)
if fp2 is not None:
test_data2: t.CConst = " Append data!"
written2: t.CUInt32T = fs.write(fp2, test_data2, 13)
print(f"追加写入: {written2} 字节")
fs.close(fp2)
# 读取文件
fp3: types.fileobj | t.CPtr = fs.open("/test_dir_1/file1.txt", types.FA_OPEN_EXISTING | types.FA_READ)
if fp3 is not None:
buf: t.CArray[t.CUInt8T, 256]
read_bytes: t.CUInt32T = fs.read(fp3, t.CVoid(c.Addr(buf[0]), t.CPtr), 256)
print(f"读取字节数: {read_bytes}")
print(f"文件内容: {t.CChar(c.Addr(buf[0]), t.CPtr)}")
# seek 和 tell
fs.seek(fp3, 5)
pos: t.CUInt32T = fs.tell(fp3)
print(f"seek 到位置 5, 当前位置: {pos}")
read_bytes2: t.CUInt32T = fs.read(fp3, t.CVoid(c.Addr(buf[0]), t.CPtr), 256)
print(f"从位置 5 读取: {t.CChar(c.Addr(buf[0]), t.CPtr)}")
# 获取文件大小
fsize: t.CUInt32T = fs.size(fp3)
print(f"文件大小: {fsize} 字节")
fs.close(fp3)
# 创建另一个文件
fp4: types.fileobj | t.CPtr = fs.open("/test_dir_1/large_file.txt", types.FA_CREATE_ALWAYS | types.FA_WRITE)
if fp4 is not None:
large_data: t.CArray[t.CUInt8T, 1024]
k: t.CInt
for k in range(1024):
large_data[k] = t.CUInt8T(ord('A') + (k % 26))
written3: t.CUInt32T = fs.write(fp4, t.CVoid(c.Addr(large_data[0]), t.CPtr), 1024)
print(f"创建大文件, 写入: {written3} 字节")
fs.close(fp4)
print()
# 8. 文件状态和属性
print("--- 8. 文件状态和属性 ---")
info: types.fileinfo
res = fs.stat("/test_dir_1/file1.txt", info)
print_fresult_void(res, "获取文件信息")
if res == types.FRESULT.FR_OK:
print(f" 文件名: {info.fname}")
print(f" 大小: {info.file_size} 字节")
print(f" 属性: 0x{info.attr:02X}")
print(f" 创建时间: 0x{info.ctime:04X}")
print(f" 创建日期: 0x{info.cdate:04X}")
print(f" 修改时间: 0x{info.mtime:04X}")
print(f" 修改日期: 0x{info.mdate:04X}")
print(f" 访问日期: 0x{info.adate:04X}")
# 修改属性
res = fs.chmod("/test_dir_1/file1.txt", types.AM_RDO, types.AM_RDO)
print_fresult_void(res, "设置文件为只读")
print()
# 9. 重命名和移动
print("--- 9. 重命名和移动 ---")
res = fs.rename("/test_dir_1/file1.txt", "/test_dir_1/renamed.txt")
print_fresult_void(res, "重命名文件")
res = fs.move("/test_dir_1/renamed.txt", "/test_dir_2/moved.txt")
print_fresult_void(res, "移动文件")
res = fs.rename("/test_dir_2", "/test_dir_renamed")
print_fresult_void(res, "重命名目录")
print()
# 10. 列出目录
print("--- 10. 列出目录 ---")
dp: types.dirobj
res = fs.opendir("/", dp)
if res == types.FRESULT.FR_OK:
print("根目录内容:")
dir_info: types.fileinfo
while True:
res = fs.readdir(dp, dir_info)
if res != types.FRESULT.FR_OK:
break
attr_str: t.CConst = ""
if dir_info.attr & types.AM_DIR:
attr_str = attr_str + "D"
else:
attr_str = attr_str + "F"
if dir_info.attr & types.AM_RDO:
attr_str = attr_str + "R"
if dir_info.attr & types.AM_HID:
attr_str = attr_str + "H"
if dir_info.attr & types.AM_SYS:
attr_str = attr_str + "S"
if dir_info.attr & types.AM_ARC:
attr_str = attr_str + "A"
print(f" [{attr_str}] {dir_info.fname} ({dir_info.file_size} 字节)")
fs.closedir(dp)
print()
# 11. 删除文件和目录
print("--- 11. 删除文件和目录 ---")
res = fs.remove("/test_dir_renamed/moved.txt")
print_fresult_void(res, "删除文件")
res = fs.remove("/test_dir_1/large_file.txt")
print_fresult_void(res, "删除大文件")
res = fs.rmdir("/test_dir_1/subdir")
print_fresult_void(res, "删除子目录")
res = fs.rmdir("/test_dir_1")
print_fresult_void(res, "删除目录 /test_dir_1")
res = fs.rmdir("/test_dir_renamed")
print_fresult_void(res, "删除目录 /test_dir_renamed")
print()
# 12. 卸载文件系统
print("--- 12. 卸载文件系统 ---")
res = fs.unmount()
print_fresult_void(res, "卸载文件系统")
print()
print("=== 示例完成 ===")
return 0
+208
View File
@@ -0,0 +1,208 @@
from stdint import *
import string
import fat32_types as types
import fat32_time as ftime
import fat32_mkfs as mkfs_mod
import fat32_part as part
import fat32_diskio as diskio
import sched.sched as sched
import t, c
default_fs: types.volinfo = types.volinfo()
_fs_lock: t.CInt = 0
def lock():
global _fs_lock
while True:
c.Asm("cli")
if _fs_lock == 0:
_fs_lock = 1
c.Asm("sti")
return
c.Asm("sti")
sched.Scheduler._yield()
def unlock():
global _fs_lock
_fs_lock = 0
def mount() -> types.FRESULT:
default_fs.pdrv = 0
default_fs.hidden_sectors = 0
buf: t.CArray[t.CUInt8T, 512]
dres: types.DRESULT = diskio.read(0, c.Addr(buf[0]), 0, 1)
if dres == types.DRESULT.RES_OK:
if buf[0] == 0xEB or buf[0] == 0xE9:
default_fs.hidden_sectors = 0
else:
sig: t.CUInt16T = t.CUInt16T(buf[510]) | (t.CUInt16T(buf[511]) << 8)
if sig == 0xAA55:
ptype: t.CUInt8T = buf[450]
if ptype != 0:
slba: t.CUInt32T = t.CUInt32T(buf[454]) | (t.CUInt32T(buf[455]) << 8) | (t.CUInt32T(buf[456]) << 16) | (t.CUInt32T(buf[457]) << 24)
default_fs.hidden_sectors = slba
if default_fs.mounted: return types.FRESULT.FR_OK
dsk_res: types.DRESULT = diskio.status(default_fs.pdrv)
if dsk_res != types.DRESULT.RES_OK:
dsk_res = diskio.init(default_fs.pdrv)
if dsk_res != types.DRESULT.RES_OK: return types.FRESULT.FR_NOT_READY
default_fs.winsect = 0xFFFFFFFFFFFFFFFF
default_fs.fat_winsect = 0xFFFFFFFFFFFFFFFF
wres: types.FRESULT = default_fs.win_read(0)
if wres != types.FRESULT.FR_OK: return types.FRESULT.FR_DISK_ERR
bps: t.CUInt16T = default_fs.win[11] | (t.CUInt16T(default_fs.win[12]) << 8)
spc: t.CUInt8T = default_fs.win[13]
rsc: t.CUInt16T = default_fs.win[14] | (t.CUInt16T(default_fs.win[15]) << 8)
nf: t.CUInt8T = default_fs.win[16]
if bps != 512: return types.FRESULT.FR_NO_FILESYSTEM
if spc == 0 or (spc & (spc - 1)) != 0: return types.FRESULT.FR_NO_FILESYSTEM
if rsc == 0: return types.FRESULT.FR_NO_FILESYSTEM
if nf == 0: return types.FRESULT.FR_NO_FILESYSTEM
root_ent: t.CUInt16T = default_fs.win[17] | (t.CUInt16T(default_fs.win[18]) << 8)
fat16sz: t.CUInt16T = default_fs.win[22] | (t.CUInt16T(default_fs.win[23]) << 8)
tot32: t.CUInt32T = t.CUInt32T(default_fs.win[32]) | (t.CUInt32T(default_fs.win[33]) << 8) | (t.CUInt32T(default_fs.win[34]) << 16) | (t.CUInt32T(default_fs.win[35]) << 24)
fat32sz: t.CUInt32T = t.CUInt32T(default_fs.win[36]) | (t.CUInt32T(default_fs.win[37]) << 8) | (t.CUInt32T(default_fs.win[38]) << 16) | (t.CUInt32T(default_fs.win[39]) << 24)
root_clus: t.CUInt32T = t.CUInt32T(default_fs.win[44]) | (t.CUInt32T(default_fs.win[45]) << 8) | (t.CUInt32T(default_fs.win[46]) << 16) | (t.CUInt32T(default_fs.win[47]) << 24)
if root_ent != 0 or fat16sz != 0: return types.FRESULT.FR_NO_FILESYSTEM
if fat32sz == 0: return types.FRESULT.FR_NO_FILESYSTEM
if tot32 == 0: return types.FRESULT.FR_NO_FILESYSTEM
if default_fs.win[510] != 0x55 or default_fs.win[511] != 0xAA: return types.FRESULT.FR_NO_FILESYSTEM
fsinfo_sec: t.CUInt16T = default_fs.win[48] | (t.CUInt16T(default_fs.win[49]) << 8)
bkboot_sec: t.CUInt16T = default_fs.win[50] | (t.CUInt16T(default_fs.win[51]) << 8)
default_fs.sector_size = 512
default_fs.cluster_sectors = spc
default_fs.cluster_size = t.CUInt32T(spc) * 512
default_fs.num_fats = nf
default_fs.reserved_sectors = rsc
default_fs.fat_size = fat32sz
default_fs.fat_start = t.CUInt64T(rsc)
default_fs.total_sectors = t.CUInt64T(tot32)
default_fs.root_cluster = root_clus
default_fs.fsinfo_sector = fsinfo_sec
default_fs.backup_sector = bkboot_sec
default_fs.vol_id = t.CUInt32T(default_fs.win[67]) | (t.CUInt32T(default_fs.win[68]) << 8) | (t.CUInt32T(default_fs.win[69]) << 16) | (t.CUInt32T(default_fs.win[70]) << 24)
data_sectors: t.CUInt64T = default_fs.total_sectors - t.CUInt64T(rsc) - t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz))
default_fs.total_clusters = t.CUInt32T(data_sectors / t.CUInt64T(spc))
default_fs.data_start = default_fs.fat_start + t.CUInt64T(t.CUInt64T(nf) * t.CUInt64T(fat32sz))
default_fs.last_alloc = 2
default_fs.free_clusters = 0xFFFFFFFF
if fsinfo_sec != 0:
default_fs.fsinfo_read()
default_fs.mounted = 1
return types.FRESULT.FR_OK
def unmount() -> types.FRESULT:
return default_fs.unmount()
def format(sec_per_clus: t.CUInt8T = 0, vol_id: t.CUInt32T = 0) -> types.FRESULT:
return mkfs_mod.mkfs(default_fs.pdrv, sec_per_clus, vol_id)
def open(path: t.CConst | str, mode: t.CUInt8T) -> types.fileobj | t.CPtr:
return default_fs.file_open(path, mode)
def close(fp: types.fileobj | t.CPtr) -> types.FRESULT:
return fp.close()
def read(fp: types.fileobj | t.CPtr, buff: t.CVoid | t.CPtr, btr: t.CUInt32T) -> t.CUInt32T:
br: t.CUInt32T = 0
fp.read(buff, btr, c.Addr(br))
return br
def write(fp: types.fileobj | t.CPtr, buff: t.CConst | t.CVoid | t.CPtr, btw: t.CUInt32T) -> t.CUInt32T:
bw: t.CUInt32T = 0
fp.write(buff, btw, c.Addr(bw))
return bw
def seek(fp: types.fileobj | t.CPtr, offset: t.CUInt32T) -> types.FRESULT:
return fp.seek(offset)
def tell(fp: types.fileobj | t.CPtr) -> t.CUInt32T:
return fp.tell()
def size(fp: types.fileobj | t.CPtr) -> t.CUInt32T:
return fp.size()
def truncate(fp: types.fileobj | t.CPtr) -> types.FRESULT:
return fp.truncate()
def remove(path: t.CConst | str) -> types.FRESULT:
return default_fs.file_delete(path)
def rename(old_path: t.CConst | str, new_path: t.CConst | str) -> types.FRESULT:
return default_fs.file_rename(old_path, new_path)
def move(old_path: t.CConst | str, new_path: t.CConst | str) -> types.FRESULT:
return default_fs.file_rename(old_path, new_path)
def stat(path: t.CConst | str, info: types.fileinfo | t.CPtr) -> types.FRESULT:
return default_fs.file_stat(path, info)
def mkdir(path: t.CConst | str) -> types.FRESULT:
return default_fs.dir_make(path)
def rmdir(path: t.CConst | str) -> types.FRESULT:
return default_fs.dir_remove(path)
def opendir(path: t.CConst | str, dp: types.dirobj | t.CPtr) -> types.FRESULT:
return default_fs.dir_open_path(path, dp)
def readdir(dp: types.dirobj | t.CPtr, info: types.fileinfo | t.CPtr) -> types.FRESULT:
return default_fs.dir_read(dp, info)
def closedir(dp: types.dirobj | t.CPtr):
dp.is_open = 0
def chmod(path: t.CConst | str, attr: t.CUInt8T, mask: t.CUInt8T) -> types.FRESULT:
return default_fs.chmod(path, attr, mask)
def utime(path: t.CConst | str, mtime: t.CUInt16T, mdate: t.CUInt16T) -> types.FRESULT:
return default_fs.utime(path, mtime, mdate)
def get_free() -> t.CUInt32T:
if not default_fs.mounted: return 0
if default_fs.free_clusters == 0xFFFFFFFF:
return default_fs.count_free_clusters()
return default_fs.free_clusters
def get_total() -> t.CUInt32T:
if not default_fs.mounted: return 0
return default_fs.total_clusters
def get_cluster_size() -> t.CUInt32T:
if not default_fs.mounted: return 0
return default_fs.cluster_size
def is_mounted() -> t.CInt:
return default_fs.mounted
def last_error() -> t.CUInt32T:
return default_fs.last_error
def get_fattime() -> t.CUInt32T:
return ftime.get_fattime()
def get_metadata() -> t.CUInt32T:
return default_fs.metadata
def set_metadata(val: t.CUInt32T):
default_fs.metadata = val
def scan_drives() -> types.FRESULT:
return part.scan_drives()
def get_drive_count() -> t.CInt:
return part.get_drive_count()
def get_drive_letter(idx: t.CInt) -> t.CChar:
return part.get_drive_letter(idx)
def set_drive_letter(idx: t.CInt, letter: t.CChar) -> types.FRESULT:
return part.set_drive_letter(idx, letter)
def get_partition_count(letter: t.CChar) -> t.CInt:
return part.get_partition_count(letter)
def is_partition_fat32(letter: t.CChar, part_idx: t.CInt) -> t.CInt:
return part.is_partition_fat32(letter, part_idx)
@@ -0,0 +1,16 @@
from stdint import *
import fat32_types as types
import t, c
_read32 = types._read32
_read16 = types._read16
_write32 = types._write32
_write16 = types._write16
def is_eoc(cluster: t.CUInt32T) -> t.CInt:
return 1 if (cluster >= types.CLUSTER_EOC_MIN and cluster <= types.CLUSTER_EOC_MAX) else 0
def is_free(cluster: t.CUInt32T) -> t.CInt:
return 1 if cluster == types.CLUSTER_FREE else 0
+13
View File
@@ -0,0 +1,13 @@
from stdint import *
import fat32_types as types
import t, c
sfn_checksum = types.sfn_checksum
uni2oem_placeholder = types.uni2oem_placeholder
lfn_get_char = types.lfn_get_char
lfn_set_char = types.lfn_set_char
lfn_extract = types.lfn_extract
sfn_from_lfn = types.sfn_from_lfn
sfn_to_str = types.sfn_to_str
_split_path = types._split_path
@@ -0,0 +1,76 @@
import drivers.storage.ide.ide as ide
import fat32_types as types
import spinlock
import string
import t, c
MAX_DRIVES: t.CDefine = 4
drive_initialized: t.CArray[t.CInt, MAX_DRIVES]
drive_lock: spinlock._spinlock = spinlock._spinlock()
def status(pdrv: t.CUInt8T) -> types.DRESULT:
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
return types.DRESULT.RES_OK
def init(pdrv: t.CUInt8T) -> types.DRESULT:
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
drive_lock.lock()
err: t.CInt = ide.init()
if err != 0:
drive_lock.unlock()
return types.DRESULT.RES_NOTRDY
drive_initialized[pdrv] = 1
drive_lock.unlock()
return types.DRESULT.RES_OK
def read(pdrv: t.CUInt8T, buff: t.CUInt8T | t.CPtr, sector: t.CUInt32T, count: t.CUInt32T) -> types.DRESULT:
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
drive_lock.lock()
i: t.CUInt32T
for i in range(count):
rd_res: t.CInt = ide.readSector(sector + i, buff + (i * 512))
if rd_res != 0:
drive_lock.unlock()
return types.DRESULT.RES_ERROR
drive_lock.unlock()
return types.DRESULT.RES_OK
def write(pdrv: t.CUInt8T, buff: t.CConst | t.CUInt8T | t.CPtr, sector: t.CUInt32T, count: t.CUInt32T) -> types.DRESULT:
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
drive_lock.lock()
i: t.CUInt32T
for i in range(count):
if ide.writeSector(sector + i, buff + (i * 512)) != 0:
drive_lock.unlock()
return types.DRESULT.RES_ERROR
drive_lock.unlock()
return types.DRESULT.RES_OK
def ioctl(pdrv: t.CUInt8T, cmd: t.CUInt8T, buff: t.CVoid | t.CPtr) -> types.DRESULT:
if pdrv >= MAX_DRIVES: return types.DRESULT.RES_PARERR
if not drive_initialized[pdrv]: return types.DRESULT.RES_NOTRDY
match cmd:
case types.GET_SECTOR_COUNT:
disk_size: t.CUInt64T = ide.getDiskSize()
if disk_size == 0:
c.Set(t.CUInt32T(buff, t.CPtr), 131072)
elif disk_size > 0xFFFFFFFF:
c.Set(t.CUInt32T(buff, t.CPtr), 0xFFFFFFFF)
else:
c.Set(t.CUInt32T(buff, t.CPtr), t.CUInt32T(disk_size))
return types.DRESULT.RES_OK
case types.GET_SECTOR_SIZE:
c.Set(t.CUInt16T(buff, t.CPtr), 512)
return types.DRESULT.RES_OK
case types.GET_BLOCK_SIZE:
c.Set(t.CUInt32T(buff, t.CPtr), 1)
return types.DRESULT.RES_OK
case types.CTRL_SYNC:
return types.DRESULT.RES_OK
case _:
return types.DRESULT.RES_PARERR
@@ -0,0 +1,17 @@
from stdint import *
import fat32_types as types
import t, c
handle_table = types.handle_table
lock_table = types.lock_table
handles_initialized = types.handles_initialized
handles_init = types.handles_init
handle_alloc = types.handle_alloc
lock_check = types.lock_check
lock_acquire = types.lock_acquire
lock_release = types.lock_release
def handle_free(fp):
fp.is_open = 0
fp.lock_count = 0
+143
View File
@@ -0,0 +1,143 @@
from stdint import *
import fat32_types as types
import fat32_diskio as diskio
import fat32_time as ftime
import string
import t, c
def _div_round_up(a: t.CUInt64T, b: t.CUInt64T) -> t.CUInt64T:
return (a + b - 1) / b
def _calc_fat_size(total_sectors: t.CUInt64T, reserved: t.CUInt32T, sec_per_clus: t.CUInt8T, num_fats: t.CUInt8T) -> t.CUInt32T:
tmp: t.CUInt64T = total_sectors - t.CUInt64T(reserved)
fat_sz: t.CUInt32T = 1
while True:
data_sectors: t.CUInt64T = tmp - t.CUInt64T(fat_sz) * t.CUInt64T(num_fats)
total_clusters: t.CUInt32T = t.CUInt32T(data_sectors / t.CUInt64T(sec_per_clus))
needed: t.CUInt64T = _div_round_up(t.CUInt64T(total_clusters) * 4, 512)
if needed <= t.CUInt64T(fat_sz): break
fat_sz = t.CUInt32T(needed)
return fat_sz
def _write_boot_sector(pdrv: t.CUInt8T, total_sectors: t.CUInt64T, sec_per_clus: t.CUInt8T, vol_id: t.CUInt32T, fat_sz: t.CUInt32T) -> types.DRESULT:
buf: t.CArray[t.CUInt8T, 512]
string.memset(c.Addr(buf[0]), 0, 512)
buf[0] = 0xEB; buf[1] = 0x58; buf[2] = 0x90
buf[3] = 'M'; buf[4] = 'S'; buf[5] = 'W'; buf[6] = 'I'; buf[7] = 'N'
buf[8] = '4'; buf[9] = '.'; buf[10] = '1'
types._write16(c.Addr(buf[0]), 11, 512)
buf[13] = sec_per_clus
types._write16(c.Addr(buf[0]), 14, 32)
buf[16] = 2
types._write16(c.Addr(buf[0]), 17, 0)
buf[21] = 0xF8
types._write16(c.Addr(buf[0]), 22, 0)
types._write16(c.Addr(buf[0]), 24, 63)
types._write16(c.Addr(buf[0]), 26, 255)
types._write32(c.Addr(buf[0]), 28, 0)
types._write32(c.Addr(buf[0]), 32, t.CUInt32T(total_sectors))
types._write32(c.Addr(buf[0]), 36, fat_sz)
types._write16(c.Addr(buf[0]), 40, 0)
types._write16(c.Addr(buf[0]), 42, 0)
types._write32(c.Addr(buf[0]), 44, 2)
types._write16(c.Addr(buf[0]), 48, 1)
types._write16(c.Addr(buf[0]), 50, 6)
buf[64] = 0x80
buf[66] = 0x29
types._write32(c.Addr(buf[0]), 67, vol_id)
vol_label: t.CArray[t.CChar, 11]
vol_label[0] = 'N'; vol_label[1] = 'O'; vol_label[2] = ' '; vol_label[3] = ' '
vol_label[4] = ' '; vol_label[5] = ' '; vol_label[6] = ' '; vol_label[7] = ' '
vol_label[8] = ' '; vol_label[9] = ' '; vol_label[10] = ' '
i: t.CInt
for i in range(11): buf[71 + i] = t.CUInt8T(vol_label[i])
buf[82] = 'F'; buf[83] = 'A'; buf[84] = 'T'; buf[85] = '3'
buf[86] = '2'; buf[87] = ' '; buf[88] = ' '; buf[89] = ' '
buf[510] = 0x55; buf[511] = 0xAA
return diskio.write(pdrv, c.Addr(buf[0]), 0, 1)
def _write_fsinfo(pdrv: t.CUInt8T, free_count: t.CUInt32T, next_free: t.CUInt32T) -> types.DRESULT:
buf: t.CArray[t.CUInt8T, 512]
string.memset(c.Addr(buf[0]), 0, 512)
types._write32(c.Addr(buf[0]), 0, 0x41615252)
types._write32(c.Addr(buf[0]), 484, 0x61417272)
types._write32(c.Addr(buf[0]), 488, free_count)
types._write32(c.Addr(buf[0]), 492, next_free)
types._write32(c.Addr(buf[0]), 508, 0xAA550000)
return diskio.write(pdrv, c.Addr(buf[0]), 1, 1)
def _init_fat(pdrv: t.CUInt8T, fat_start: t.CUInt64T, fat_sz: t.CUInt32T, root_cluster: t.CUInt32T) -> types.DRESULT:
buf: t.CArray[t.CUInt8T, 512]
string.memset(c.Addr(buf[0]), 0, 512)
types._write32(c.Addr(buf[0]), 0, 0x0FFFFFF8)
types._write32(c.Addr(buf[0]), 4, 0x0FFFFFFF)
types._write32(c.Addr(buf[0]), 8, 0x0FFFFFFF)
if root_cluster >= 2:
cluster_off: t.CUInt32T = root_cluster * 4
sector_off: t.CUInt32T = cluster_off / 512
byte_off: t.CUInt32T = cluster_off % 512
if sector_off == 0:
types._write32(c.Addr(buf[0]), byte_off, 0x0FFFFFFF)
res: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(fat_start), 1)
if res != types.DRESULT.RES_OK: return res
string.memset(c.Addr(buf[0]), 0, 512)
s: t.CUInt32T
for s in range(1, fat_sz):
res2: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(fat_start) + s, 1)
if res2 != types.DRESULT.RES_OK: return res2
return types.DRESULT.RES_OK
def _init_root_dir(pdrv: t.CUInt8T, root_sector: t.CUInt64T, sec_per_clus: t.CUInt8T) -> types.DRESULT:
buf: t.CArray[t.CUInt8T, 512]
string.memset(c.Addr(buf[0]), 0, 512)
s: t.CUInt8T
for s in range(sec_per_clus):
res: types.DRESULT = diskio.write(pdrv, c.Addr(buf[0]), t.CUInt32T(root_sector + t.CUInt64T(s)), 1)
if res != types.DRESULT.RES_OK: return res
return types.DRESULT.RES_OK
def _calc_sec_per_clus(total_sectors: t.CUInt64T) -> t.CUInt8T:
ts: t.CUInt64T = total_sectors
if ts < 532480: return 1
elif ts < 16777216: return 8
elif ts < 33554432: return 16
elif ts < 67108864: return 32
else: return 64
def mkfs(pdrv: t.CUInt8T, sec_per_clus: t.CUInt8T, vol_id: t.CUInt32T) -> types.FRESULT:
if pdrv != types.DEV_DISK: return types.FRESULT.FR_INVALID_DRIVE
total_sectors_buf: t.CUInt32T = 0
diskio.ioctl(pdrv, types.GET_SECTOR_COUNT, c.Addr(total_sectors_buf))
total_sectors: t.CUInt64T = t.CUInt64T(total_sectors_buf)
if total_sectors < 65536: return types.FRESULT.FR_MKFS_ABORTED
if sec_per_clus == 0: sec_per_clus = _calc_sec_per_clus(total_sectors)
if sec_per_clus != 1 and sec_per_clus != 2 and sec_per_clus != 4 and sec_per_clus != 8 and sec_per_clus != 16 and sec_per_clus != 32 and sec_per_clus != 64 and sec_per_clus != 128:
return types.FRESULT.FR_INVALID_PARAMETER
if vol_id == 0: vol_id = ftime.get_fattime()
reserved: t.CUInt32T = 32
num_fats: t.CUInt8T = 2
fat_sz: t.CUInt32T = _calc_fat_size(total_sectors, reserved, sec_per_clus, num_fats)
data_sectors: t.CUInt64T = total_sectors - t.CUInt64T(reserved) - t.CUInt64T(fat_sz) * t.CUInt64T(num_fats)
total_clusters: t.CUInt32T = t.CUInt32T(data_sectors / t.CUInt64T(sec_per_clus))
free_count: t.CUInt32T = total_clusters - 1
res: types.DRESULT = _write_boot_sector(pdrv, total_sectors, sec_per_clus, vol_id, fat_sz)
if res != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
res2: types.DRESULT = _write_fsinfo(pdrv, free_count, 3)
if res2 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
boot_backup: t.CArray[t.CUInt8T, 512]
diskio.read(pdrv, c.Addr(boot_backup[0]), 0, 1)
diskio.write(pdrv, c.Addr(boot_backup[0]), 6, 1)
fsinfo_backup: t.CArray[t.CUInt8T, 512]
diskio.read(pdrv, c.Addr(fsinfo_backup[0]), 1, 1)
diskio.write(pdrv, c.Addr(fsinfo_backup[0]), 7, 1)
fat_start: t.CUInt64T = t.CUInt64T(reserved)
res3: types.DRESULT = _init_fat(pdrv, fat_start, fat_sz, 2)
if res3 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
fat2_start: t.CUInt64T = fat_start + t.CUInt64T(fat_sz)
res4: types.DRESULT = _init_fat(pdrv, fat2_start, fat_sz, 2)
if res4 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
root_sector: t.CUInt64T = fat2_start + t.CUInt64T(fat_sz)
res5: types.DRESULT = _init_root_dir(pdrv, root_sector, sec_per_clus)
if res5 != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
return types.FRESULT.FR_OK
+147
View File
@@ -0,0 +1,147 @@
from stdint import *
import fat32_types as types
import fat32_diskio as diskio
import string
import t, c
MAX_VOLUMES: t.CDefine = 4
MAX_PARTITIONS: t.CDefine = 4
MBR_SIGNATURE: t.CDefine = 0xAA55
PART_TYPE_FAT32: t.CDefine = 0x0C
PART_TYPE_FAT32_LBA: t.CDefine = 0x0B
PART_TYPE_FAT16: t.CDefine = 0x06
PART_TYPE_FAT16_LBA: t.CDefine = 0x0E
class partition_entry:
boot_flag: t.CUInt8T
start_chs: t.CArray[t.CUInt8T, 3]
part_type: t.CUInt8T
end_chs: t.CArray[t.CUInt8T, 3]
start_lba: t.CUInt32T
total_sectors: t.CUInt32T
drive_pdrv: t.CArray[t.CUInt8T, MAX_VOLUMES]
drive_num_parts: t.CArray[t.CInt, MAX_VOLUMES]
drive_letter_map: t.CArray[t.CChar, MAX_VOLUMES]
volume_mounted: t.CArray[t.CInt, MAX_VOLUMES]
part_types: t.CArray[t.CUInt8T, MAX_VOLUMES * MAX_PARTITIONS]
part_start_lbas: t.CArray[t.CUInt64T, MAX_VOLUMES * MAX_PARTITIONS]
part_totals: t.CArray[t.CUInt64T, MAX_VOLUMES * MAX_PARTITIONS]
part_is_fat32: t.CArray[t.CInt, MAX_VOLUMES * MAX_PARTITIONS]
num_drives: t.CInt = 0
def _init():
if num_drives > 0: return
i: t.CInt
for i in range(MAX_VOLUMES):
drive_letter_map[i] = 0
volume_mounted[i] = 0
drive_pdrv[i] = 0
drive_num_parts[i] = 0
j: t.CInt
for j in range(MAX_VOLUMES * MAX_PARTITIONS):
part_types[j] = 0
part_start_lbas[j] = 0
part_totals[j] = 0
part_is_fat32[j] = 0
def _part_idx(drive: t.CInt, part: t.CInt) -> t.CInt:
return drive * MAX_PARTITIONS + part
def parse_mbr(pdrv: t.CUInt8T, drive_idx: t.CInt) -> types.FRESULT:
buf: t.CArray[t.CUInt8T, 512]
res: types.DRESULT = diskio.read(pdrv, c.Addr(buf[0]), 0, 1)
if res != types.DRESULT.RES_OK: return types.FRESULT.FR_DISK_ERR
if buf[0] == 0xEB or buf[0] == 0xE9:
return types.FRESULT.FR_NO_FILESYSTEM
sig: t.CUInt16T = t.CUInt16T(buf[510]) | (t.CUInt16T(buf[511]) << 8)
if sig != MBR_SIGNATURE: return types.FRESULT.FR_NO_FILESYSTEM
drive_pdrv[drive_idx] = pdrv
drive_num_parts[drive_idx] = 0
i: t.CInt
for i in range(4):
base: t.CUInt32T = 446 + t.CUInt32T(i * 16)
ptype: t.CUInt8T = buf[base + 4]
slba: t.CUInt32T = t.CUInt32T(buf[base + 8]) | (t.CUInt32T(buf[base + 9]) << 8) | (t.CUInt32T(buf[base + 10]) << 16) | (t.CUInt32T(buf[base + 11]) << 24)
tsec: t.CUInt32T = t.CUInt32T(buf[base + 12]) | (t.CUInt32T(buf[base + 13]) << 8) | (t.CUInt32T(buf[base + 14]) << 16) | (t.CUInt32T(buf[base + 15]) << 24)
if ptype != 0 and tsec > 0:
pi: t.CInt = drive_num_parts[drive_idx]
if pi < MAX_PARTITIONS:
pidx: t.CInt = _part_idx(drive_idx, pi)
part_types[pidx] = ptype
part_start_lbas[pidx] = t.CUInt64T(slba)
part_totals[pidx] = t.CUInt64T(tsec)
part_is_fat32[pidx] = 1 if (ptype == PART_TYPE_FAT32 or ptype == PART_TYPE_FAT32_LBA) else 0
drive_num_parts[drive_idx] = pi + 1
return types.FRESULT.FR_OK
def scan_drives() -> types.FRESULT:
_init()
global num_drives
num_drives = 0
pdrv: t.CUInt8T
for pdrv in range(MAX_VOLUMES):
res: types.DRESULT = diskio.init(pdrv)
if res != types.DRESULT.RES_OK: continue
res2: types.FRESULT = parse_mbr(pdrv, num_drives)
if res2 == types.FRESULT.FR_OK and drive_num_parts[num_drives] > 0:
drive_letter_map[num_drives] = t.CChar(ord('C') + num_drives)
else:
drive_pdrv[num_drives] = pdrv
drive_num_parts[num_drives] = 1
pidx: t.CInt = _part_idx(num_drives, 0)
part_types[pidx] = PART_TYPE_FAT32_LBA
part_start_lbas[pidx] = 0
part_totals[pidx] = 0
part_is_fat32[pidx] = 1
drive_letter_map[num_drives] = t.CChar(ord('C') + num_drives)
num_drives = num_drives + 1
if num_drives >= MAX_VOLUMES: break
return types.FRESULT.FR_OK
def get_drive(letter: t.CChar) -> t.CInt:
_init()
i: t.CInt
for i in range(num_drives):
if drive_letter_map[i] == letter or drive_letter_map[i] == (letter - 32) or drive_letter_map[i] == (letter + 32):
return i
return -1
def get_pdrv(letter: t.CChar) -> t.CUInt8T:
idx: t.CInt = get_drive(letter)
if idx < 0: return 0
return drive_pdrv[idx]
def get_partition_start(letter: t.CChar, part_idx: t.CInt) -> t.CUInt64T:
idx: t.CInt = get_drive(letter)
if idx < 0: return 0
if part_idx >= drive_num_parts[idx]: return 0
pidx: t.CInt = _part_idx(idx, part_idx)
return part_start_lbas[pidx]
def get_partition_count(letter: t.CChar) -> t.CInt:
idx: t.CInt = get_drive(letter)
if idx < 0: return 0
return drive_num_parts[idx]
def is_partition_fat32(letter: t.CChar, part_idx: t.CInt) -> t.CInt:
idx: t.CInt = get_drive(letter)
if idx < 0: return 0
if part_idx >= drive_num_parts[idx]: return 0
pidx: t.CInt = _part_idx(idx, part_idx)
return part_is_fat32[pidx]
def get_drive_count() -> t.CInt:
return num_drives
def get_drive_letter(idx: t.CInt) -> t.CChar:
if idx < 0 or idx >= num_drives: return 0
return drive_letter_map[idx]
def set_drive_letter(idx: t.CInt, letter: t.CChar) -> types.FRESULT:
if idx < 0 or idx >= num_drives: return types.FRESULT.FR_INVALID_PARAMETER
drive_letter_map[idx] = letter
return types.FRESULT.FR_OK
@@ -0,0 +1,64 @@
from stdint import *
import t, c
import asm
rtc_cmos_addr: t.CUInt16T = 0x70
rtc_cmos_data: t.CUInt16T = 0x71
def cmos_read(reg: t.CUInt8T) -> t.CUInt8T:
asm.outb(rtc_cmos_addr, reg)
return asm.inb(rtc_cmos_data)
def bcd_to_bin(bcd: t.CUInt8T) -> t.CUInt8T:
return (bcd >> 4) * 10 + (bcd & 0x0F)
def get_rtc_time() -> t.CUInt32T:
while cmos_read(0x0A) & 0x80: pass
sec: t.CUInt8T = cmos_read(0x00)
min_: t.CUInt8T = cmos_read(0x02)
hour: t.CUInt8T = cmos_read(0x04)
day: t.CUInt8T = cmos_read(0x07)
mon: t.CUInt8T = cmos_read(0x08)
year: t.CUInt8T = cmos_read(0x09)
reg_b: t.CUInt8T = cmos_read(0x0B)
if not (reg_b & 0x04):
sec = bcd_to_bin(sec)
min_ = bcd_to_bin(min_)
hour = bcd_to_bin(hour)
day = bcd_to_bin(day)
mon = bcd_to_bin(mon)
year = bcd_to_bin(year)
fat_year: t.CUInt16T = t.CUInt16T(year) + 2000 - 1980
if fat_year > 127: fat_year = 127
fat_date: t.CUInt16T = (fat_year << 9) | (t.CUInt16T(mon) << 5) | t.CUInt16T(day)
fat_time: t.CUInt16T = (t.CUInt16T(hour) << 11) | (t.CUInt16T(min_) << 5) | (t.CUInt16T(sec) / 2)
return (t.CUInt32T(fat_date) << 16) | t.CUInt32T(fat_time)
def get_fattime() -> t.CUInt32T:
return get_rtc_time()
def to_year(ft: t.CUInt32T) -> t.CUInt16T:
return t.CUInt16T((ft >> 25) & 0x7F) + 1980
def to_month(ft: t.CUInt32T) -> t.CUInt8T:
return t.CUInt8T((ft >> 21) & 0x0F)
def to_day(ft: t.CUInt32T) -> t.CUInt8T:
return t.CUInt8T((ft >> 16) & 0x1F)
def to_hour(ft: t.CUInt32T) -> t.CUInt8T:
return t.CUInt8T((ft >> 11) & 0x1F)
def to_minute(ft: t.CUInt32T) -> t.CUInt8T:
return t.CUInt8T((ft >> 5) & 0x3F)
def to_second(ft: t.CUInt32T) -> t.CUInt8T:
return t.CUInt8T((ft & 0x1F) * 2)
def make(year: t.CUInt16T, month: t.CUInt8T, day: t.CUInt8T, hour: t.CUInt8T, minute: t.CUInt8T, second: t.CUInt8T) -> t.CUInt32T:
fat_year: t.CUInt16T = year - 1980
if fat_year > 127: fat_year = 127
fat_date: t.CUInt16T = (fat_year << 9) | (t.CUInt16T(month) << 5) | t.CUInt16T(day)
fat_time: t.CUInt16T = (t.CUInt16T(hour) << 11) | (t.CUInt16T(minute) << 5) | (t.CUInt16T(second) / 2)
return (t.CUInt32T(fat_date) << 16) | t.CUInt32T(fat_time)
File diff suppressed because it is too large. Load diff
+3
View File
@@ -0,0 +1,3 @@
import fat32
@@ -0,0 +1,146 @@
import asm
import intr.idt as idt
import drivers.serial.uart.serial as serial
import viperstring as string
import t, c
KBD_DATA: t.CDefine = 0x60
KBD_STATUS: t.CDefine = 0x64
KBD_COMMAND: t.CDefine = 0x64
KBD_BUF_SIZE: t.CDefine = 1024
KEY_RELEASED: t.CDefine = 0x80
buffer: t.CArray[t.CUInt8T, KBD_BUF_SIZE]
buf_head: t.CStatic | t.CUInt32T = 0
buf_tail: t.CStatic | t.CUInt32T = 0
buf_count: t.CStatic | t.CUInt32T = 0
shift_l: t.CStatic | t.CUInt8T = 0
shift_r: t.CStatic | t.CUInt8T = 0
ctrl_l: t.CStatic | t.CUInt8T = 0
alt_l: t.CStatic | t.CUInt8T = 0
caps_lock: t.CStatic | t.CUInt8T = 0
scancode_set1: t.CArray[t.CChar, 128] = [
'\0', '\x1b', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
'\0', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '`', '\0',
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0',
'1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
scancode_set1_shift: t.CArray[t.CChar, 128] = [
'\0', '\x1b', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
'\0', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', '\0',
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '-', '8', '6', '2', '\0',
'1', '+', '4', '5', '3', '0', '.', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0'
]
def _wait_read():
timeout: t.CUInt32T = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x01) != 0: return
timeout -= 1
def _wait_write():
timeout: t.CUInt32T = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x02) == 0: return
timeout -= 1
def irq_handler() -> t.CInt:
global buf_head, buf_tail, buf_count
scancode: t.CUInt8T = asm.inb(KBD_DATA)
if buf_count < KBD_BUF_SIZE:
buffer[buf_tail] = scancode
buf_tail = buf_tail + 1
if buf_tail >= KBD_BUF_SIZE:
buf_tail = 0
buf_count += 1
return 0
def read_scancode() -> t.CUInt8T:
global buf_head, buf_count
asm.cli()
if buf_count == 0:
asm.sti()
return 0
code: t.CUInt8T = buffer[buf_head]
buf_head = buf_head + 1
if buf_head >= KBD_BUF_SIZE:
buf_head = 0
buf_count -= 1
asm.sti()
return code
def scancode_to_ascii(scancode: t.CUInt8T) -> t.CChar:
code: t.CUInt8T = scancode & ~t.CUInt8T(KEY_RELEASED)
if code >= 128: return '\0'
shifted: bool = (shift_l or shift_r) != 0
if caps_lock and code >= 0x10 and code <= 0x32:
shifted = not shifted
if shifted:
return scancode_set1_shift[code]
return scancode_set1[code]
def has_data() -> t.CInt:
return buf_count
def init():
drain: t.CUInt32T
drain = 0
while (asm.inb(KBD_STATUS) & 0x01) != 0:
asm.inb(KBD_DATA)
drain += 1
if drain > 1000: break
_wait_write()
asm.outb(KBD_COMMAND, 0xAD)
drain = 0
while (asm.inb(KBD_STATUS) & 0x01) != 0:
asm.inb(KBD_DATA)
drain += 1
if drain > 1000: break
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
ccb: t.CUInt8T = asm.inb(KBD_DATA)
ccb = ccb | 0x01
ccb = ccb | 0x40
ccb = ccb & 0xEF
_wait_write()
asm.outb(KBD_COMMAND, 0x60)
_wait_write()
asm.outb(KBD_DATA, ccb)
_wait_write()
asm.outb(KBD_COMMAND, 0xAE)
drain = 0
while (asm.inb(KBD_STATUS) & 0x01) != 0:
asm.inb(KBD_DATA)
drain += 1
if drain > 1000: break
_wait_write()
asm.outb(KBD_DATA, 0xF4)
_wait_read()
asm.inb(KBD_DATA)
idt.irqInstallHandler(1, irq_handler, "keyboard")
+134
View File
@@ -0,0 +1,134 @@
import asm
import intr.idt as idt
import platform.pch.pic as pic
import drivers.serial.uart.serial as serial
import t, c
MOUSE_DATA: t.CDefine = 0x60
MOUSE_STATUS: t.CDefine = 0x64
MOUSE_COMMAND: t.CDefine = 0x64
MOUSE_BUF_SIZE: t.CDefine = 1024
MOUSE_LEFT: t.CDefine = 0x01
MOUSE_RIGHT: t.CDefine = 0x02
MOUSE_MIDDLE: t.CDefine = 0x04
class mouse_packet:
flags: t.CUInt8T
dx: t.CInt8T
dy: t.CInt8T
buttons: t.CUInt8T
packets: t.CArray[mouse_packet, MOUSE_BUF_SIZE]
pack_head: t.CStatic | t.CUInt32T = 0
pack_tail: t.CStatic | t.CUInt32T = 0
pack_count: t.CStatic | t.CUInt32T = 0
cycle: t.CStatic | t.CUInt8T = 0
byte1: t.CStatic | t.CUInt8T = 0
byte2: t.CStatic | t.CInt8T = 0
byte3: t.CStatic | t.CInt8T = 0
mouse_x: t.CStatic | t.CInt32T = 0
mouse_y: t.CStatic | t.CInt32T = 0
mouse_buttons: t.CStatic | t.CUInt8T = 0
def _wait_read():
timeout: t.CUInt32T = 100000
while timeout > 0:
if (asm.inb(MOUSE_STATUS) & 0x01) != 0: return
timeout -= 1
def _wait_write():
timeout: t.CUInt32T = 100000
while timeout > 0:
if (asm.inb(MOUSE_STATUS) & 0x02) == 0: return
timeout -= 1
def _write_cmd(cmd: t.CUInt8T):
_wait_write()
asm.outb(MOUSE_COMMAND, 0xD4)
_wait_write()
asm.outb(MOUSE_DATA, cmd)
_wait_read()
asm.inb(MOUSE_DATA)
def irq_handler() -> t.CInt:
global cycle, byte1, byte2, byte3, pack_tail, pack_count
status: t.CUInt8T = asm.inb(MOUSE_STATUS)
if (status & 0x20) == 0: return 0
data: t.CUInt8T = asm.inb(MOUSE_DATA)
if cycle == 0:
if (data & 0x08) != 0:
byte1 = data
cycle = 1
return 0
if cycle == 1:
byte2 = data
cycle = 2
return 0
byte3 = data
cycle = 0
if pack_count < MOUSE_BUF_SIZE:
packets[pack_tail].flags = byte1
packets[pack_tail].dx = byte2
packets[pack_tail].dy = byte3
packets[pack_tail].buttons = byte1 & t.CUInt8T(0x07)
pack_tail = pack_tail + 1
if pack_tail >= MOUSE_BUF_SIZE:
pack_tail = 0
pack_count += 1
return 0
def read_packet(out: mouse_packet | t.CPtr) -> t.CInt:
global pack_head, pack_count
asm.cli()
if pack_count == 0:
asm.sti()
out.flags = 0
out.dx = 0
out.dy = 0
out.buttons = 0
return -1
out.flags = packets[pack_head].flags
out.dx = packets[pack_head].dx
out.dy = packets[pack_head].dy
out.buttons = packets[pack_head].buttons
pack_head = pack_head + 1
if pack_head >= MOUSE_BUF_SIZE:
pack_head = 0
pack_count -= 1
asm.sti()
return 0
def has_data() -> t.CInt:
return pack_count
def get_x() -> t.CInt32T:
return mouse_x
def get_y() -> t.CInt32T:
return mouse_y
def get_buttons() -> t.CUInt8T:
return mouse_buttons
def init():
_wait_write()
asm.outb(MOUSE_COMMAND, 0xA8)
_wait_write()
asm.outb(MOUSE_COMMAND, 0x20)
_wait_read()
status: t.CUInt8T = asm.inb(MOUSE_DATA)
status = status | 0x02
status = status & 0xDF
_wait_write()
asm.outb(MOUSE_COMMAND, 0x60)
_wait_write()
asm.outb(MOUSE_DATA, status)
_write_cmd(0xF6)
_write_cmd(0xF4)
pic.clearMask(12)
idt.irqInstallHandler(12, irq_handler, "mouse")
+46
View File
@@ -0,0 +1,46 @@
import platform.pch.pic as pic
import su8250
import t, c
# 串口初始化
def init():
# 初始化COM1端口
su8250.init(su8250.COM1_PORT)
# 启用COM1中断(IRQ4)
pic.clearMask(4)
# 发送一个字符到COM1
def putc(cr: t.CChar):
su8250.putchar(su8250.COM1_PORT, cr)
def putchar(cr: t.CChar):
su8250.putchar(su8250.COM1_PORT, cr)
# 从COM1读取一个字符
def getchar() -> t.CChar:
return su8250.getchar(su8250.COM1_PORT)
# 发送一个字符串到COM1
def puts(s: str):
# 逐个字符发送,直接复用putchar的逻辑
for i in s:
putchar(i)
# 检查COM1是否有数据可读
def isDataAvailable() -> t.CInt:
return su8250.isDataAvailable(su8250.COM1_PORT)
# 检查COM1发送缓冲区是否为空
def isTransmitEmpty() -> t.CInt:
return su8250.isTransmitEmpty(su8250.COM1_PORT)
# 发送32位十六进制值到COM1
def put_hex32(value: t.CUInt32T):
hex_digits: t.CArray[t.CChar, None] = ["0123456789ABCDEF"]
buffer: t.CArray[t.CChar, 9]
for i in range(7, -1, -1):
buffer[i] = hex_digits[value & 0xF]
value >>= 4
buffer[8] = '\0'
puts(buffer)
+116
View File
@@ -0,0 +1,116 @@
import platform.pch as pch
import asm
import t, c
# 串口端口定义
COM1_PORT: t.CDefine = 0x3F8 # COM1端口地址
COM2_PORT: t.CDefine = 0x2F8 # COM2端口地址
# 8250/16550 UART寄存器偏移
UART_RBR: t.CDefine = 0 # 接收缓冲区寄存器(读)
UART_THR: t.CDefine = 0 # 发送保持寄存器(写)
UART_DLL: t.CDefine = 0 # 除数锁存低位(写,DLAB=1)
UART_IER: t.CDefine = 1 # 中断使能寄存器
UART_DLM: t.CDefine = 1 # 除数锁存高位(写,DLAB=1)
UART_FCR: t.CDefine = 2 # FIFO控制寄存器
UART_IIR: t.CDefine = 2 # 中断识别寄存器(读)
UART_LCR: t.CDefine = 3 # 线路控制寄存器
UART_MCR: t.CDefine = 4 # 调制解调器控制寄存器
UART_LSR: t.CDefine = 5 # 线路状态寄存器
UART_MSR: t.CDefine = 6 # 调制解调器状态寄存器
UART_SCR: t.CDefine = 7 # scratch register
UART_LSR_THRE: t.CDefine = 0x20 # THRE:发送保持寄存器空
UART_LSR_TEMT: t.CDefine = 0x40 # TEMT:发送移位寄存器空(真正的发送完成)
# 线路控制寄存器位定义
UART_LCR_DLAB: t.CDefine = 0x80 # 除数锁存访问位
UART_LCR_8BIT: t.CDefine = 0x03 # 8位数据位
# 线路状态寄存器位定义
UART_LSR_DR: t.CDefine = 0x01 # 数据就绪
UART_LSR_THRE: t.CDefine = 0x20 # 发送保持寄存器空
# 波特率除数(115200 bps)
BAUD_115200: t.CDefine = 1
# 初始化UART 8250/16550
def init(port: t.CUInt16T):
# 1. 禁用中断
asm.outb(port + UART_IER, 0x00)
delay(1000)
# 2. 设置波特率除数(115200 bps)
asm.outb(port + UART_LCR, UART_LCR_DLAB) # 设置DLAB位
delay(1000)
asm.outb(port + UART_DLL, 0x01) # 除数低位 (115200 bps)
delay(1000)
asm.outb(port + UART_DLM, 0x00) # 除数高位
delay(1000)
# 3. 设置线路控制:8位数据位,1位停止位,无校验
asm.outb(port + UART_LCR, UART_LCR_8BIT)
delay(1000)
# 4. 初始化FIFO
asm.outb(port + UART_FCR, 0x07) # 启用FIFO,清除接收和发送FIFO
delay(1000)
# 5. 设置调制解调器控制
asm.outb(port + UART_MCR, 0x03) # 启用DTR、RTS
delay(1000)
# 6. 清除任何待处理的中断
asm.inb(port + UART_IIR)
delay(1000)
asm.inb(port + UART_RBR)
delay(1000)
# 7. 等待一段时间让设备稳定
delay(10000)
# 延迟函数
def delay(count: t.CInt):
for i in range(count): c.Asm("nop")
# 检查发送缓冲区是否为空
def isTransmitEmpty(port: t.CUInt16T) -> t.CInt:
return asm.inb(port + UART_LSR) & UART_LSR_THRE
def putchar(port: t.CUInt16T, cr: t.CChar):
# 1. 处理换行符:串口终端需要\r\n才会正确换行,只发\n会错位
if cr == '\n':
# 直接发送\r,避免递归调用
timeout: t.CUInt32T = 1000000
while ((asm.inb(port + UART_LSR) & UART_LSR_THRE) == 0) and timeout:
c.Asm("nop")
timeout -= 1
if timeout == 0: return
asm.outb(port + UART_THR, '\r')
timeout = 1000000
while ((asm.inb(port + UART_LSR) & UART_LSR_TEMT) == 0) and timeout:
c.Asm("nop")
timeout -= 1
# 2. 等待发送缓冲区为空
timeout: t.CUInt32T = 1000000
while ((asm.inb(port + UART_LSR) & UART_LSR_THRE) == 0) and timeout:
c.Asm("nop")
timeout -= 1
if timeout == 0: return
# 3. 发送字符
asm.outb(port + UART_THR, cr)
# 4. 等待发送完成
timeout = 1000000
while ((asm.inb(port + UART_LSR) & UART_LSR_TEMT) == 0) and timeout:
c.Asm("nop")
timeout -= 1
# 检查是否有数据可读
def isDataAvailable(port: t.CUInt16T) -> t.CInt:
return asm.inb(port + UART_LSR) & UART_LSR_DR
# 读取一个字符
def getchar(port: t.CUInt16T) -> t.CChar:
# 等待数据可用
while not isDataAvailable(port): pass
# 读取字符
return asm.inb(port + UART_RBR)
+161
View File
@@ -0,0 +1,161 @@
import asm
import t, c
import serial
import viperlib
import pci
class IDError(t.CEnum):
IDE_ERR_NONE: t.State = 0
IDE_ERR_TIMEOUT: t.State
IDE_ERR_DEVICE_ERROR: t.State
IDE_ERR_DEVICE_BUSY: t.State
IDE_ERR_INVALID_PARAM: t.State
IDE_ERR_BUFFER_NULL: t.State
IDE_ERR_SECTOR_COUNT: t.State
IDE_ERR_DEVICE_NOT_READY: t.State
IDE_ERR_DEVICE_FAILURE: t.State
IDE_ERR_COMMAND_FAILED: t.State
IDE_ERR_INVALID_LBA: t.State
IDE_baseport_PRIMARY: t.CDefine = 0x1F0
IDE_baseport_SECONDARY: t.CDefine = 0x170
IDE_STATUS_BSY: t.CDefine = 0x80
IDE_STATUS_DRDY: t.CDefine = 0x40
IDE_STATUS_DF: t.CDefine = 0x20
IDE_STATUS_DRQ: t.CDefine = 0x08
IDE_STATUS_ERR: t.CDefine = 0x01
SECTOR_SIZE: t.CDefine = 512
_ide_initialized: t.CInt = 0
def waitReady(baseport: t.CUInt16T) -> t.CStatic | IDError:
timeout: t.CInt = 100000
while timeout > 0:
status: t.CUInt8T = asm.inb(baseport + 7)
if status & IDE_STATUS_DF:
return IDError.IDE_ERR_DEVICE_FAILURE
if not status & IDE_STATUS_BSY and status & IDE_STATUS_DRDY:
return IDError.IDE_ERR_NONE
timeout -= 1
return IDError.IDE_ERR_TIMEOUT
def waitDataReady(baseport: t.CUInt16T) -> t.CStatic | IDError:
timeout: t.CInt = 100000
while timeout > 0:
status: t.CUInt8T = asm.inb(baseport + 7)
if status & IDE_STATUS_ERR: return IDError.IDE_ERR_DEVICE_ERROR
if status & IDE_STATUS_DF: return IDError.IDE_ERR_DEVICE_FAILURE
if not status & IDE_STATUS_BSY and (status & IDE_STATUS_DRQ or status & IDE_STATUS_DRDY):
return IDError.IDE_ERR_NONE
timeout -= 1
return IDError.IDE_ERR_TIMEOUT
def init() -> t.CInt:
if _ide_initialized: return IDError.IDE_ERR_NONE
dbg: t.CArray[t.CChar, 120]
bus: t.CUInt8T
dev_n: t.CUInt8T
func: t.CUInt8T
for bus in range(2):
for dev_n in range(32):
for func in range(8):
vendor: t.CUInt16T = pci.pci_read16(bus, dev_n, func, 0x00)
if vendor == 0xFFFF: continue
cls: t.CUInt32T = pci.pci_get_class(bus, dev_n, func)
if cls == 0x010180 or cls == 0x0101:
cmd: t.CUInt16T = pci.pci_read16(bus, dev_n, func, 0x04)
pci.pci_write32(bus, dev_n, func, 0x04, t.CUInt32T(cmd | 0x0007))
viperlib.snprintf(c.Addr(dbg), 120, "[ide] found at %d:%d:%d cmd=0x%04x\n", t.CInt(bus), t.CInt(dev_n), t.CInt(func), t.CUInt32T(pci.pci_read16(bus, dev_n, func, 0x04)))
serial.puts(dbg)
asm.outb(0x3F6, 0x04)
i: t.CInt
for i in range(10000):
asm.nop()
asm.outb(0x3F6, 0x00)
for i in range(10000):
asm.nop()
status: t.CUInt8T = asm.inb(0x1F7)
viperlib.snprintf(c.Addr(dbg), 120, "[ide] status=0x%02x\n", t.CUInt32T(status))
serial.puts(dbg)
_ide_initialized = 1
return IDError.IDE_ERR_NONE
def readSector(lba: t.CUInt32T, buffer: t.CVoid | t.CPtr) -> t.CInt:
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
dbg: t.CArray[t.CChar, 120]
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE:
viperlib.snprintf(c.Addr(dbg), 120, "[readSector] waitReady err=%d\n", t.CInt(err))
serial.puts(dbg)
return err
asm.outb(0x3F6, 0x02)
asm.outb(0x1F2, 1)
asm.outb(0x1F3, t.CUInt8T(lba & 0xFF))
asm.outb(0x1F4, t.CUInt8T((lba >> 8) & 0xFF))
asm.outb(0x1F5, t.CUInt8T((lba >> 16) & 0xFF))
asm.outb(0x1F6, t.CUInt8T(0xE0 | ((lba >> 24) & 0x0F)))
asm.outb(0x1F7, 0x20)
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE:
st: t.CUInt8T = asm.inb(0x1F7)
viperlib.snprintf(c.Addr(dbg), 120, "[readSector] waitDataReady err=%d status=0x%02x\n", t.CInt(err), t.CUInt32T(st))
serial.puts(dbg)
return err
st2: t.CUInt8T = asm.inb(0x1F7)
buf: t.CUInt16T | t.CPtr = t.CUInt16T(buffer, t.CPtr)
for j in range(256):
c.Set(c.Deref(buf), asm.inw(0x1F0))
buf += 1
first_byte: t.CUInt8T = c.Deref(t.CUInt8T(buffer, t.CPtr))
# viperlib.snprintf(c.Addr(dbg), 120, "[readSector] lba=%lu status=0x%02x first=%02x\n", lba, t.CUInt32T(st2), t.CUInt32T(first_byte))
# serial.puts(dbg)
return IDError.IDE_ERR_NONE
def writeSector(lba: t.CUInt32T, buffer: t.CConst | t.CVoid | t.CPtr) -> t.CInt:
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return err
asm.outb(0x3F6, 0x02)
asm.outb(0x1F2, 1)
asm.outb(0x1F3, t.CUInt8T(lba & 0xFF))
asm.outb(0x1F4, t.CUInt8T((lba >> 8) & 0xFF))
asm.outb(0x1F5, t.CUInt8T((lba >> 16) & 0xFF))
asm.outb(0x1F6, t.CUInt8T(0xE0 | ((lba >> 24) & 0x0F)))
asm.outb(0x1F7, 0x30)
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return err
buf: t.CUInt16T | t.CPtr = t.CUInt16T(buffer, t.CPtr)
for j in range(256):
val: t.CUInt16T = c.Deref(buf)
asm.outw(0x1F0, val)
buf += 1
asm.outb(0x1F7, 0xE7)
waitReady(IDE_baseport_PRIMARY)
return IDError.IDE_ERR_NONE
def getDiskSize() -> t.CUInt64T:
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return 0
asm.outb(0x1F6, 0xE0)
asm.outb(0x1F7, 0xEC)
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return 0
status: t.CUInt8T = asm.inb(0x1F7)
if not status & IDE_STATUS_DRDY: return 0
if status & IDE_STATUS_ERR: return 0
identify_data: t.CArray[t.CUInt16T, 256]
for i in range(256):
identify_data[i] = asm.inw(0x1F0)
totalSectors: t.CUInt64T = identify_data[60] | t.CUInt64T(identify_data[61]) << 16
if identify_data[83] & (1 << 10):
lba48_sectors: t.CUInt64T = (
t.CUInt64T(identify_data[100]) |
t.CUInt64T(identify_data[101]) << 16 |
t.CUInt64T(identify_data[102]) << 32 |
t.CUInt64T(identify_data[103]) << 48)
if lba48_sectors > totalSectors:
totalSectors = lba48_sectors
return totalSectors
+37
View File
@@ -0,0 +1,37 @@
import t, c
import drivers.usb.usb as usb
HID_BOOT_PROTOCOL: t.CDefine = 0
HID_REPORT_PROTOCOL: t.CDefine = 1
HID_KBD_REPORT_SIZE: t.CDefine = 8
HID_MOUSE_REPORT_SIZE: t.CDefine = 4
class hid_kbd_report(t.CStruct):
modifier: t.CUInt8T
reserved: t.CUInt8T
keycode: t.CArray[t.CUInt8T, 6]
class hid_mouse_report(t.CStruct):
buttons: t.CUInt8T
dx: t.CInt8T
dy: t.CInt8T
wheel: t.CInt8T
def init_keyboard(dev: usb.usb_device | t.CPtr) -> t.CInt:
if dev.iface_class != 0x03: return -1
if dev.iface_protocol != 0x01: return -2
if dev.ep_in == 0: return -3
return 0
def init_mouse(dev: usb.usb_device | t.CPtr) -> t.CInt:
if dev.iface_class != 0x03: return -1
if dev.iface_protocol != 0x02: return -2
if dev.ep_in == 0: return -3
return 0
def read_keyboard(dev: usb.usb_device | t.CPtr, report: hid_kbd_report | t.CPtr) -> t.CInt:
return usb.read_interrupt(dev, c.Addr(report))
def read_mouse(dev: usb.usb_device | t.CPtr, report: hid_mouse_report | t.CPtr) -> t.CInt:
return usb.read_interrupt(dev, c.Addr(report))
+111
View File
@@ -0,0 +1,111 @@
import t, c
import drivers.usb.usb as usb
import drivers.usb.hid.hid as hid
import drivers.usb.uhci as uhci
import drivers.serial.uart.serial as serial
KBD_BUF_SIZE: t.CDefine = 1024
KBD_EVT_PRESS: t.CDefine = 0
KBD_EVT_RELEASE: t.CDefine = 1
class usb_kbd_event(t.CStruct):
modifier: t.CUInt8T
keycode: t.CUInt8T
event_type: t.CUInt8T
kbd_buf: t.CArray[usb_kbd_event, KBD_BUF_SIZE]
kbd_buf_head: t.CInt = 0
kbd_buf_tail: t.CInt = 0
kbd_buf_count: t.CInt = 0
kbd_dev: usb.usb_device | t.CPtr = None
kbd_report: hid.hid_kbd_report
kbd_prev_keycodes: t.CArray[t.CUInt8T, 6]
kbd_initialized: t.CInt = 0
kbd_slot_id: t.CInt = -1
def init() -> t.CInt:
global kbd_dev, kbd_initialized, kbd_slot_id
dev: usb.usb_device | t.CPtr = usb.find_hid_device(0x01)
if dev is None:
serial.puts("[usb_kbd] no USB keyboard found\n")
return -1
kbd_dev = dev
if hid.init_keyboard(dev) != 0:
serial.puts("[usb_kbd] init failed\n")
return -2
kbd_initialized = 1
serial.puts("[usb_kbd] initialized\n")
kbd_slot_id = uhci.register_int_callback(c.Addr(_on_int_complete))
if kbd_slot_id < 0:
serial.puts("[usb_kbd] register callback failed\n")
kbd_initialized = 0
return -3
uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(kbd_report))
return 0
def _on_int_complete() -> t.CInt:
global kbd_buf_head, kbd_buf_tail, kbd_buf_count, kbd_prev_keycodes
if kbd_initialized == 0: return 0
i: t.CInt
j: t.CInt
for i in range(6):
code: t.CUInt8T = kbd_report.keycode[i]
if code == 0: continue
found: t.CInt = 0
for j in range(6):
if code == kbd_prev_keycodes[j]:
found = 1
break
if found == 0:
if kbd_buf_count < KBD_BUF_SIZE:
kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier
kbd_buf[kbd_buf_tail].keycode = code
kbd_buf[kbd_buf_tail].event_type = KBD_EVT_PRESS
kbd_buf_tail = kbd_buf_tail + 1
if kbd_buf_tail >= KBD_BUF_SIZE:
kbd_buf_tail = 0
kbd_buf_count += 1
for i in range(6):
prev: t.CUInt8T = kbd_prev_keycodes[i]
if prev == 0: continue
found2: t.CInt = 0
for j in range(6):
if prev == kbd_report.keycode[j]:
found2 = 1
break
if found2 == 0:
if kbd_buf_count < KBD_BUF_SIZE:
kbd_buf[kbd_buf_tail].modifier = kbd_report.modifier
kbd_buf[kbd_buf_tail].keycode = prev
kbd_buf[kbd_buf_tail].event_type = KBD_EVT_RELEASE
kbd_buf_tail = kbd_buf_tail + 1
if kbd_buf_tail >= KBD_BUF_SIZE:
kbd_buf_tail = 0
kbd_buf_count += 1
for i in range(6):
kbd_prev_keycodes[i] = kbd_report.keycode[i]
dev: usb.usb_device | t.CPtr = kbd_dev
if dev is not None and kbd_slot_id >= 0:
uhci.schedule_int_transfer(kbd_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(kbd_report))
return 0
def has_data() -> t.CInt:
if kbd_buf_count > 0: return 1
return 0
def poll() -> t.CInt:
if kbd_initialized == 0: return 0
if kbd_buf_count > 0: return 1
return 0
def read_event(out: usb_kbd_event | t.CPtr) -> t.CInt:
global kbd_buf_head, kbd_buf_count
if kbd_buf_count == 0: return -1
out.modifier = kbd_buf[kbd_buf_head].modifier
out.keycode = kbd_buf[kbd_buf_head].keycode
out.event_type = kbd_buf[kbd_buf_head].event_type
kbd_buf_head = kbd_buf_head + 1
if kbd_buf_head >= KBD_BUF_SIZE:
kbd_buf_head = 0
kbd_buf_count -= 1
return 0
+77
View File
@@ -0,0 +1,77 @@
import t, c
import drivers.usb.usb as usb
import drivers.usb.hid.hid as hid
import drivers.usb.uhci as uhci
import drivers.serial.uart.serial as serial
MOUSE_BUF_SIZE: t.CDefine = 1024
class usb_mouse_packet(t.CStruct):
buttons: t.CUInt8T
dx: t.CInt16T
dy: t.CInt16T
wheel: t.CInt8T
mse_buf: t.CArray[usb_mouse_packet, MOUSE_BUF_SIZE]
mse_buf_head: t.CInt = 0
mse_buf_tail: t.CInt = 0
mse_buf_count: t.CInt = 0
mse_dev: usb.usb_device | t.CPtr = None
mse_report: hid.hid_mouse_report
mse_initialized: t.CInt = 0
mse_slot_id: t.CInt = -1
def init() -> t.CInt:
global mse_dev, mse_initialized, mse_slot_id
dev: usb.usb_device | t.CPtr = usb.find_hid_device(0x02)
if dev is None:
serial.puts("[usb_mse] no USB mouse found\n")
return -1
mse_dev = dev
if hid.init_mouse(dev) != 0:
serial.puts("[usb_mse] init failed\n")
return -2
mse_initialized = 1
serial.puts("[usb_mse] initialized\n")
mse_slot_id = uhci.register_int_callback(c.Addr(_on_int_complete))
if mse_slot_id < 0:
serial.puts("[usb_mse] register callback failed\n")
mse_initialized = 0
return -3
uhci.schedule_int_transfer(mse_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(mse_report))
return 0
def _on_int_complete() -> t.CInt:
global mse_buf_head, mse_buf_tail, mse_buf_count
if mse_initialized == 0: return 0
if mse_report.dx != 0 or mse_report.dy != 0 or mse_report.buttons != 0:
if mse_buf_count < MOUSE_BUF_SIZE:
mse_buf[mse_buf_tail].buttons = mse_report.buttons
mse_buf[mse_buf_tail].dx = t.CInt16T(mse_report.dx)
mse_buf[mse_buf_tail].dy = t.CInt16T(mse_report.dy)
mse_buf[mse_buf_tail].wheel = mse_report.wheel
mse_buf_tail = mse_buf_tail + 1
if mse_buf_tail >= MOUSE_BUF_SIZE:
mse_buf_tail = 0
mse_buf_count += 1
dev: usb.usb_device | t.CPtr = mse_dev
if dev is not None and mse_slot_id >= 0:
uhci.schedule_int_transfer(mse_slot_id, dev.addr, dev.ep_in, dev.ls, t.CUInt8T(dev.ep_in_max), dev.ep_in_toggle, c.Addr(mse_report))
return 0
def has_data() -> t.CInt:
if mse_buf_count > 0: return 1
return 0
def read_packet(out: usb_mouse_packet | t.CPtr) -> t.CInt:
global mse_buf_head, mse_buf_count
if mse_buf_count == 0: return -1
out.buttons = mse_buf[mse_buf_head].buttons
out.dx = mse_buf[mse_buf_head].dx
out.dy = mse_buf[mse_buf_head].dy
out.wheel = mse_buf[mse_buf_head].wheel
mse_buf_head = mse_buf_head + 1
if mse_buf_head >= MOUSE_BUF_SIZE:
mse_buf_head = 0
mse_buf_count -= 1
return 0
+93
View File
@@ -0,0 +1,93 @@
import t, c
import asm
PCI_CONFIG_ADDR: t.CDefine = 0xCF8
PCI_CONFIG_DATA: t.CDefine = 0xCFC
PCI_CLASS_SERIAL_USB: t.CDefine = 0x0C03
PCI_CLASS_SERIAL_UHCI: t.CDefine = 0x0C0300
PCI_CLASS_SERIAL_OHCI: t.CDefine = 0x0C0310
PCI_CLASS_SERIAL_EHCI: t.CDefine = 0x0C0320
PCI_CLASS_SERIAL_XHCI: t.CDefine = 0x0C0330
PCI_CMD_IO_SPACE: t.CDefine = 0x0001
PCI_CMD_BUS_MASTER: t.CDefine = 0x0004
class pci_device(t.CStruct):
bus: t.CUInt8T
dev: t.CUInt8T
func: t.CUInt8T
vendor_id: t.CUInt16T
device_id: t.CUInt16T
class_code: t.CUInt32T
irq: t.CUInt8T
bar: t.CArray[t.CUInt32T, 6]
def pci_read32(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt32T:
addr: t.CUInt32T = 0x80000000 | (t.CUInt32T(bus) << 16) | (t.CUInt32T(dev) << 11) | (t.CUInt32T(func) << 8) | (t.CUInt32T(offset) & 0xFC)
asm.outl(PCI_CONFIG_ADDR, addr)
return asm.inl(PCI_CONFIG_DATA)
def pci_write32(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T, value: t.CUInt32T):
addr: t.CUInt32T = 0x80000000 | (t.CUInt32T(bus) << 16) | (t.CUInt32T(dev) << 11) | (t.CUInt32T(func) << 8) | (t.CUInt32T(offset) & 0xFC)
asm.outl(PCI_CONFIG_ADDR, addr)
asm.outl(PCI_CONFIG_DATA, value)
def pci_read16(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt16T:
val: t.CUInt32T = pci_read32(bus, dev, func, offset)
shift: t.CUInt32T = (t.CUInt32T(offset) & 0x02) * 8
return t.CUInt16T((val >> shift) & 0xFFFF)
def pci_read8(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, offset: t.CUInt8T) -> t.CUInt8T:
val: t.CUInt32T = pci_read32(bus, dev, func, offset)
shift: t.CUInt32T = (t.CUInt32T(offset) & 0x03) * 8
return t.CUInt8T((val >> shift) & 0xFF)
def pci_get_class(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T) -> t.CUInt32T:
cls: t.CUInt8T = pci_read8(bus, dev, func, 0x0B)
sub: t.CUInt8T = pci_read8(bus, dev, func, 0x0A)
proto: t.CUInt8T = pci_read8(bus, dev, func, 0x09)
return (t.CUInt32T(cls) << 16) | (t.CUInt32T(sub) << 8) | t.CUInt32T(proto)
def pci_get_bar(bus: t.CUInt8T, dev: t.CUInt8T, func: t.CUInt8T, bar_idx: t.CUInt8T) -> t.CUInt32T:
return pci_read32(bus, dev, func, 0x10 + bar_idx * 4)
def pci_enable_device(dev: pci_device | t.CPtr):
cmd: t.CUInt16T = pci_read16(dev.bus, dev.dev, dev.func, 0x04)
cmd = cmd | PCI_CMD_IO_SPACE | PCI_CMD_BUS_MASTER
pci_write32(dev.bus, dev.dev, dev.func, 0x04, t.CUInt32T(cmd))
def pci_fill_device(bus: t.CUInt8T, dev_n: t.CUInt8T, func: t.CUInt8T, out: pci_device | t.CPtr):
out.bus = bus
out.dev = dev_n
out.func = func
out.vendor_id = pci_read16(bus, dev_n, func, 0x00)
out.device_id = pci_read16(bus, dev_n, func, 0x02)
out.class_code = pci_get_class(bus, dev_n, func)
out.irq = pci_read8(bus, dev_n, func, 0x3C)
i: t.CInt
for i in range(6):
out.bar[i] = pci_get_bar(bus, dev_n, func, t.CUInt8T(i))
_uhci_dev: pci_device
_uhci_found: t.CInt = 0
def find_uhci() -> t.CUInt16T:
global _uhci_dev, _uhci_found
bus: t.CUInt8T
dev_n: t.CUInt8T
func: t.CUInt8T
for bus in range(1):
for dev_n in range(32):
for func in range(8):
vendor: t.CUInt16T = pci_read16(bus, dev_n, func, 0x00)
if vendor == 0xFFFF: continue
cls: t.CUInt32T = pci_get_class(bus, dev_n, func)
if cls == PCI_CLASS_SERIAL_UHCI:
pci_fill_device(bus, dev_n, func, c.Addr(_uhci_dev))
pci_enable_device(c.Addr(_uhci_dev))
_uhci_found = 1
bar: t.CUInt32T = _uhci_dev.bar[4]
io_base: t.CUInt16T = t.CUInt16T(bar & 0xFFFE)
return io_base
return 0
+415
View File
@@ -0,0 +1,415 @@
import t, c
import asm
import mm.mm as mm
import viperstring as string
import drivers.serial.uart.serial as serial
import platform.pch.timer as timer
import intr.idt as idt
import platform.pch.pic as pic
import drivers.usb.pci as pci
UHCI_USBCMD: t.CDefine = 0x00
UHCI_USBSTS: t.CDefine = 0x02
UHCI_USBINTR: t.CDefine = 0x04
UHCI_FRNUM: t.CDefine = 0x06
UHCI_FLBASEADD: t.CDefine = 0x08
UHCI_SOFMOD: t.CDefine = 0x0C
UHCI_PORTSC1: t.CDefine = 0x10
UHCI_PORTSC2: t.CDefine = 0x12
UHCI_CMD_RUN: t.CDefine = 0x0001
UHCI_CMD_HCRESET: t.CDefine = 0x0002
UHCI_CMD_GRESET: t.CDefine = 0x0004
UHCI_CMD_MAXPKT: t.CDefine = 0x0080
UHCI_CMD_CF: t.CDefine = 0x0040
UHCI_STS_HCHALTED: t.CDefine = 0x0020
UHCI_STS_USBINT: t.CDefine = 0x0001
UHCI_STS_USBERRINT: t.CDefine = 0x0002
UHCI_STS_RESUME: t.CDefine = 0x0004
UHCI_INTR_TIMEOUT: t.CDefine = 0x0001
UHCI_INTR_RESUME: t.CDefine = 0x0002
UHCI_INTR_IOC: t.CDefine = 0x0004
UHCI_INTR_SHORT: t.CDefine = 0x0008
UHCI_PORT_CONNECT: t.CDefine = 0x0001
UHCI_PORT_CONNECT_CHANGE: t.CDefine = 0x0002
UHCI_PORT_ENABLE: t.CDefine = 0x0004
UHCI_PORT_ENABLE_CHANGE: t.CDefine = 0x0008
UHCI_PORT_RESET: t.CDefine = 0x0200
UHCI_PORT_LOW_SPEED: t.CDefine = 0x0100
TD_CTRL_ACTIVE: t.CDefine = 0x00800000
TD_CTRL_IOC: t.CDefine = 0x01000000
TD_CTRL_LS: t.CDefine = 0x04000000
TD_CTRL_SPD: t.CDefine = 0x20000000
TD_CTRL_CERR_SHIFT: t.CDefine = 27
TD_CTRL_CERR_MASK: t.CDefine = 0x18000000
TD_TOKEN_SETUP: t.CDefine = 0x2D
TD_TOKEN_IN: t.CDefine = 0x69
TD_TOKEN_OUT: t.CDefine = 0xE1
TD_TOKEN_DATA0: t.CDefine = 0
TD_TOKEN_DATA1: t.CDefine = 1
TD_LINK_TERMINATE: t.CDefine = 0x00000001
TD_LINK_QH: t.CDefine = 0x00000002
TD_LINK_DEPTH_FIRST: t.CDefine = 0x00000004
UHCI_NUM_FRAMES: t.CDefine = 1024
UHCI_INT_SLOTS: t.CDefine = 4
class uhci_td(t.CStruct):
link: t.CUInt32T
ctrl_status: t.CUInt32T
token: t.CUInt32T
buffer: t.CUInt32T
class uhci_qh(t.CStruct):
link: t.CUInt32T
element: t.CUInt32T
class int_slot(t.CStruct):
td: uhci_td | t.CPtr
callback: t.CInt | t.CPtr
active: t.CInt
qh: uhci_qh | t.CPtr
_io_base: t.CUInt16T = 0
_frame_list: t.CUInt32T | t.CPtr = None
_ctrl_qh: uhci_qh | t.CPtr = None
_bulk_qh: uhci_qh | t.CPtr = None
_irq: t.CInt = -1
_int_slots: t.CArray[int_slot, UHCI_INT_SLOTS]
_int_slot_count: t.CInt = 0
def _reg_read16(offset: t.CUInt16T) -> t.CUInt16T:
return asm.inw(_io_base + offset)
def _reg_write16(offset: t.CUInt16T, value: t.CUInt16T):
asm.outw(_io_base + offset, value)
def _reg_read32(offset: t.CUInt16T) -> t.CUInt32T:
return asm.inl(_io_base + offset)
def _reg_write32(offset: t.CUInt16T, value: t.CUInt32T):
asm.outl(_io_base + offset, value)
def _uhci_irq_handler() -> t.CInt:
sts: t.CUInt16T = _reg_read16(UHCI_USBSTS)
_reg_write16(UHCI_USBSTS, sts)
if (sts & (UHCI_STS_USBINT | UHCI_STS_USBERRINT)) == 0:
return 0
for si in range(_int_slot_count):
slot: int_slot | t.CPtr = c.Addr(_int_slots[si])
if slot.active and slot.td is not None and slot.callback is not None:
status: t.CUInt32T = slot.td.ctrl_status
if (status & TD_CTRL_ACTIVE) == 0:
slot.active = 0
c.Asm(f"""mov rbp, rsp
and rsp, -16
call {c.AsmInp(slot.callback, t.ASM_DESCR.REG_ANY)}
mov rsp, rbp""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RBX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_RSI, t.ASM_DESCR.CLOBBER_RDI,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11,
t.ASM_DESCR.CLOBBER_RBP])
return 0
def register_int_callback(cb: t.CInt | t.CPtr) -> t.CInt:
global _int_slot_count
if _int_slot_count >= UHCI_INT_SLOTS:
return -1
si: t.CInt = _int_slot_count
qh: uhci_qh | t.CPtr = mm.malloc_direct(uhci_qh.__sizeof__())
if qh is None:
return -2
string.memset(c.Addr(qh), 0, uhci_qh.__sizeof__())
_qh_set_link(qh, 0, 0, 1)
_qh_set_element(qh, 0, 1)
td: uhci_td | t.CPtr = mm.malloc_direct(uhci_td.__sizeof__())
if td is None:
return -3
string.memset(c.Addr(td), 0, uhci_td.__sizeof__())
slot: int_slot | t.CPtr = c.Addr(_int_slots[si])
slot.td = td
slot.callback = cb
slot.active = 0
slot.qh = qh
_int_slot_count = si + 1
_rebuild_int_chain()
return si
def schedule_int_transfer(slot_id: t.CInt, dev_addr: t.CUInt8T, endpoint: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, toggle_ptr: t.CUInt8T | t.CPtr, buf: t.CVoid | t.CPtr):
if slot_id < 0 or slot_id >= _int_slot_count:
return
slot: int_slot | t.CPtr = c.Addr(_int_slots[slot_id])
td: uhci_td | t.CPtr = slot.td
qh: uhci_qh | t.CPtr = slot.qh
if td is None or qh is None:
return
string.memset(c.Addr(td), 0, uhci_td.__sizeof__())
_td_set_link(td, 0, 0, 1)
_td_set_ctrl(td, ls, max_pkt)
_td_set_token(td, TD_TOKEN_IN, dev_addr, endpoint, toggle_ptr, max_pkt)
td.ctrl_status = td.ctrl_status | TD_CTRL_IOC
td.buffer = t.CUInt32T(t.CUInt64T(buf))
_qh_set_element(qh, t.CUInt32T(t.CUInt64T(td)), 0)
slot.active = 1
def _rebuild_int_chain():
if _int_slot_count == 0:
return
first_qh: uhci_qh | t.CPtr = None
prev_qh: uhci_qh | t.CPtr = None
for si in range(_int_slot_count):
cur_qh: uhci_qh | t.CPtr = _int_slots[si].qh
if cur_qh is not None:
if first_qh is None:
first_qh = cur_qh
if prev_qh is not None:
_qh_set_link(prev_qh, t.CUInt32T(t.CUInt64T(cur_qh)) | TD_LINK_QH, 1, 0)
prev_qh = cur_qh
if prev_qh is not None:
_qh_set_link(prev_qh, t.CUInt32T(t.CUInt64T(_ctrl_qh)) | TD_LINK_QH, 1, 0)
if first_qh is not None:
qh_addr: t.CUInt32T = t.CUInt32T(t.CUInt64T(first_qh)) | TD_LINK_QH
i: t.CInt
for i in range(UHCI_NUM_FRAMES):
entry_ptr: t.CUInt32T | t.CPtr = _frame_list + t.CUInt64T(i) * 4
c.Set(c.Deref(entry_ptr), qh_addr)
def _td_set_link(td: uhci_td | t.CPtr, addr: t.CUInt32T, is_qh: t.CInt, terminate: t.CInt):
val: t.CUInt32T = (addr & 0xFFFFFFF0)
if is_qh: val = val | TD_LINK_QH
if terminate: val = val | TD_LINK_TERMINATE
td.link = val
def _td_set_token(td: uhci_td | t.CPtr, pid: t.CUInt8T, dev_addr: t.CUInt8T, endpoint: t.CUInt8T, toggle: t.CUInt8T, max_len: t.CUInt16T):
len_bits: t.CUInt32T
if max_len == 0:
len_bits = 0x7FF
else:
len_bits = t.CUInt32T(max_len) - 1
td.token = (t.CUInt32T(pid) << 0) | (t.CUInt32T(dev_addr) << 8) | (t.CUInt32T(endpoint & 0x0F) << 15) | (t.CUInt32T(toggle & 1) << 19) | (len_bits << 21)
def _td_set_ctrl(td: uhci_td | t.CPtr, ls: t.CInt, maxlen: t.CUInt16T):
td.ctrl_status = TD_CTRL_ACTIVE | (3 << TD_CTRL_CERR_SHIFT)
if ls: td.ctrl_status = td.ctrl_status | TD_CTRL_LS
def _qh_set_link(qh: uhci_qh | t.CPtr, addr: t.CUInt32T, is_qh: t.CInt, terminate: t.CInt):
val: t.CUInt32T = (addr & 0xFFFFFFF0)
if is_qh: val = val | TD_LINK_QH
if terminate: val = val | TD_LINK_TERMINATE
qh.link = val
def _qh_set_element(qh: uhci_qh | t.CPtr, addr: t.CUInt32T, terminate: t.CInt):
val: t.CUInt32T = (addr & 0xFFFFFFF0)
if terminate: val = val | TD_LINK_TERMINATE
qh.element = val
def _wait_for_complete(td: uhci_td | t.CPtr, timeout_ms: t.CInt) -> t.CInt:
start: t.CUInt64T = timer.timer_get_ticks()
deadline: t.CUInt64T = start + t.CUInt64T(timeout_ms)
poll_cnt: t.CInt = 0
while timer.timer_get_ticks() < deadline:
status: t.CUInt32T = td.ctrl_status
if (status & TD_CTRL_ACTIVE) == 0:
if (status & 0x00600000) != 0:
return -1
return 0
poll_cnt += 1
if poll_cnt >= 100:
poll_cnt = 0
asm.sti()
asm.hlt()
return -2
def reset(io_base: t.CUInt16T) -> t.CInt:
global _io_base
_io_base = io_base
_reg_write16(UHCI_USBCMD, UHCI_CMD_HCRESET)
i: t.CInt
for i in range(1000):
if (_reg_read16(UHCI_USBCMD) & UHCI_CMD_HCRESET) == 0:
break
_reg_write16(UHCI_USBSTS, 0xFFFF)
_reg_write16(UHCI_USBINTR, 0x0000)
return 0
def init(io_base: t.CUInt16T) -> t.CInt:
global _io_base, _frame_list, _ctrl_qh, _bulk_qh, _int_slot_count
_io_base = io_base
_int_slot_count = 0
serial.puts("[uhci] resetting...\n")
if reset(io_base) != 0:
serial.puts("[uhci] reset failed\n")
return -1
serial.puts("[uhci] allocating frame list...\n")
_frame_list = mm.malloc(UHCI_NUM_FRAMES * 4)
if _frame_list is None:
serial.puts("[uhci] frame list alloc failed\n")
return -2
string.memset(_frame_list, 0, UHCI_NUM_FRAMES * 4)
_ctrl_qh = mm.malloc_direct(uhci_qh.__sizeof__())
if _ctrl_qh is None:
serial.puts("[uhci] ctrl_qh alloc failed\n")
return -3
string.memset(c.Addr(_ctrl_qh), 0, uhci_qh.__sizeof__())
_qh_set_link(_ctrl_qh, 0, 0, 1)
_qh_set_element(_ctrl_qh, 0, 1)
_bulk_qh = mm.malloc_direct(uhci_qh.__sizeof__())
if _bulk_qh is None:
serial.puts("[uhci] bulk_qh alloc failed\n")
return -4
string.memset(c.Addr(_bulk_qh), 0, uhci_qh.__sizeof__())
_qh_set_link(_bulk_qh, 0, 0, 1)
_qh_set_element(_bulk_qh, 0, 1)
serial.puts("[uhci] setting up frame list...\n")
qh_addr: t.CUInt32T = t.CUInt32T(c.Addr(_ctrl_qh)) | TD_LINK_QH
i: t.CInt
for i in range(UHCI_NUM_FRAMES):
entry_ptr: t.CUInt32T | t.CPtr = _frame_list + t.CUInt64T(i) * 4
c.Set(c.Deref(entry_ptr), qh_addr)
_reg_write32(UHCI_FLBASEADD, t.CUInt32T(t.CUInt64T(_frame_list)))
_reg_write16(UHCI_FRNUM, 0)
_reg_write16(UHCI_SOFMOD, 64)
_reg_write16(UHCI_USBCMD, UHCI_CMD_CF | UHCI_CMD_MAXPKT | UHCI_CMD_RUN)
timer.timer_msleep(10)
_reg_write16(UHCI_USBSTS, 0xFFFF)
_reg_write16(UHCI_USBINTR, UHCI_INTR_IOC | UHCI_INTR_SHORT)
_irq = t.CInt(pci._uhci_dev.irq)
if _irq > 0 and _irq < 16:
idt.irqInstallHandler(_irq, _uhci_irq_handler, "uhci")
pic.clearMask(t.CUInt8T(_irq))
serial.puts("[uhci] started\n")
return 0
def port_reset(port: t.CInt) -> t.CInt:
sc: t.CUInt16T
if port == 0:
sc = _reg_read16(UHCI_PORTSC1)
else:
sc = _reg_read16(UHCI_PORTSC2)
if (sc & UHCI_PORT_CONNECT) == 0:
return -1
if port == 0:
_reg_write16(UHCI_PORTSC1, sc | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE)
else:
_reg_write16(UHCI_PORTSC2, sc | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE)
if port == 0:
_reg_write16(UHCI_PORTSC1, UHCI_PORT_RESET)
else:
_reg_write16(UHCI_PORTSC2, UHCI_PORT_RESET)
timer.timer_msleep(50)
if port == 0:
_reg_write16(UHCI_PORTSC1, 0x0000)
else:
_reg_write16(UHCI_PORTSC2, 0x0000)
timer.timer_msleep(10)
if port == 0:
sc = _reg_read16(UHCI_PORTSC1)
_reg_write16(UHCI_PORTSC1, sc | UHCI_PORT_ENABLE | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE)
else:
sc = _reg_read16(UHCI_PORTSC2)
_reg_write16(UHCI_PORTSC2, sc | UHCI_PORT_ENABLE | UHCI_PORT_CONNECT_CHANGE | UHCI_PORT_ENABLE_CHANGE)
timer.timer_msleep(10)
if port == 0:
sc = _reg_read16(UHCI_PORTSC1)
else:
sc = _reg_read16(UHCI_PORTSC2)
if (sc & UHCI_PORT_ENABLE) == 0:
return -2
return 0
def port_is_connected(port: t.CInt) -> t.CInt:
sc: t.CUInt16T
if port == 0:
sc = _reg_read16(UHCI_PORTSC1)
else:
sc = _reg_read16(UHCI_PORTSC2)
if sc & UHCI_PORT_CONNECT: return 1
return 0
def port_is_low_speed(port: t.CInt) -> t.CInt:
sc: t.CUInt16T
if port == 0:
sc = _reg_read16(UHCI_PORTSC1)
else:
sc = _reg_read16(UHCI_PORTSC2)
if sc & UHCI_PORT_LOW_SPEED: return 1
return 0
def control_transfer(dev_addr: t.CUInt8T, endpoint: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, setup_buf: t.CVoid | t.CPtr, data_buf: t.CVoid | t.CPtr, data_len: t.CUInt16T, data_in: t.CInt) -> t.CInt:
td_count: t.CInt = 2
if data_len > 0:
td_count = td_count + 1
total: t.CUInt64T = t.CUInt64T(td_count) * uhci_td.__sizeof__()
tds: uhci_td | t.CPtr = mm.malloc_direct(total)
if tds is None: return -1
string.memset(c.Addr(tds), 0, total)
setup_td: uhci_td | t.CPtr = tds
_td_set_link(setup_td, t.CUInt32T(t.CUInt64T(tds) + uhci_td.__sizeof__()), 0, 0)
_td_set_ctrl(setup_td, ls, 8)
_td_set_token(setup_td, TD_TOKEN_SETUP, dev_addr, endpoint, TD_TOKEN_DATA0, 8)
setup_td.buffer = t.CUInt32T(t.CUInt64T(setup_buf))
if data_len > 0:
data_td: uhci_td | t.CPtr = t.CUInt64T(tds) + uhci_td.__sizeof__()
_td_set_link(data_td, t.CUInt32T(t.CUInt64T(data_td) + uhci_td.__sizeof__()), 0, 0)
_td_set_ctrl(data_td, ls, max_pkt)
if data_in:
_td_set_token(data_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, data_len)
else:
_td_set_token(data_td, TD_TOKEN_OUT, dev_addr, endpoint, TD_TOKEN_DATA1, data_len)
data_td.buffer = t.CUInt32T(t.CUInt64T(data_buf))
status_td: uhci_td | t.CPtr = t.CUInt64T(data_td) + uhci_td.__sizeof__()
_td_set_link(status_td, 0, 0, 1)
_td_set_ctrl(status_td, ls, max_pkt)
if data_in:
_td_set_token(status_td, TD_TOKEN_OUT, dev_addr, endpoint, TD_TOKEN_DATA1, 0)
else:
_td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0)
status_td.buffer = 0
else:
status_td: uhci_td | t.CPtr = t.CUInt64T(tds) + uhci_td.__sizeof__()
_td_set_link(status_td, 0, 0, 1)
_td_set_ctrl(status_td, ls, max_pkt)
_td_set_token(status_td, TD_TOKEN_IN, dev_addr, endpoint, TD_TOKEN_DATA1, 0)
status_td.buffer = 0
_qh_set_element(_ctrl_qh, t.CUInt32T(t.CUInt64T(setup_td)), 0)
result: t.CInt = _wait_for_complete(status_td, 500)
_qh_set_element(_ctrl_qh, 0, 1)
mm.free(tds)
return result
+330
View File
@@ -0,0 +1,330 @@
import t, c
import mm.mm as mm
import viperstring as string
import drivers.serial.uart.serial as serial
import drivers.usb.uhci as uhci
import drivers.usb.pci as pci
import platform.pch.timer as timer
import viperlib
USB_DESC_DEVICE: t.CDefine = 1
USB_DESC_CONFIG: t.CDefine = 2
USB_DESC_STRING: t.CDefine = 3
USB_DESC_INTERFACE: t.CDefine = 4
USB_DESC_ENDPOINT: t.CDefine = 5
USB_REQ_GET_STATUS: t.CDefine = 0
USB_REQ_CLEAR_FEATURE: t.CDefine = 1
USB_REQ_SET_FEATURE: t.CDefine = 3
USB_REQ_SET_ADDRESS: t.CDefine = 5
USB_REQ_GET_DESCRIPTOR: t.CDefine = 6
USB_REQ_SET_DESCRIPTOR: t.CDefine = 7
USB_REQ_GET_CONFIGURATION: t.CDefine = 8
USB_REQ_SET_CONFIGURATION: t.CDefine = 9
USB_REQ_SET_INTERFACE: t.CDefine = 11
HID_CLASS: t.CDefine = 0x03
HID_SUBCLASS_BOOT: t.CDefine = 0x01
HID_PROTO_KEYBOARD: t.CDefine = 0x01
HID_PROTO_MOUSE: t.CDefine = 0x02
HID_REQ_SET_PROTOCOL: t.CDefine = 0x0B
HID_REQ_SET_IDLE: t.CDefine = 0x0A
HID_REQ_GET_REPORT: t.CDefine = 0x01
USB_MAX_DEVICES: t.CDefine = 8
class usb_dev_desc(t.CStruct):
bLength: t.CUInt8T
bDescriptorType: t.CUInt8T
bcdUSB: t.CUInt16T
bDeviceClass: t.CUInt8T
bDeviceSubClass: t.CUInt8T
bDeviceProtocol: t.CUInt8T
bMaxPacketSize0: t.CUInt8T
idVendor: t.CUInt16T
idProduct: t.CUInt16T
bcdDevice: t.CUInt16T
iManufacturer: t.CUInt8T
iProduct: t.CUInt8T
iSerialNumber: t.CUInt8T
bNumConfigurations: t.CUInt8T
class usb_config_desc(t.CStruct):
bLength: t.CUInt8T
bDescriptorType: t.CUInt8T
wTotalLength: t.CUInt16T
bNumInterfaces: t.CUInt8T
bConfigurationValue: t.CUInt8T
iConfiguration: t.CUInt8T
bmAttributes: t.CUInt8T
bMaxPower: t.CUInt8T
class usb_iface_desc(t.CStruct):
bLength: t.CUInt8T
bDescriptorType: t.CUInt8T
bInterfaceNumber: t.CUInt8T
bAlternateSetting: t.CUInt8T
bNumEndpoints: t.CUInt8T
bInterfaceClass: t.CUInt8T
bInterfaceSubClass: t.CUInt8T
bInterfaceProtocol: t.CUInt8T
iInterface: t.CUInt8T
class usb_ep_desc(t.CStruct):
bLength: t.CUInt8T
bDescriptorType: t.CUInt8T
bEndpointAddress: t.CUInt8T
bmAttributes: t.CUInt8T
wMaxPacketSize: t.CUInt16T
bInterval: t.CUInt8T
class usb_setup_pkt(t.CStruct):
bmRequestType: t.CUInt8T
bRequest: t.CUInt8T
wValue: t.CUInt16T
wIndex: t.CUInt16T
wLength: t.CUInt16T
class usb_device(t.CStruct):
addr: t.CUInt8T
ls: t.CInt
max_pkt0: t.CUInt8T
iface_class: t.CUInt8T
iface_subclass: t.CUInt8T
iface_protocol: t.CUInt8T
iface_num: t.CUInt8T
ep_in: t.CUInt8T
ep_in_max: t.CUInt16T
ep_in_interval: t.CUInt8T
ep_in_toggle: t.CUInt8T
configured: t.CInt
_devices: t.CArray[usb_device, USB_MAX_DEVICES]
_device_count: t.CInt = 0
def _setup_packet(bmRequestType: t.CUInt8T, bRequest: t.CUInt8T, wValue: t.CUInt16T, wIndex: t.CUInt16T, wLength: t.CUInt16T, buf: usb_setup_pkt | t.CPtr):
buf.bmRequestType = bmRequestType
buf.bRequest = bRequest
buf.wValue = wValue
buf.wIndex = wIndex
buf.wLength = wLength
def _send_control(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, setup: usb_setup_pkt | t.CPtr, data_buf: t.CVoid | t.CPtr, data_len: t.CUInt16T, data_in: t.CInt) -> t.CInt:
return uhci.control_transfer(dev_addr, 0, ls, max_pkt, c.Addr(setup), data_buf, data_len, data_in)
def get_descriptor(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, desc_type: t.CUInt8T, desc_idx: t.CUInt8T, buf: t.CVoid | t.CPtr, length: t.CUInt16T) -> t.CInt:
setup: usb_setup_pkt
_setup_packet(0x80, USB_REQ_GET_DESCRIPTOR, (t.CUInt16T(desc_type) << 8) | desc_idx, 0, length, c.Addr(setup))
return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), buf, length, 1)
def set_address(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, new_addr: t.CUInt8T) -> t.CInt:
setup: usb_setup_pkt
_setup_packet(0x00, USB_REQ_SET_ADDRESS, new_addr, 0, 0, c.Addr(setup))
return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0)
def set_configuration(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, config_val: t.CUInt8T) -> t.CInt:
setup: usb_setup_pkt
_setup_packet(0x00, USB_REQ_SET_CONFIGURATION, config_val, 0, 0, c.Addr(setup))
return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0)
def hid_set_protocol(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, iface: t.CUInt8T, protocol: t.CUInt8T) -> t.CInt:
setup: usb_setup_pkt
_setup_packet(0x21, HID_REQ_SET_PROTOCOL, protocol, t.CUInt16T(iface), 0, c.Addr(setup))
return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0)
def hid_set_idle(dev_addr: t.CUInt8T, ls: t.CInt, max_pkt: t.CUInt8T, iface: t.CUInt8T, duration: t.CUInt8T) -> t.CInt:
setup: usb_setup_pkt
_setup_packet(0x21, HID_REQ_SET_IDLE, t.CUInt16T(duration) << 8, t.CUInt16T(iface), 0, c.Addr(setup))
return _send_control(dev_addr, ls, max_pkt, c.Addr(setup), None, 0, 0)
def _parse_config(dev: usb_device | t.CPtr, config_buf: t.CVoid | t.CPtr, total_len: t.CUInt16T):
offset: t.CInt = 0
found_hid: t.CInt = 0
while offset < t.CInt(total_len):
p: t.CUInt8T | t.CPtr = config_buf + t.CUInt64T(offset)
desc_len: t.CUInt8T = c.Deref(p)
p2: t.CUInt8T | t.CPtr = config_buf + t.CUInt64T(offset) + 1
desc_type: t.CUInt8T = c.Deref(p2)
if desc_len == 0: break
if desc_type == USB_DESC_INTERFACE:
iface: usb_iface_desc | t.CPtr = config_buf + t.CUInt64T(offset)
if iface.bInterfaceClass == HID_CLASS:
found_hid = 1
dev.iface_class = HID_CLASS
dev.iface_subclass = iface.bInterfaceSubClass
dev.iface_protocol = iface.bInterfaceProtocol
dev.iface_num = iface.bInterfaceNumber
if desc_type == USB_DESC_ENDPOINT and found_hid:
ep: usb_ep_desc | t.CPtr = config_buf + t.CUInt64T(offset)
if (ep.bEndpointAddress & 0x80) != 0:
dev.ep_in = ep.bEndpointAddress & 0x0F
dev.ep_in_max = ep.wMaxPacketSize
dev.ep_in_interval = ep.bInterval
found_hid = 0
offset = offset + t.CInt(desc_len)
def enumerate_port(port: t.CInt) -> t.CInt:
global _device_count
if _device_count >= USB_MAX_DEVICES: return -1
ls: t.CInt = uhci.port_is_low_speed(port)
reset_ok: t.CInt = -1
reset_retry: t.CInt
for reset_retry in range(3):
if uhci.port_reset(port) == 0:
reset_ok = 0
break
timer.timer_msleep(50)
if reset_ok != 0:
return -2
timer.timer_msleep(10)
dev_buf: t.CVoid | t.CPtr = mm.malloc(usb_dev_desc.__sizeof__())
if dev_buf is None: return -3
string.memset(dev_buf, 0, usb_dev_desc.__sizeof__())
result: t.CInt = get_descriptor(0, ls, 8, USB_DESC_DEVICE, 0, dev_buf, 8)
retry: t.CInt
for retry in range(3):
if result == 0: break
timer.timer_msleep(50)
uhci.port_reset(port)
timer.timer_msleep(10)
result = get_descriptor(0, ls, 8, USB_DESC_DEVICE, 0, dev_buf, 8)
if result != 0:
mm.free(dev_buf)
return -4
desc: usb_dev_desc | t.CPtr = dev_buf
max_pkt: t.CUInt8T = desc.bMaxPacketSize0
if max_pkt < 8: max_pkt = 8
new_addr: t.CUInt8T = t.CUInt8T(_device_count + 1)
result = set_address(0, ls, max_pkt, new_addr)
for retry in range(3):
if result == 0: break
timer.timer_msleep(20)
result = set_address(0, ls, max_pkt, new_addr)
if result != 0:
mm.free(dev_buf)
return -5
timer.timer_msleep(10)
dev: usb_device | t.CPtr = c.Addr(_devices[_device_count])
dev.addr = new_addr
dev.ls = ls
dev.max_pkt0 = max_pkt
dev.configured = 0
dev.iface_class = 0
dev.iface_subclass = 0
dev.iface_protocol = 0
dev.ep_in = 0
dev.ep_in_max = 0
dev.ep_in_interval = 0
dev.ep_in_toggle = 0
config_buf: t.CVoid | t.CPtr = mm.malloc(256)
if config_buf is None:
mm.free(dev_buf)
return -6
string.memset(config_buf, 0, 256)
result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, 9)
for retry in range(3):
if result == 0: break
timer.timer_msleep(50)
result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, 9)
if result != 0:
mm.free(config_buf)
mm.free(dev_buf)
return -7
cfg: usb_config_desc | t.CPtr = config_buf
total_len: t.CUInt16T = cfg.wTotalLength
if total_len > 256: total_len = 256
result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, total_len)
for retry in range(3):
if result == 0: break
timer.timer_msleep(50)
result = get_descriptor(new_addr, ls, max_pkt, USB_DESC_CONFIG, 0, config_buf, total_len)
if result != 0:
mm.free(config_buf)
mm.free(dev_buf)
return -8
_parse_config(dev, config_buf, total_len)
result = set_configuration(new_addr, ls, max_pkt, cfg.bConfigurationValue)
if result != 0:
mm.free(config_buf)
mm.free(dev_buf)
return -9
dev.configured = 1
if dev.iface_class == HID_CLASS:
hid_set_protocol(new_addr, ls, max_pkt, dev.iface_num, 0)
hid_set_idle(new_addr, ls, max_pkt, dev.iface_num, 0)
_device_count = _device_count + 1
mm.free(config_buf)
mm.free(dev_buf)
return t.CInt(new_addr)
def init() -> t.CInt:
global _device_count
_device_count = 0
io_base: t.CUInt16T = pci.find_uhci()
if io_base == 0:
serial.puts("[usb] UHCI controller not found\n")
return -1
buf: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(buf), 64, "[usb] UHCI at I/O 0x%04X\n", t.CUInt32T(io_base))
serial.puts(buf)
if uhci.init(io_base) != 0:
serial.puts("[usb] UHCI init failed\n")
return -2
serial.puts("[usb] UHCI initialized\n")
timer.timer_msleep(50)
port: t.CInt
for port in range(2):
if uhci.port_is_connected(port):
viperlib.snprintf(c.Addr(buf), 64, "[usb] port %d connected\n", t.CUInt32T(port))
serial.puts(buf)
dev_id: t.CInt = enumerate_port(port)
if dev_id > 0:
dev: usb_device | t.CPtr = c.Addr(_devices[dev_id - 1])
viperlib.snprintf(c.Addr(buf), 64, "[usb] device addr=%d class=%02X proto=%02X ep=%d\n",
t.CUInt32T(dev.addr), t.CUInt32T(dev.iface_class),
t.CUInt32T(dev.iface_protocol), t.CUInt32T(dev.ep_in))
serial.puts(buf)
else:
viperlib.snprintf(c.Addr(buf), 64, "[usb] enumerate port %d failed: %d\n", t.CUInt32T(port), t.CInt32T(dev_id))
serial.puts(buf)
return _device_count
def find_hid_device(protocol: t.CUInt8T) -> usb_device | t.CPtr:
i: t.CInt
for i in range(_device_count):
dev: usb_device | t.CPtr = c.Addr(_devices[i])
if dev.iface_class == HID_CLASS:
if dev.iface_protocol == protocol:
if dev.configured != 0:
return dev
return None
def read_interrupt(dev: usb_device | t.CPtr, buf: t.CVoid | t.CPtr) -> t.CInt:
return 0
+85
View File
@@ -0,0 +1,85 @@
from stdint import *
import drivers.video.vesafb.gfx as gfx
import t, c
MAX_SHEETS: t.CDefine = 64
MAX_UI_COMPONENTS: t.CDefine = 256
_next_sheet_id: t.CInt = 0
_next_component_id: t.CInt = 0
@t.Object
class Sheet:
id: t.CInt
x: t.CInt
y: t.CInt
w: t.CInt
h: t.CInt
visible: t.CInt
_surf_obj: gfx.Surface
_renderer_obj: gfx.Renderer
surf: gfx.Surface | t.CPtr
renderer: gfx.Renderer | t.CPtr
parent: 'Sheet | t.CPtr'
bg_color: t.CUInt32T
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, parent: 'Sheet | t.CPtr' = None):
global _next_sheet_id
self.id = _next_sheet_id
_next_sheet_id += 1
self.x = x
self.y = y
self.w = w
self.h = h
self.visible = 1
self._surf_obj = gfx.Surface(fb, zb, w, h)
self.surf = c.Addr(self._surf_obj)
self._renderer_obj = gfx.Renderer(self.surf)
self.renderer = c.Addr(self._renderer_obj)
self.parent = parent
self.bg_color = gfx.COLOR_RGB(20, 20, 30)
def MoveTo(self, nx: t.CInt, ny: t.CInt):
self.x = nx
self.y = ny
def Resize(self, nw: t.CInt, nh: t.CInt):
self.w = nw
self.h = nh
def Show(self):
self.visible = 1
def Hide(self):
self.visible = 0
def Clear(self):
r: gfx.Renderer = c.Deref(self.renderer)
r.Clear(self.bg_color)
def Fill(self, color: t.CUInt32T):
r: gfx.Renderer = c.Deref(self.renderer)
r.Clear(color)
def DrawRect(self, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T):
r: gfx.Renderer = c.Deref(self.renderer)
r.DrawLine2D(rx, ry, rx + rw - 1, ry, color)
r.DrawLine2D(rx + rw - 1, ry, rx + rw - 1, ry + rh - 1, color)
r.DrawLine2D(rx + rw - 1, ry + rh - 1, rx, ry + rh - 1, color)
r.DrawLine2D(rx, ry + rh - 1, rx, ry, color)
def DrawFilledRect(self, rx: t.CInt, ry: t.CInt, rw: t.CInt, rh: t.CInt, color: t.CUInt32T):
surf: gfx.Surface = c.Deref(self.surf)
fb: UINT32PTR = surf.fb
w: t.CInt = surf.w
h: t.CInt = surf.h
for py in range(ry, ry + rh):
if py < 0 or py >= h: continue
for px in range(rx, rx + rw):
if px < 0 or px >= w: continue
fb[py * w + px] = color
def Contains(self, px: t.CInt, py: t.CInt) -> t.CInt:
if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h:
return 1
return 0
+152
View File
@@ -0,0 +1,152 @@
from stdint import *
import drivers.video.vesafb.gfx as gfx
import drivers.video.ui.sheet as sheet
import string
import t, c
UI_LABEL: t.CDefine = 0
UI_BUTTON: t.CDefine = 1
UI_PANEL: t.CDefine = 2
UI_PROGRESS: t.CDefine = 3
@t.Object
class UIComponent:
id: t.CInt
ctype: t.CInt
x: t.CInt
y: t.CInt
w: t.CInt
h: t.CInt
visible: t.CInt
sheet: sheet.Sheet | t.CPtr
fg_color: t.CUInt32T
bg_color: t.CUInt32T
text: t.CArray[t.CChar, 128]
text_len: t.CInt
value: t.CInt
def __init__(self, ctype: t.CInt, sh: sheet.Sheet | t.CPtr, x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt):
global _next_component_id
self.id = _next_component_id
_next_component_id += 1
self.ctype = ctype
self.x = x
self.y = y
self.w = w
self.h = h
self.visible = 1
self.sheet = sh
self.fg_color = gfx.COLOR_RGB(255, 255, 255)
self.bg_color = gfx.COLOR_RGB(40, 40, 60)
string.memset(c.Addr(self.text), 0, 128)
self.text_len = 0
self.value = 0
def SetText(self, s: str):
i: t.CInt = 0
for ch in s:
if i >= 127: break
self.text[i] = ch
i += 1
self.text[i] = 0
self.text_len = i
def SetValue(self, v: t.CInt):
self.value = v
def Render(self):
if self.visible == 0: return
sh: sheet.Sheet = c.Deref(self.sheet)
match self.ctype:
case 0:
self._RenderLabel(sh)
case 1:
self._RenderButton(sh)
case 2:
self._RenderPanel(sh)
case 3:
self._RenderProgress(sh)
def _RenderLabel(self, sh: sheet.Sheet):
sh.DrawFilledRect(self.x, self.y, self.w, self.h, 0)
def _RenderButton(self, sh: sheet.Sheet):
sh.DrawFilledRect(self.x, self.y, self.w, self.h, self.bg_color)
sh.DrawRect(self.x, self.y, self.w, self.h, self.fg_color)
def _RenderPanel(self, sh: sheet.Sheet):
sh.DrawFilledRect(self.x, self.y, self.w, self.h, self.bg_color)
sh.DrawRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(80, 80, 120))
def _RenderProgress(self, sh: sheet.Sheet):
sh.DrawFilledRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(30, 30, 40))
fill_w: t.CInt = self.w * self.value / 100
if fill_w > 0:
sh.DrawFilledRect(self.x, self.y, fill_w, self.h, gfx.COLOR_RGB(0, 180, 255))
sh.DrawRect(self.x, self.y, self.w, self.h, gfx.COLOR_RGB(80, 80, 100))
def Contains(self, px: t.CInt, py: t.CInt) -> t.CInt:
if px >= self.x and px < self.x + self.w and py >= self.y and py < self.y + self.h:
return 1
return 0
@t.Object
class UIManager:
sheets: t.CArray[sheet.Sheet | t.CPtr, 64]
sheet_count: t.CInt
components: t.CArray[UIComponent | t.CPtr, 256]
component_count: t.CInt
screen_w: t.CInt
screen_h: t.CInt
def __init__(self, w: t.CInt, h: t.CInt):
self.sheet_count = 0
self.component_count = 0
self.screen_w = w
self.screen_h = h
i: t.CInt
for i in range(64):
self.sheets[i] = None
for i in range(256):
self.components[i] = None
def AddSheet(self, sh: sheet.Sheet | t.CPtr) -> t.CInt:
if self.sheet_count >= 64: return -1
self.sheets[self.sheet_count] = sh
self.sheet_count += 1
return 0
def AddComponent(self, comp: UIComponent | t.CPtr) -> t.CInt:
if self.component_count >= 256: return -1
self.components[self.component_count] = comp
self.component_count += 1
return 0
def FindComponent(self, cid: t.CInt) -> UIComponent | t.CPtr:
i: t.CInt
for i in range(self.component_count):
p: UIComponent | t.CPtr = self.components[i]
if p is not None:
comp: UIComponent = c.Deref(p)
if comp.id == cid: return p
return None
def RenderAll(self):
i: t.CInt
for i in range(self.component_count):
p: UIComponent | t.CPtr = self.components[i]
if p is not None:
comp: UIComponent = c.Deref(p)
comp.Render()
def HitTest(self, px: t.CInt, py: t.CInt) -> UIComponent | t.CPtr:
i: t.CInt = self.component_count - 1
while i >= 0:
p: UIComponent | t.CPtr = self.components[i]
if p is not None:
comp: UIComponent = c.Deref(p)
if comp.visible == 1 and comp.Contains(px, py) == 1:
return p
i -= 1
return None
+305
View File
@@ -0,0 +1,305 @@
from stdint import *
import vipermath
import string
import t, c
PI: t.CDefine = float(3.14159265358979323846)
SPHERE_SLICES: t.CDefine = (24 * 1)
SPHERE_STACKS: t.CDefine = (16 * 1)
SIN_WAVE_GRID: t.CDefine = (40 * 1)
def COLOR_RGB(r: int, g: int, b: int) -> t.CInline | t.CInt:
return (255 << 24) | (r << 16) | (g << 8) | (b)
@t.Object
class Vec3:
x: float
y: float
z: float
def __init__(self, x: float, y: float, z: float):
self.x = x
self.y = y
self.z = z
def Sub(self, b: 'Vec3') -> 'Vec3':
return Vec3(self.x - b.x, self.y - b.y, self.z - b.z)
def Cross(self, b: 'Vec3') -> 'Vec3':
return Vec3(self.y * b.z - self.z * b.y, self.z * b.x - self.x * b.z, self.x * b.y - self.y * b.x)
def Dot(self, b: 'Vec3') -> float:
return self.x * b.x + self.y * b.y + self.z * b.z
def Norm(self) -> 'Vec3':
l = vipermath.sqrtf(self.Dot(self))
return Vec3(self.x / l, self.y / l, self.z / l)
class Mat4:
m: t.CArray[t.CArray[float, 4], 4]
def Mat4_Identity() -> Mat4:
m: Mat4 = Mat4()
string.memset(c.Addr(m), 0, Mat4.__sizeof__())
m.m[0][0] = 1; m.m[1][1] = 1; m.m[2][2] = 1; m.m[3][3] = 1
return m
def Mat4_Mul(a: Mat4, b: Mat4) -> Mat4:
res: Mat4 = Mat4()
string.memset(c.Addr(res), 0, Mat4.__sizeof__())
for i in range(4):
for j in range(4):
for k in range(4):
res.m[i][j] += a.m[i][k] * b.m[k][j]
return res
def Mat4_MulVec(m: Mat4, v: Vec3) -> Vec3:
x: float = m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3]
y: float = m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3]
z: float = m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3]
w: float = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3]
if w != 0:
x /= w; y /= w; z /= w
return Vec3(x, y, z)
def Mat4_Perspective(fov: float, aspect: float, zn: float, zf: float) -> Mat4:
m: Mat4 = Mat4()
string.memset(c.Addr(m), 0, Mat4.__sizeof__())
f: float = float(1.0) / vipermath.tanf(fov / float(2.0))
m.m[0][0] = f / aspect; m.m[1][1] = f
m.m[2][2] = zf / (zn - zf); m.m[2][3] = (zf * zn) / (zn - zf); m.m[3][2] = -1
return m
def Mat4_LookAt(eye: Vec3, target: Vec3, up: Vec3) -> Mat4:
zaxis: Vec3 = eye.Sub(target).Norm()
xaxis: Vec3 = up.Cross(zaxis).Norm()
yaxis: Vec3 = zaxis.Cross(xaxis)
m: Mat4 = Mat4_Identity()
m.m[0][0]=xaxis.x; m.m[0][1]=xaxis.y; m.m[0][2]=xaxis.z; m.m[0][3]= -xaxis.Dot(eye)
m.m[1][0]=yaxis.x; m.m[1][1]=yaxis.y; m.m[1][2]=yaxis.z; m.m[1][3]= -yaxis.Dot(eye)
m.m[2][0]=zaxis.x; m.m[2][1]=zaxis.y; m.m[2][2]=zaxis.z; m.m[2][3]= -zaxis.Dot(eye)
m.m[3][3] = 1
return m
def Mat4_Translate(x: float, y: float, z: float) -> Mat4:
m: Mat4 = Mat4_Identity()
m.m[0][3] = x; m.m[1][3] = y; m.m[2][3] = z
return m
def Mat4_RotateY(a: float) -> Mat4:
m: Mat4 = Mat4_Identity();
m.m[0][0] = vipermath.cosf(a); m.m[0][2] = vipermath.sinf(a)
m.m[2][0] = -vipermath.sinf(a); m.m[2][2] = vipermath.cosf(a)
return m
@t.Object
class Surface:
fb: UINT32PTR
zb: float | t.CPtr
w: int
h: int
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int):
self.fb = fb
self.zb = zb
self.w = w
self.h = h
@t.Object
class Renderer:
_fb: UINT32PTR
_zb: float | t.CPtr
_w: int
_h: int
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int):
self._fb = fb
self._zb = zb
self._w = w
self._h = h
def Clear(self, color: t.CUInt32T):
fb: UINT32PTR = self._fb
zb: float | t.CPtr = self._zb
size: int = self._w * self._h
for i in range(size):
fb[i] = color
zb[i] = float(1.0)
def DrawLine2D(self, x0: int, y0: int, x1: int, y1: int, col: t.CUInt32T):
fb: UINT32PTR = self._fb
w: int = self._w; h: int = self._h
dx: int = abs(x1-x0); dy: int = abs(y1-y0)
sx: int = 1 if x0 < x1 else -1; sy: int = 1 if y0 < y1 else -1
err: int = dx - dy
while True:
if x0 >= 0 and x0 < w and y0 >= 0 and y0 < h: fb[y0 * w + x0] = col
if x0 == x1 and y0 == y1: break
e2: int = 2 * err
if e2 > -dy: err -= dy; x0 += sx
if e2 < dx: err += dx; y0 += sy
def DrawTriangle(self, v0: Vec3, v1: Vec3, v2: Vec3, col: t.CUInt32T):
if v0.z < float(0.0) or v1.z < float(0.0) or v2.z < float(0.0): return
if (v0.x > float(10.0) or v0.x < float(-10.0) or v0.y > float(10.0) or v0.y < float(-10.0) or
v1.x > float(10.0) or v1.x < float(-10.0) or v1.y > float(10.0) or v1.y < float(-10.0) or
v2.x > float(10.0) or v2.x < float(-10.0) or v2.y > float(10.0) or v2.y < float(-10.0)): return
fb: UINT32PTR = self._fb
zb: float | t.CPtr = self._zb
w: int = self._w
h: int = self._h
sx0: float = (v0.x + float(1.0)) * float(0.5) * w; sy0: float = (float(1.0) - v0.y) * float(0.5) * h
sx1: float = (v1.x + float(1.0)) * float(0.5) * w; sy1: float = (float(1.0) - v1.y) * float(0.5) * h
sx2: float = (v2.x + float(1.0)) * float(0.5) * w; sy2: float = (float(1.0) - v2.y) * float(0.5) * h
minX: int = max(0, int(vipermath.floorf(min(min(sx0, sx1), sx2))))
maxX: int = min(w - 1, int(vipermath.ceilf(max(max(sx0, sx1), sx2))))
minY: int = max(0, int(vipermath.floorf(min(min(sy0, sy1), sy2))))
maxY: int = min(h - 1, int(vipermath.ceilf(max(max(sy0, sy1), sy2))))
dx12: float = sx1 - sx2; dy12: float = sy1 - sy2
dx02: float = sx0 - sx2; dy02: float = sy0 - sy2
denom: float = dx12 * dy02 - dy12 * dx02
if denom < float(0.001) and denom > float(-0.001): return
invDenom: float = float(1.0) / denom
row_dxP_start: float = float(minX) - sx2
for y in range(minY, maxY + 1):
dyP: float = float(y) - sy2
dxP: float = row_dxP_start
row_base: int = y * w
for x in range(minX, maxX + 1):
w0: float = (dx12 * dyP - dy12 * dxP) * invDenom
w1: float = (dxP * dy02 - dyP * dx02) * invDenom
w2: float = float(1.0) - w0 - w1
if w0 >= 0 and w1 >= 0 and w2 >= 0:
z: float = w0 * v0.z + w1 * v1.z + w2 * v2.z
idx: int = row_base + x
if z < zb[idx]:
zb[idx] = z
fb[idx] = col
dxP += float(1.0)
def DrawGlowDot(self, x: float, y: float, radius: float, core_col: t.CUInt32T, intensity: float):
fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h
r: int = int(radius) + 4
ix: int = int(x); iy: int = int(y)
for dy in range(-r, r + 1):
py: int = iy + dy
if py < 0 or py >= h: continue
for dx in range(-r, r + 1):
px: int = ix + dx
if px < 0 or px >= w: continue
dist: float = vipermath.sqrtf(float(dx*dx + dy*dy))
if dist < radius:
self.BlendPixel(px, py, core_col, intensity)
elif dist < float(r):
fade: float = 1.0 - (dist - radius) / 4.0
self.BlendPixel(px, py, core_col, intensity * fade * 0.5)
def BlendPixel(self, x: int, y: int, col: t.CUInt32T, alpha: float):
if alpha <= 0.0: return
fb: UINT32PTR = self._fb; w: int = self._w; h: int = self._h
if x < 0 or x >= w or y < 0 or y >= h: return
idx: int = y * w + x
bg: t.CUInt32T = fb[idx]
bg_b: int = bg & 0xFF; bg_g: int = (bg >> 8) & 0xFF; bg_r: int = (bg >> 16) & 0xFF
f_b: int = col & 0xFF; f_g: int = (col >> 8) & 0xFF; f_r: int = (col >> 16) & 0xFF
a: int = int(alpha * 255.0)
inv_a: int = 255 - a
nr: int = (f_r * a + bg_r * inv_a) >> 8
ng: int = (f_g * a + bg_g * inv_a) >> 8
nb: int = (f_b * a + bg_b * inv_a) >> 8
fb[idx] = COLOR_RGB(255 if (nr > 255) else nr,
255 if (ng > 255) else ng,
255 if (nb > 255) else nb)
@t.Object
class Scene3D:
renderer: Renderer
def __init__(self, fb: UINT32PTR, zb: float | t.CPtr, w: int, h: int):
self.renderer = Renderer(fb, zb, w, h)
def Render(self, time: float):
rw: int = self.renderer._w
rh: int = self.renderer._h
self.renderer.Clear(COLOR_RGB(15, 15, 25))
yaw: float = float(45.0) * PI / float(180.0); pitch: float = float(45.0) * PI / float(180.0); radius: float = float(10.0)
eye: Vec3 = Vec3(radius*vipermath.cosf(pitch)*vipermath.cosf(yaw), radius*vipermath.sinf(pitch), radius*vipermath.cosf(pitch)*vipermath.sinf(yaw))
target: Vec3 = Vec3(0, 0, 0); up: Vec3 = Vec3(0, 1, 0)
view: Mat4 = Mat4_LookAt(eye, target, up)
proj: Mat4 = Mat4_Perspective(PI / float(3.0), float(rw)/float(rh), float(0.1), float(100.0))
vp: Mat4 = Mat4_Mul(proj, view)
cubeX: float = float(-3.5)
cubeMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(cubeX, 0, 0))
self._DrawCube(cubeMVP, COLOR_RGB(180, 180, 180))
colors: t.CArray[t.CUInt32T, 4] = [COLOR_RGB(255,50,50), COLOR_RGB(50,50,255), COLOR_RGB(255,255,50), COLOR_RGB(50,255,255)]
for i in range(4):
a: float = time * float(1.5) + i * (PI / float(2.0)); r: float = float(3.0)
pos: Vec3 = Vec3(vipermath.cosf(a) * r + cubeX, 0, vipermath.sinf(a) * r)
sphereMVP: Mat4 = Mat4_Mul(vp, Mat4_Translate(pos.x, pos.y, pos.z))
self._DrawSphere(sphereMVP, Vec3(0,0,0), float(0.5), colors[i])
self._Draw3DSinWave(vp, Vec3(float(3.5), 0, 0), time)
self.renderer.DrawLine2D(50, rh - 150, rw - 50, rh - 150, COLOR_RGB(80, 80, 80))
self.renderer.DrawLine2D(50, rh - 50, 50, rh - 250, COLOR_RGB(80, 80, 80))
wave_shift: float = time * float(2.0)
prev_x: int = 50; prev_y: int = rh - 150 - int(vipermath.sinf(0 - wave_shift) * float(100.0))
for i in range(1, rw - 100):
tr: float = float(i) / float(rw-100) * float(4.0) * PI
cx: int = 50 + i; cy: int = rh - 150 - int(vipermath.sinf(tr - wave_shift) * float(100.0))
self.renderer.DrawLine2D(prev_x, prev_y, cx, cy, COLOR_RGB(0, 255, 100))
prev_x = cx; prev_y = cy
def _DrawCube(self, mvp: Mat4, col: t.CUInt32T):
v: t.CArray[Vec3, 8] = [
Vec3(-1, -1, -1), Vec3(-1, -1, 1), Vec3(-1, 1, -1), Vec3(-1, 1, 1),
Vec3( 1, -1, -1), Vec3( 1, -1, 1), Vec3( 1, 1, -1), Vec3( 1, 1, 1)
]
for i in range(8): v[i] = Mat4_MulVec(mvp, v[i])
faces: t.CArray[t.CArray[int, 3], 12] = [
[0, 2, 1], [1, 2, 3], [4, 1, 5], [4, 0, 1], [6, 3, 2], [6, 7, 3],
[4, 6, 0], [0, 6, 2], [5, 7, 4], [4, 7, 6], [1, 3, 5], [5, 3, 7]
]
for i in range(12): self.renderer.DrawTriangle(v[faces[i][0]], v[faces[i][1]], v[faces[i][2]], col)
def _DrawSphere(self, mvp: Mat4, center: Vec3, radius: float, col: t.CUInt32T):
step_pi: float = PI / SPHERE_STACKS; step_2pi: float = float(2.0) * PI / SPHERE_SLICES
for i in range(SPHERE_STACKS):
for j in range(SPHERE_SLICES):
theta1: float = i * step_pi; theta2: float = (i + 1) * step_pi
phi1: float = j * step_2pi; phi2: float = (j + 1) * step_2pi
v: t.CArray[Vec3, 4]
v[0] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi1))
v[1] = Vec3(center.x+radius*vipermath.sinf(theta1)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta1), center.z+radius*vipermath.sinf(theta1)*vipermath.sinf(phi2))
v[2] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi1), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi1))
v[3] = Vec3(center.x+radius*vipermath.sinf(theta2)*vipermath.cosf(phi2), center.y+radius*vipermath.cosf(theta2), center.z+radius*vipermath.sinf(theta2)*vipermath.sinf(phi2))
for k in range(4): v[k] = Mat4_MulVec(mvp, v[k])
self.renderer.DrawTriangle(v[0], v[1], v[2], col)
self.renderer.DrawTriangle(v[1], v[3], v[2], col)
def _Draw3DSinWave(self, vp: Mat4, pos: Vec3, time: float):
_range: float = float(2.0) * PI; step: float = _range / SIN_WAVE_GRID; scale: float = float(0.5)
mvp: Mat4 = Mat4_Mul(vp, Mat4_Mul(Mat4_Translate(pos.x, pos.y, pos.z), Mat4_RotateY(time * float(1.0))))
for i in range(SIN_WAVE_GRID):
for j in range(SIN_WAVE_GRID):
x0: float = -PI + i * step; z0 = -PI + j * step; x1: float = x0 + step; z1 = z0 + step
y00: float = vipermath.sinf(x0) * vipermath.cosf(z0) * scale; y10: float = vipermath.sinf(x1) * vipermath.cosf(z0) * scale
y01: float = vipermath.sinf(x0) * vipermath.cosf(z1) * scale; y11: float = vipermath.sinf(x1) * vipermath.cosf(z1) * scale
t1: float = (y00 / scale + float(1.0)) * float(0.5); r1: int = int(50 + 205 * t1); g1: int = int(80 + 120 * vipermath.sinf(t1 * PI)); b1: int = int(255 - 205 * t1)
col1: t.CUInt32T = COLOR_RGB(255 if (r1 > 255) else (0 if (r1 < 0) else r1), 255 if (g1 > 255) else (0 if (g1 < 0) else g1), 255 if (b1 > 255) else (0 if (b1 < 0) else b1))
t2: float = (y11 / scale + float(1.0)) * float(0.5); r2: int = int(50 + 205 * t2); g2: int = int(80 + 120 * vipermath.sinf(t2 * PI)); b2: int = int(255 - 205 * t2)
col2: t.CUInt32T = COLOR_RGB(255 if (r2 > 255) else (0 if (r2 < 0) else r2), 255 if (g2 > 255) else (0 if (g2 < 0) else g2), 255 if (b2 > 255) else (0 if (b2 < 0) else b2))
v00: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y00, z0)); v10: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y10, z0))
v01: Vec3 = Mat4_MulVec(mvp, Vec3(x0, y01, z1)); v11: Vec3 = Mat4_MulVec(mvp, Vec3(x1, y11, z1))
self.renderer.DrawTriangle(v00, v10, v11, col1); self.renderer.DrawTriangle(v00, v11, v01, col2)
+504
View File
@@ -0,0 +1,504 @@
import string # std: standard
#import math # std: standard
import t, c
# 颜色格式枚举
class __ColorFormat(t.CEnum):
ColorFormat_BGRA: t.State # BGRA格式(蓝色、绿色、红色、Alpha)
ColorFormat_RGBA: t.State # RGBA格式(红色、绿色、蓝色、Alpha)
ColorFormat_BGR: t.State # BGR格式(蓝色、绿色、红色)
ColorFormat_RGB: t.State # RGB格式(红色、绿色、蓝色)
# 常量定义 - 提升代码可读性和可维护性
VGA_WIDTH: t.CDefine = 80
VGA_HEIGHT: t.CDefine = 25
FONT_WIDTH: t.CDefine = 8
FONT_HEIGHT: t.CDefine = 16
DEFAULT_CHAR: t.CDefine = 0 # 非ASCII字符的默认替换字符
SERIAL_PORT: t.CDefine = 0x3F8 # 串口调试端口地址
# 8x16字符点阵字库 (每个字符16字节,共256个字符)
font: t.CArray[t.CArray[t.CUInt8T, 16], 256] = [
# 字符编号 0x00 - 0x0F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x00
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x01
{0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x56, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x02
{0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x56, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x03
{0x00, 0x00, 0x70, 0x55, 0x55, 0x57, 0x71, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x04
{0x00, 0x00, 0x70, 0x57, 0x54, 0x57, 0x71, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x05
{0x00, 0x00, 0x70, 0x57, 0x54, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x06
{0x00, 0x00, 0x70, 0x57, 0x51, 0x52, 0x72, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x07
{0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x08
{0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x71, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x09
{0x00, 0x00, 0x70, 0x56, 0x51, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0A
{0x00, 0x00, 0x70, 0x54, 0x54, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0B
{0x00, 0x00, 0x70, 0x50, 0x50, 0x57, 0x74, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0C
{0x00, 0x00, 0x70, 0x51, 0x51, 0x57, 0x75, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0D
{0x00, 0x00, 0x70, 0x57, 0x55, 0x57, 0x74, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0E
{0x00, 0x00, 0x70, 0x53, 0x54, 0x56, 0x74, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x0F
# 字符编号 0x10 - 0x1F
{0x00, 0x00, 0x10, 0x37, 0x15, 0x15, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x10
{0x00, 0x00, 0x10, 0x32, 0x16, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x11
{0x00, 0x00, 0x20, 0x6E, 0x22, 0x2E, 0x28, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x12
{0x00, 0x00, 0x10, 0x37, 0x11, 0x13, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x13
{0x00, 0x00, 0x10, 0x35, 0x15, 0x17, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x14
{0x00, 0x00, 0x10, 0x37, 0x14, 0x17, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x15
{0x00, 0x00, 0x10, 0x37, 0x14, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x16
{0x00, 0x00, 0x10, 0x37, 0x11, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x17
{0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x18
{0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x11, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x19
{0x00, 0x00, 0x10, 0x36, 0x11, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1A
{0x00, 0x00, 0x10, 0x34, 0x14, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1B
{0x00, 0x00, 0x10, 0x30, 0x10, 0x17, 0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1C
{0x00, 0x00, 0x10, 0x31, 0x11, 0x17, 0x15, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1D
{0x00, 0x00, 0x10, 0x37, 0x15, 0x17, 0x14, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1E
{0x00, 0x00, 0x10, 0x33, 0x14, 0x16, 0x14, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x1F
# 字符编号 0x20 - 0x2F
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x20
{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x21
{0x6C, 0x6C, 0x6C, 0x6C, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x22
{0x36, 0x36, 0x36, 0x7F, 0x7F, 0x36, 0x36, 0x36, 0x7F, 0x7F, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00}, # 0x23
{0x0C, 0x0C, 0x3E, 0x7F, 0x68, 0x68, 0x3E, 0x0B, 0x0B, 0x7F, 0x3E, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x24
{0x60, 0x60, 0x66, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x66, 0x06, 0x06, 0x00, 0x00, 0x00}, # 0x25
{0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x38, 0x6C, 0x6D, 0x66, 0x66, 0x6F, 0x3B, 0x00, 0x00, 0x00}, # 0x26
{0x18, 0x18, 0x18, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x27
{0x1C, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x1C, 0x00, 0x00}, # 0x28
{0x38, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x38, 0x00, 0x00}, # 0x29
{0x00, 0x00, 0x18, 0x18, 0x7E, 0x3C, 0x3C, 0x3C, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2A
{0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x30, 0x00}, # 0x2C
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2D
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x2E
{0x00, 0x00, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x2F
# 字符编号 0x30 - 0x3F
{0x3C, 0x7E, 0x66, 0x66, 0x6E, 0x6E, 0x7E, 0x76, 0x76, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x30
{0x18, 0x38, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x31
{0x3C, 0x7E, 0x66, 0x06, 0x06, 0x0E, 0x1C, 0x38, 0x70, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x32
{0x3C, 0x7E, 0x66, 0x06, 0x06, 0x1C, 0x1C, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x33
{0x0C, 0x0C, 0x1C, 0x1C, 0x3C, 0x2C, 0x6C, 0x7E, 0x7E, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00}, # 0x34
{0x7E, 0x7E, 0x60, 0x60, 0x7C, 0x7E, 0x06, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x35
{0x3C, 0x7E, 0x66, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x36
{0x7E, 0x7E, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x37
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x38
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x39
{0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x3A
{0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x30, 0x00}, # 0x3B
{0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, 0x00, 0x00}, # 0x3C
{0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x3D
{0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00}, # 0x3E
{0x3C, 0x7E, 0x66, 0x06, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x3F
# 字符编号 0x40 - 0x4F
{0x3C, 0x7E, 0x66, 0x66, 0x6E, 0x6A, 0x6A, 0x6A, 0x6E, 0x60, 0x60, 0x7C, 0x3C, 0x00, 0x00, 0x00}, # 0x40
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x41
{0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7C, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x00, 0x00, 0x00}, # 0x42
{0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x43
{0x78, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0x7C, 0x78, 0x00, 0x00, 0x00}, # 0x44
{0x7E, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x45
{0x7E, 0x7E, 0x60, 0x60, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x46
{0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x6E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x47
{0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x48
{0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x49
{0x3E, 0x3E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x7C, 0x38, 0x00, 0x00, 0x00}, # 0x4A
{0x66, 0x66, 0x66, 0x6C, 0x6C, 0x78, 0x78, 0x78, 0x6C, 0x6C, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x4B
{0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x4C
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0x4D
{0x66, 0x66, 0x66, 0x66, 0x76, 0x76, 0x7E, 0x6E, 0x6E, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x4E
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x4F
# 字符编号 0x50 - 0x5F
{0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x50
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6A, 0x6A, 0x6C, 0x7E, 0x36, 0x00, 0x00, 0x00}, # 0x51
{0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x52
{0x3C, 0x7E, 0x66, 0x60, 0x60, 0x78, 0x3C, 0x0E, 0x06, 0x06, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x53
{0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x54
{0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x55
{0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x56
{0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x77, 0x77, 0x22, 0x00, 0x00, 0x00}, # 0x57
{0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x3C, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x58
{0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x59
{0x7E, 0x7E, 0x06, 0x06, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x5A
{0x78, 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x78, 0x00, 0x00}, # 0x5B
{0xC0, 0xC0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x0C, 0x0C, 0x06, 0x06, 0x03, 0x03, 0x00, 0x00, 0x00}, # 0x5C
{0x1E, 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x1E, 0x00, 0x00}, # 0x5D
{0x3C, 0x7E, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x5E
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00}, # 0x5F
# 字符编号 0x60 - 0x6F
{0x30, 0x38, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x60
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x61
{0x60, 0x60, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x00, 0x00, 0x00}, # 0x62
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x63
{0x06, 0x06, 0x06, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x64
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x65
{0x1C, 0x3E, 0x30, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x66
{0x00, 0x00, 0x00, 0x00, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x3E, 0x3C, 0x00}, # 0x67
{0x60, 0x60, 0x60, 0x60, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x68
{0x18, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x69
{0x0C, 0x0C, 0x00, 0x00, 0x3C, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x7C, 0x78, 0x00}, # 0x6A
{0x60, 0x60, 0x60, 0x60, 0x66, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x6C, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x6B
{0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x6C
{0x00, 0x00, 0x00, 0x00, 0x36, 0x7F, 0x6B, 0x6B, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0x6D
{0x00, 0x00, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x6E
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x6F
# 字符编号 0x70 - 0x7F
{0x00, 0x00, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x7C, 0x60, 0x60, 0x60, 0x60, 0x00}, # 0x70
{0x00, 0x00, 0x00, 0x00, 0x3E, 0x7E, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x07, 0x07, 0x00}, # 0x71
{0x00, 0x00, 0x00, 0x00, 0x6C, 0x7E, 0x76, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0x72
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x62, 0x70, 0x3C, 0x0E, 0x46, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0x73
{0x30, 0x30, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x1C, 0x00, 0x00, 0x00}, # 0x74
{0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0x75
{0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x76
{0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x7F, 0x36, 0x00, 0x00, 0x00}, # 0x77
{0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x3C, 0x3C, 0x18, 0x3C, 0x3C, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x78
{0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0x79
{0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x0C, 0x0C, 0x18, 0x30, 0x30, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0x7A
{0x0E, 0x1E, 0x18, 0x18, 0x18, 0x18, 0x30, 0x30, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x0E, 0x00, 0x00}, # 0x7B
{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00}, # 0x7C
{0x70, 0x78, 0x18, 0x18, 0x18, 0x18, 0x0C, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x78, 0x70, 0x00, 0x00}, # 0x7D
{0x31, 0x79, 0x6B, 0x4F, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x7E
{0x00, 0x00, 0x70, 0x13, 0x24, 0x26, 0x24, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x7F
# 字符编号 0x80 - 0x8F
{0x03, 0x03, 0x03, 0x03, 0x06, 0x06, 0x06, 0x06, 0x66, 0x7C, 0x3C, 0x1C, 0x0C, 0x00, 0x00, 0x00}, # 0x80
{0x1C, 0x36, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x6B, 0x7F, 0x7F, 0x77, 0x22, 0x00, 0x00, 0x00}, # 0x81
{0x1C, 0x36, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00, 0x00, 0x00}, # 0x82
{0xFE, 0x92, 0x92, 0x92, 0x92, 0x92, 0xF2, 0x82, 0x82, 0x82, 0x82, 0x82, 0xFE, 0x00, 0x00, 0x00}, # 0x83
{0x66, 0x66, 0x99, 0x99, 0x81, 0x42, 0x42, 0x42, 0x81, 0x99, 0x99, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x84
{0x18, 0x3C, 0x66, 0x00, 0x42, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0x85
{0x18, 0x3C, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0x86
{0x07, 0x01, 0x01, 0x01, 0x02, 0x02, 0x64, 0x94, 0x94, 0x90, 0x60, 0x90, 0x90, 0x90, 0x60, 0x00}, # 0x87
{0x18, 0x28, 0x28, 0x48, 0x4F, 0x81, 0x81, 0x81, 0x4F, 0x48, 0x28, 0x28, 0x18, 0x00, 0x00, 0x00}, # 0x88
{0x18, 0x14, 0x14, 0x12, 0xF2, 0x81, 0x81, 0x81, 0xF2, 0x12, 0x14, 0x14, 0x18, 0x00, 0x00, 0x00}, # 0x89
{0x3C, 0x24, 0x24, 0x24, 0x24, 0x24, 0xE7, 0x81, 0x42, 0x42, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00}, # 0x8A
{0x18, 0x24, 0x24, 0x42, 0x42, 0x81, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3C, 0x00, 0x00, 0x00}, # 0x8B
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0x00, 0x00, 0x00}, # 0x8C
{0x45, 0x4B, 0x51, 0x61, 0x61, 0x61, 0x51, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x8D
{0xC0, 0xC0, 0xCC, 0x0C, 0x18, 0x18, 0x30, 0x60, 0x60, 0xC0, 0xDB, 0x1B, 0x1B, 0x00, 0x00, 0x00}, # 0x8E
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x8F
# 字符编号 0x90 - 0x9F
{0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x90
{0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x91
{0x00, 0x00, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x30, 0x18, 0x18, 0x0C, 0x0C, 0x00, 0x00, 0x00}, # 0x92
{0x00, 0x00, 0x30, 0x30, 0x18, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x18, 0x30, 0x30, 0x00, 0x00, 0x00}, # 0x93
{0x1B, 0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x94
{0x36, 0x36, 0x36, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x95
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x36, 0x36, 0x6C, 0x6C}, # 0x96
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x97
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x98
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0x99
{0x77, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0x77, 0x00, 0x00, 0x00}, # 0x9A
{0x00, 0x00, 0x00, 0x00, 0x6E, 0xFF, 0xDB, 0xDB, 0xDF, 0xD8, 0xD8, 0xFF, 0x6E, 0x00, 0x00, 0x00}, # 0x9B
{0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, # 0x9C
{0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, # 0x9D
{0x3C, 0x7C, 0x60, 0x66, 0x66, 0xF0, 0xF6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x9E
{0x3E, 0x7E, 0x66, 0x66, 0x66, 0xF6, 0xF6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0x9F
# 字符编号 0xA0 - 0xAF
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xA0
{0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA1
{0x08, 0x08, 0x3E, 0x7F, 0x6B, 0x68, 0x68, 0x68, 0x6B, 0x7F, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00}, # 0xA2
{0x1C, 0x3E, 0x36, 0x30, 0x30, 0x7C, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xA3
{0x00, 0x00, 0x42, 0x66, 0x3C, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x3C, 0x66, 0x42, 0x00, 0x00, 0x00}, # 0xA4
{0x66, 0x66, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA5
{0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xA6
{0x3C, 0x7E, 0x60, 0x78, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x1E, 0x06, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xA7
{0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xA8
{0x3C, 0x42, 0x42, 0x81, 0x99, 0xA5, 0xA1, 0xA1, 0xA1, 0xA5, 0x99, 0x81, 0x42, 0x42, 0x3C, 0x00}, # 0xA9
{0x1C, 0x1E, 0x06, 0x06, 0x1E, 0x36, 0x36, 0x3E, 0x1E, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xAA
{0x00, 0x00, 0x33, 0x33, 0x66, 0x66, 0xCC, 0xCC, 0xCC, 0x66, 0x66, 0x33, 0x33, 0x00, 0x00, 0x00}, # 0xAB
{0x7E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAC
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAD
{0x3C, 0x42, 0x42, 0x81, 0xB9, 0xA5, 0xA5, 0xA5, 0xB9, 0xA5, 0xA5, 0x81, 0x42, 0x42, 0x3C, 0x00}, # 0xAE
{0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xAF
# 字符编号 0xB0 - 0xBF
{0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB0
{0x00, 0x00, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00}, # 0xB1
{0x38, 0x04, 0x04, 0x04, 0x18, 0x20, 0x20, 0x20, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB2
{0x38, 0x04, 0x04, 0x04, 0x18, 0x04, 0x04, 0x04, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB3
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB4
{0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x3E, 0x30, 0x60, 0x60}, # 0xB5
{0x03, 0x03, 0x3E, 0x7E, 0x76, 0x76, 0x76, 0x36, 0x36, 0x36, 0x36, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xB6
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB7
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x18, 0x30, 0x00}, # 0xB8
{0x10, 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, # 0xB9
{0x1C, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x00}, # 0xBA
{0x00, 0x00, 0x88, 0xCC, 0x66, 0x66, 0x33, 0x33, 0x33, 0x66, 0x66, 0xCC, 0x88, 0x00, 0x00, 0x00}, # 0xBB
{0x40, 0x40, 0xC0, 0x40, 0x40, 0x40, 0x48, 0x48, 0x48, 0x08, 0x0A, 0x0A, 0x0F, 0x02, 0x02, 0x00}, # 0xBC
{0x40, 0x40, 0xC0, 0x40, 0x40, 0x40, 0x4F, 0x41, 0x41, 0x01, 0x0F, 0x08, 0x08, 0x08, 0x0F, 0x00}, # 0xBD
{0xE0, 0x20, 0x20, 0x20, 0xE0, 0x20, 0x28, 0x28, 0xE8, 0x08, 0x0A, 0x0A, 0x0F, 0x02, 0x02, 0x00}, # 0xBE
{0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x30, 0x60, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xBF
# 字符编号 0xC0 - 0xCF
{0x30, 0x38, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC0
{0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC1
{0x18, 0x3C, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC2
{0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC3
{0x66, 0x66, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC4
{0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xC5
{0x3F, 0x7F, 0x6C, 0x6C, 0x6C, 0x7F, 0x7F, 0x6C, 0x6C, 0x6C, 0x6C, 0x6F, 0x6F, 0x00, 0x00, 0x00}, # 0xC6
{0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x18, 0x30, 0x30, 0x60, 0x00}, # 0xC7
{0x30, 0x38, 0x18, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xC8
{0x0C, 0x1C, 0x18, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xC9
{0x3C, 0x7E, 0x66, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCA
{0x66, 0x66, 0x00, 0x00, 0x7E, 0x7E, 0x60, 0x7C, 0x7C, 0x60, 0x60, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCB
{0x30, 0x38, 0x18, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCC
{0x0C, 0x1C, 0x18, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCD
{0x3C, 0x7E, 0x66, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCE
{0x66, 0x66, 0x66, 0x00, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xCF
# 字符编号 0xD0 - 0xDF
{0x78, 0x7C, 0x6C, 0x66, 0x66, 0xF6, 0xF6, 0x66, 0x66, 0x66, 0x6C, 0x7C, 0x78, 0x00, 0x00, 0x00}, # 0xD0
{0x36, 0x7E, 0x6C, 0x00, 0x66, 0x66, 0x76, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xD1
{0x30, 0x38, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD2
{0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD3
{0x3C, 0x7E, 0x66, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD4
{0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD5
{0x66, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD6
{0x00, 0x00, 0x63, 0x63, 0x36, 0x36, 0x1C, 0x08, 0x1C, 0x36, 0x36, 0x63, 0x63, 0x00, 0x00, 0x00}, # 0xD7
{0x3D, 0x7F, 0x66, 0x66, 0x6E, 0x6E, 0x7E, 0x76, 0x76, 0x66, 0x66, 0x7E, 0xBC, 0x80, 0x00, 0x00}, # 0xD8
{0x30, 0x38, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xD9
{0x0C, 0x1C, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDA
{0x3C, 0x7E, 0x66, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDB
{0x66, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xDC
{0x0C, 0x1C, 0x18, 0x00, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00}, # 0xDD
{0xF0, 0xF0, 0x60, 0x78, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x60, 0xF0, 0xF0, 0x00, 0x00, 0x00}, # 0xDE
{0x3C, 0x7E, 0x66, 0x66, 0x66, 0x6C, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x6E, 0x6C, 0x60, 0xC0, 0x00}, # 0xDF
# 字符编号 0xE0 - 0xEF
{0x30, 0x38, 0x18, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE0
{0x0C, 0x1C, 0x18, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE1
{0x18, 0x3C, 0x66, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE2
{0x36, 0x7E, 0x6C, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE3
{0x66, 0x66, 0x00, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE4
{0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x3E, 0x06, 0x3E, 0x7E, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE5
{0x00, 0x00, 0x00, 0x00, 0x3E, 0x3F, 0x0B, 0x3B, 0x7F, 0x6F, 0x68, 0x7F, 0x3F, 0x00, 0x00, 0x00}, # 0xE6
{0x00, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0x60, 0x60, 0x66, 0x7E, 0x3C, 0x10, 0x60, 0x00}, # 0xE7
{0x30, 0x38, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE8
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xE9
{0x3C, 0x7E, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xEA
{0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x7E, 0x7E, 0x60, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xEB
{0x30, 0x38, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEC
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xED
{0x3C, 0x7E, 0x66, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEE
{0x66, 0x66, 0x00, 0x00, 0x00, 0x38, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x7E, 0x00, 0x00, 0x00}, # 0xEF
# 字符编号 0xF0 - 0xFF
{0x18, 0x18, 0x7E, 0x7E, 0x0C, 0x06, 0x06, 0x3E, 0x7E, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF0
{0x36, 0x7E, 0x6C, 0x00, 0x00, 0x7C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00}, # 0xF1
{0x30, 0x38, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF2
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF3
{0x3C, 0x7E, 0x66, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF4
{0x36, 0x7E, 0x6C, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF5
{0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3C, 0x00, 0x00, 0x00}, # 0xF6
{0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0xFF, 0xFF, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00}, # 0xF7
{0x00, 0x00, 0x00, 0x02, 0x02, 0x3C, 0x7E, 0x6E, 0x76, 0x76, 0x66, 0x7E, 0xBC, 0x80, 0x00, 0x00}, # 0xF8
{0x30, 0x38, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xF9
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFA
{0x18, 0x3C, 0x66, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFB
{0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x00, 0x00, 0x00}, # 0xFC
{0x0C, 0x1C, 0x18, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0xFD
{0x60, 0x60, 0x60, 0x78, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00}, # 0xFE
{0x66, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7E, 0x3E, 0x06, 0x06, 0x7E, 0x7C, 0x00}, # 0xFF
]
@t.Object
class _VGAScreenDriver:
# 帧缓冲区信息
fbv: t.CVoid | t.CPtr # = None
fb: t.CUnsignedChar | t.CPtr # = None
fw: int # = 800
fh: int # = 600
fp: int # = 800 * 4 # 3200 800*4
ColorFormat: __ColorFormat # = __ColorFormat.ColorFormat_BGRA # 默认使用BGRA格式
BgColor: t.CUnsignedInt = 0xFF000080 # RGBA: 不透明深蓝色
# 初始化VGA驱动
def __init__(self, fb: t.CVoid | t.CPtr, width: int, height: int, pitch: int, format: int):
# 先做参数合法性检查
if fb is None or width <= 0 or height <= 0 or pitch <= 0: return
# 更新帧缓冲区参数
self.fbv = fb
self.fb = t.CUnsignedChar(fb, t.CPtr)
self.fw = width
self.fh = height
self.fp = pitch
# 设置颜色格式
match format:
case 0: self.ColorFormat = __ColorFormat.ColorFormat_RGBA
case 1: self.ColorFormat = __ColorFormat.ColorFormat_BGRA
case 2: pass
case 3: pass
case _: self.ColorFormat = __ColorFormat.ColorFormat_BGRA
# 检测硬件实际颜色格式(覆盖传入的format)
self.DetectColorFormat()
# 初始化背景颜色
self.ClearScreen(self.BgColor)
# 检测硬件颜色格式 - 优化测试逻辑,避免误判
def DetectColorFormat(self) -> t.CStatic | t.CVoid:
if self.fb is None: return
fb: t.CUnsignedChar | t.CPtr = t.CUnsignedChar(self.fb, t.CPtr)
testX: int = 0
testY: int = 0
offset: int = testY * self.fp + testX * 4
# 保存测试位置原始像素值,避免污染屏幕
OriginalPixel: t.CUInt32T = c.Deref(t.CUInt32T(fb + offset, t.CPtr))
# 写入测试颜色:青色 (RGBA逻辑值: 0xFF00FFFF)
fb[offset + 0] = 0xFF # B
fb[offset + 1] = 0xFF # G
fb[offset + 2] = 0x00 # R
fb[offset + 3] = 0xFF # A
# 读取回测试像素
b: t.CUnsignedChar = fb[offset + 0]
g: t.CUnsignedChar = fb[offset + 1]
r: t.CUnsignedChar = fb[offset + 2]
# t.CVoid(fb[offset + 3]) # 忽略Alpha
# 恢复原始像素
c.Set(c.Deref(t.CUInt32T(fb + offset, t.CPtr)), OriginalPixel)
# 判定颜色格式
if r == 0x00 and g == 0xFF and b == 0xFF: self.ColorFormat = __ColorFormat.ColorFormat_BGRA
elif r == 0xFF and g == 0xFF and b == 0x00: self.ColorFormat = __ColorFormat.ColorFormat_RGBA
else: self.ColorFormat = __ColorFormat.ColorFormat_BGRA
# 设置像素颜色 - 统一接口,输入为RGBA逻辑颜色
# color格式: 0xAARRGGBB (Alpha, Red, Green, Blue)
def SetPixel(self, x: int, y: int, color: t.CUInt32T):
fb: t.CUnsignedChar | t.CPtr = self.fb
if fb is None: return
if x < 0 or x >= self.fw or y < 0 or y >= self.fh: return
offset: int = y * self.fp + x * 4
# 解析RGBA分量
r: t.CUnsignedChar = (color >> 16) & 0xFF
g: t.CUnsignedChar = (color >> 8) & 0xFF
b: t.CUnsignedChar = color & 0xFF
a: t.CUnsignedChar = (color >> 24) & 0xFF
# 根据硬件格式写入像素
match self.ColorFormat:
case __ColorFormat.ColorFormat_BGRA:
self.fb[offset + 0] = b
fb[offset + 1] = g
fb[offset + 2] = r
fb[offset + 3] = a
case __ColorFormat.ColorFormat_RGBA:
fb[offset + 0] = r
fb[offset + 1] = g
fb[offset + 2] = b
fb[offset + 3] = a
case __ColorFormat.ColorFormat_BGR:
fb[offset + 0] = b
fb[offset + 1] = g
fb[offset + 2] = r
case __ColorFormat.ColorFormat_RGB:
fb[offset + 0] = r
fb[offset + 1] = g
fb[offset + 2] = b
case _:
# 降级到BGRA
fb[offset + 0] = b
fb[offset + 1] = g
fb[offset + 2] = r
fb[offset + 3] = a
# 清除屏幕 - 优化循环效率,减少函数调用开销
def ClearScreen(self, color: t.CUInt32T):
fb: t.CUnsignedChar | t.CPtr = self.fb
fp: int = self.fp
if fb is None: return
PixelSize: int = 3 if self.ColorFormat == __ColorFormat.ColorFormat_BGR or self.ColorFormat == __ColorFormat.ColorFormat_RGB else 4
RowSize: int = self.fw * PixelSize
# 解析清除颜色的RGBA分量
r: t.CUnsignedChar = (color >> 16) & 0xFF
g: t.CUnsignedChar = (color >> 8 ) & 0xFF
b: t.CUnsignedChar = (color) & 0xFF
a: t.CUnsignedChar = (color >> 24) & 0xFF
# 逐行填充,比逐像素调用SetPixel快得多
for y in range(self.fh):
row_ptr: t.CUnsignedChar | t.CPtr = fb + y * fp
for x in range(self.fw):
offset: int = x * PixelSize
match self.ColorFormat:
case __ColorFormat.ColorFormat_BGRA:
row_ptr[offset + 0] = b
row_ptr[offset + 1] = g
row_ptr[offset + 2] = r
row_ptr[offset + 3] = a
case __ColorFormat.ColorFormat_RGBA:
row_ptr[offset + 0] = r
row_ptr[offset + 1] = g
row_ptr[offset + 2] = b
row_ptr[offset + 3] = a
case __ColorFormat.ColorFormat_BGR:
row_ptr[offset + 0] = b
row_ptr[offset + 1] = g
row_ptr[offset + 2] = r
case __ColorFormat.ColorFormat_RGB:
row_ptr[offset + 0] = r
row_ptr[offset + 1] = g
row_ptr[offset + 2] = b
def Print(self, x: int, y: int, s: str, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, size: int = 16):
self.PrintColor(x, y, s, fontcolor, 0x00000000, size)
def PutcharColor(self, x: int, y: int, cr: t.CChar, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, bgcolor: t.CUnsignedInt = 0x00000000, size: int = 16):
fw: int = self.fw
fh: int = self.fh
fb: t.CUnsignedChar | t.CPtr = self.fb
if fb is None: return
# 提前判断字符整体是否越界(8x16),避免内层循环冗余检查
if x < 0 or y < 0 or (x + FONT_WIDTH) > fw or (y + FONT_HEIGHT) > fh: return
# 处理字符范围,非ASCII字符替换为默认字符
ch: t.CUnsignedChar = t.CUnsignedChar(cr)
if ch >= 128: ch = DEFAULT_CHAR
# 预解析背景色Alpha,避免内层循环重复计算
bg_alpha: t.CUInt8T = (bgcolor >> 24) & 0xFF
draw_bg: int = (bg_alpha != 0) # 背景是否需要绘制
# 渲染8x16点阵字符
for fy in range(FONT_HEIGHT):
row: t.CUnsignedChar = font[ch][fy]
for fx in range(FONT_WIDTH):
px: int = x + fx
py: int = y + fy
# 检测当前点阵位是否点亮
if row & (0x80 >> fx):
# 绘制前景色
self.SetPixel(px, py, fontcolor)
elif draw_bg:
# 仅当背景不透明时绘制背景色
self.SetPixel(px, py, bgcolor)
# 在指定位置显示带颜色和字号的字符串
def PrintColor(self, x: int, y: int, s: str, fontcolor: t.CUnsignedInt = 0xFFFFFFFF, bgcolor: t.CUnsignedInt = 0x00000000, size: int = 16):
fw: int = self.fw
fh: int = self.fh
# fb: t.CUnsignedChar | t.CPtr = self.fb
currentX: int = x
currentY: int = y
if s is None: return
# 逐字符处理
for _s in s:
if _s == '\n':
# 换行:重置X,Y增加字符高度
currentX = x
currentY += size + 2 # 字体大小 + 2像素行间距
# 检查换行后是否越界
if currentY + size > fh: break
else:
# 显示普通字符
self.PutcharColor(currentX, currentY, _s, fontcolor, bgcolor, size)
# 计算字符宽度
CharWidth: int = size / 2 + 1 # 简单估算字符宽度
currentX += CharWidth
# 检查横向是否越界
if currentX + CharWidth > fw:
# 自动换行
currentX = x
currentY += size + 2
if currentY + size > fh: break
+757
View File
@@ -0,0 +1,757 @@
import drivers.serial.uart.serial as serial
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import drivers.core.cpu.cpu as cpu
import mm.mm as mm
import paging.paging as paging
import sched.process as proc
import sched.sched as sched
import intr.gdt as gdt
# Do NOT import KERN_STACK_SIZE/USER_STACK_SIZE from process - CDefine cross-module import is broken
# Redefine them here to ensure correct values
KERN_STACK_SIZE: t.CDefine = 8192
USER_STACK_SIZE: t.CDefine = 65536
import viperlib
import string
import asm
import t, c
ELFMAG0: t.CDefine = 0x7F
ELFMAG1: t.CDefine = 0x45
ELFMAG2: t.CDefine = 0x4C
ELFMAG3: t.CDefine = 0x46
ELFCLASS64: t.CDefine = 2
ELFDATA2LSB: t.CDefine = 1
ET_EXEC: t.CDefine = 2
ET_DYN: t.CDefine = 3
EM_X86_64: t.CDefine = 62
PT_LOAD: t.CDefine = 1
PT_DYNAMIC: t.CDefine = 2
PF_X: t.CDefine = 1
PF_W: t.CDefine = 2
PF_R: t.CDefine = 4
DT_NULL: t.CDefine = 0
DT_HASH: t.CDefine = 4
DT_STRTAB: t.CDefine = 5
DT_SYMTAB: t.CDefine = 6
DT_STRSZ: t.CDefine = 10
DT_SYMENT: t.CDefine = 11
DT_RELA: t.CDefine = 7
DT_RELASZ: t.CDefine = 8
DT_RELAENT: t.CDefine = 9
DT_PLTRELSZ: t.CDefine = 2
DT_JMPREL: t.CDefine = 23
R_X86_64_64: t.CDefine = 1
R_X86_64_GLOB_DAT: t.CDefine = 6
R_X86_64_JUMP_SLOT: t.CDefine = 7
R_X86_64_RELATIVE: t.CDefine = 8
STB_GLOBAL: t.CDefine = 1
STB_WEAK: t.CDefine = 2
@c.Attribute(t.attr.packed)
class Elf64_Ehdr:
e_ident: t.CArray[t.CUInt8T, 16]
e_type: t.CUInt16T
e_machine: t.CUInt16T
e_version: t.CUInt32T
e_entry: t.CUInt64T
e_phoff: t.CUInt64T
e_shoff: t.CUInt64T
e_flags: t.CUInt32T
e_ehsize: t.CUInt16T
e_phentsize: t.CUInt16T
e_phnum: t.CUInt16T
e_shentsize: t.CUInt16T
e_shnum: t.CUInt16T
e_shstrndx: t.CUInt16T
@c.Attribute(t.attr.packed)
class Elf64_Phdr:
p_type: t.CUInt32T
p_flags: t.CUInt32T
p_offset: t.CUInt64T
p_vaddr: t.CUInt64T
p_paddr: t.CUInt64T
p_filesz: t.CUInt64T
p_memsz: t.CUInt64T
p_align: t.CUInt64T
MAX_PHDRS: t.CDefine = 16
@c.Attribute(t.attr.packed)
class Elf64_Shdr:
sh_name: t.CUInt32T
sh_type: t.CUInt32T
sh_flags: t.CUInt64T
sh_addr: t.CUInt64T
sh_offset: t.CUInt64T
sh_size: t.CUInt64T
sh_link: t.CUInt32T
sh_info: t.CUInt32T
sh_addralign: t.CUInt64T
sh_entsize: t.CUInt64T
@c.Attribute(t.attr.packed)
class Elf64_Sym:
st_name: t.CUInt32T
st_info: t.CUInt8T
st_other: t.CUInt8T
st_shndx: t.CUInt16T
st_value: t.CUInt64T
st_size: t.CUInt64T
@c.Attribute(t.attr.packed)
class Elf64_Rela:
r_offset: t.CUInt64T
r_info: t.CUInt64T
r_addend: t.CInt64T
@c.Attribute(t.attr.packed)
class Elf64_Dyn:
d_tag: t.CInt64T
d_val: t.CUInt64T
MAX_SHDRS: t.CDefine = 64
MAX_DYNS: t.CDefine = 64
MAX_RELAS: t.CDefine = 256
def load_elf(path: t.CConst | str, do_map: t.CInt = 1) -> t.CVoid | t.CPtr:
fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ)
if fp is None:
err: t.CUInt32T = fat32.last_error()
eb: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(eb), 64, "[elf] failed to open file err=%d\n", err)
serial.puts(eb)
return None
ehdr_buf: t.CArray[t.CUInt8T, 64]
string.memset(c.Addr(ehdr_buf), 0, 64)
read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64)
if read_len < 64:
serial.puts("[elf] failed to read ehdr\n")
fat32.close(fp)
return None
ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf)
if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3:
serial.puts("[elf] bad magic\n")
fat32.close(fp)
return None
if ehdr.e_ident[4] != ELFCLASS64:
serial.puts("[elf] not ELF64\n")
fat32.close(fp)
return None
if ehdr.e_ident[5] != ELFDATA2LSB:
serial.puts("[elf] not little-endian\n")
fat32.close(fp)
return None
if ehdr.e_type != ET_EXEC and ehdr.e_type != ET_DYN:
serial.puts("[elf] not ET_EXEC or ET_DYN\n")
fat32.close(fp)
return None
if ehdr.e_machine != EM_X86_64:
serial.puts("[elf] not x86_64\n")
fat32.close(fp)
return None
entry_point: t.CUInt64T = ehdr.e_entry
phoff: t.CUInt64T = ehdr.e_phoff
phentsize: t.CUInt16T = ehdr.e_phentsize
phnum: t.CUInt16T = ehdr.e_phnum
if phentsize == 0 or phnum == 0 or phnum > MAX_PHDRS:
serial.puts("[elf] invalid phdr count\n")
fat32.close(fp)
return None
cr: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(cr), 80, "[elf] entry=0x%lx phoff=%lu phentsize=%u phnum=%u\n", entry_point, phoff, phentsize, phnum)
serial.puts(cr)
phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS]
string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56)
for pi in range(phnum):
phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize)
fat32.seek(fp, t.CUInt32T(phdr_off))
fat32.read(fp, c.Addr(phdr_bufs[pi]), 56)
found_load: t.CInt = 0
lowest_vaddr: t.CUInt64T = 0
highest_vaddr_end: t.CUInt64T = 0
for pi in range(phnum):
if phdr_bufs[pi].p_type == PT_LOAD:
seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz
if found_load == 0:
lowest_vaddr = phdr_bufs[pi].p_vaddr
highest_vaddr_end = seg_end
found_load = 1
else:
if phdr_bufs[pi].p_vaddr < lowest_vaddr:
lowest_vaddr = phdr_bufs[pi].p_vaddr
if seg_end > highest_vaddr_end:
highest_vaddr_end = seg_end
if found_load == 0:
serial.puts("[elf] no LOAD segments\n")
fat32.close(fp)
return None
total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr
total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000
cr2: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(cr2), 80, "[elf] vaddr 0x%lx-0x%lx pages=%lu\n", lowest_vaddr, highest_vaddr_end, total_pages)
serial.puts(cr2)
raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000)
_db_ra: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(_db_ra), 80, "[elf] malloc ret raw_alloc=0x%lx\n", t.CUInt64T(raw_alloc))
serial.puts(_db_ra)
if not raw_alloc:
serial.puts("[elf] malloc failed\n")
fat32.close(fp)
return None
phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + t.CUInt64T(0xFFF)) & ~t.CUInt64T(0xFFF)
phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr)
string.memset(phys_base, 0, total_pages * 0x1000)
for pi in range(phnum):
if phdr_bufs[pi].p_type == PT_LOAD:
vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr
file_off: t.CUInt64T = phdr_bufs[pi].p_offset
filesz: t.CUInt64T = phdr_bufs[pi].p_filesz
memsz: t.CUInt64T = phdr_bufs[pi].p_memsz
dest_off: t.CUInt64T = vaddr - lowest_vaddr
dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr)
if filesz > 0:
fat32.seek(fp, t.CUInt32T(file_off))
fat32.read(fp, dest, t.CUInt32T(filesz))
if memsz > filesz:
bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr)
bss_len: t.CUInt64T = memsz - filesz
string.memset(bss_start, 0, bss_len)
fat32.close(fp)
virt_page_start: t.CUInt64T = lowest_vaddr & ~t.CUInt64T(0xFFF)
phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF)
map_end: t.CUInt64T = (highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF)
num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000
# Store mapping info for spawn_elf (used when do_map=0)
global _last_elf_phys_page_start, _last_elf_virt_page_start, _last_elf_num_map_pages
_last_elf_phys_page_start = phys_page_start
_last_elf_virt_page_start = virt_page_start
_last_elf_num_map_pages = num_map_pages
if do_map != 0:
for mi in range(num_map_pages):
virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000
phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000
page_flags: t.CUInt64T = paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER
paging.MapPage(virt_addr, phys_addr, page_flags)
entry_addr: t.CVoid | t.CPtr = t.CVoid(entry_point, t.CPtr)
return entry_addr
def run_elf(path: t.CConst | str) -> t.CInt:
entry: t.CVoid | t.CPtr = load_elf(path)
if entry is None:
serial.puts("[elf] load failed\n")
return -1
serial.puts("[elf] jumping to entry...\n")
c.Asm(f"""call {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
serial.puts("[elf] returned from entry\n")
return 0
MAX_SPAWNED: t.CDefine = 8
_elf_entries: t.CArray[t.CVoid | t.CPtr, MAX_SPAWNED]
# User stack virtual address: top of 48-bit user space, 64KB below
USER_STACK_VTOP: t.CDefine = 0x7FFFFFE00000
# Globals for ELF mapping info (set by load_elf when do_map=0, used by spawn_elf
# to map ELF pages in the cloned PML4 instead of kernel PML4).
# ROOT CAUSE: previously load_elf mapped ELF vaddr (e.g. 0x400000) into the
# kernel PML4, overwriting the identity mapping of buddy system memory_block
# headers at 0x400000. This corrupted buddy metadata -> malloc failures,
# pid=0, and #GP in split_block. Fix: load_elf does NOT map; spawn_elf maps
# in the cloned PML4 after clone_for_process.
_last_elf_phys_page_start: t.CUInt64T = 0
_last_elf_virt_page_start: t.CUInt64T = 0
_last_elf_num_map_pages: t.CUInt64T = 0
def _elf_entry_wrapper(arg: t.CVoid | t.CPtr) -> t.CInt:
pid: t.CInt = t.CInt(t.CUInt64T(arg))
_db1: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(_db1), 64, "[elf] wrapper pid=%d\n", pid)
serial.puts(_db1)
# Disable interrupts around switch_pid + MapPage + TSS setup: @pml4 is a global
# that switch_pid modifies. A timer interrupt mid-section would make the scheduler
# call switch_pid for another thread, corrupting @pml4 for our MapPage calls.
asm.cli()
proc.switch_pid(pid)
cur_p: proc.Process | t.CPtr = proc.ProcessManager.current()
entry_fn: t.CVoid | t.CPtr = cur_p.entry
if entry_fn is not None:
user_stack_vtop: t.CUInt64T = USER_STACK_VTOP
us_sz: t.CUInt64T = USER_STACK_SIZE
user_stack_vbottom: t.CUInt64T = USER_STACK_VTOP - us_sz
# Physical pages of the user stack (allocated by mm.malloc, identity-mapped)
us_size: t.CUInt64T = USER_STACK_SIZE
phys_bottom: t.CUInt64T = (cur_p.user_stack - us_size) & ~t.CUInt64T(0xFFF)
# Map each page of the user stack at the virtual address with PTE_USER
num_stack_pages: t.CUInt64T = 16 # USER_STACK_SIZE(65536) / PAGE_SIZE(4096) = 16
si: t.CUInt64T
for si in range(num_stack_pages):
vaddr: t.CUInt64T = (user_stack_vbottom & ~t.CUInt64T(0xFFF)) + si * t.CUInt64T(0x1000)
paddr: t.CUInt64T = phys_bottom + si * t.CUInt64T(0x1000)
paging.MapPage(vaddr, paddr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
# Set TSS RSP0 so interrupts from Ring 3 use kernel stack
gdt.set_tss_rsp0(cur_p.kern_stack)
# Set GS kernel_rsp0 for syscall entry from Ring 3
cpu.set_kernel_rsp0(cur_p.kern_stack)
# Keep interrupts disabled until iretq: drop_to_user_mode's iretq
# will set RFLAGS.IF from the saved frame (0x202) for user mode.
# This prevents timer interrupt preemption between TSS setup and iretq.
# Drop to Ring 3 user mode via iretq
proc.drop_to_user_mode(entry_fn, user_stack_vtop, cur_p.kern_stack)
cur: proc.Process | t.CPtr = proc.ProcessManager.current()
cur.exit()
return 0
def spawn_elf(path: t.CConst | str) -> t.CInt:
fat32.lock()
name_start: t.CInt = 0
si: t.CInt = 0
while True:
ch: t.CChar = path[si]
if ch == 0: break
if ch == 47:
name_start = si + 1
si += 1
lowest_vaddr: t.CUInt64T = 0
highest_vaddr_end: t.CUInt64T = 0
fp_scan: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ)
if fp_scan is None:
serr: t.CUInt32T = fat32.last_error()
seb: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(seb), 64, "[elf] spawn scan open failed err=%d\n", serr)
serial.puts(seb)
fat32.unlock()
return -1
if fp_scan is not None:
ehdr_buf_s: t.CArray[t.CUInt8T, 64]
string.memset(c.Addr(ehdr_buf_s), 0, 64)
fat32.read(fp_scan, c.Addr(ehdr_buf_s), 64)
ehdr_s: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf_s)
if ehdr_s.e_type == ET_EXEC or ehdr_s.e_type == ET_DYN:
phoff_s: t.CUInt64T = ehdr_s.e_phoff
phentsize_s: t.CUInt16T = ehdr_s.e_phentsize
phnum_s: t.CUInt16T = ehdr_s.e_phnum
if phentsize_s > 0 and phnum_s > 0 and phnum_s <= MAX_PHDRS:
phdr_bufs_s: t.CArray[Elf64_Phdr, MAX_PHDRS]
string.memset(c.Addr(phdr_bufs_s), 0, MAX_PHDRS * 56)
found_load_s: t.CInt = 0
for pi_s in range(phnum_s):
fat32.seek(fp_scan, t.CUInt32T(phoff_s + t.CUInt64T(pi_s) * t.CUInt64T(phentsize_s)))
fat32.read(fp_scan, c.Addr(phdr_bufs_s[pi_s]), 56)
if phdr_bufs_s[pi_s].p_type == PT_LOAD:
seg_end_s: t.CUInt64T = phdr_bufs_s[pi_s].p_vaddr + phdr_bufs_s[pi_s].p_memsz
if found_load_s == 0:
lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr
highest_vaddr_end = seg_end_s
found_load_s = 1
else:
if phdr_bufs_s[pi_s].p_vaddr < lowest_vaddr:
lowest_vaddr = phdr_bufs_s[pi_s].p_vaddr
if seg_end_s > highest_vaddr_end:
highest_vaddr_end = seg_end_s
fat32.close(fp_scan)
isolate_va: t.CUInt64T = lowest_vaddr
if isolate_va == 0:
isolate_va = 0x400000
# Load ELF to physical memory WITHOUT mapping in kernel PML4.
# do_map=0: load_elf stores mapping info in globals for spawn_elf to use.
# ROOT CAUSE FIX: previously load_elf mapped ELF vaddr (e.g. 0x400000) into
# kernel PML4, overwriting identity mapping of buddy memory_block headers.
entry: t.CVoid | t.CPtr = load_elf(path, 0)
if entry is None:
serial.puts("[elf] spawn: load failed\n")
fat32.unlock()
return -1
# Clone kernel PML4 (identity mapping intact, no ELF mapping yet)
proc_pml4: t.CUInt64T = paging.clone_for_process(isolate_va)
slog1: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(slog1), 80, "[elf] spawn: clone pml4=0x%lx va=0x%lx\n", proc_pml4, isolate_va)
serial.puts(slog1)
if proc_pml4 == 0:
serial.puts("[elf] spawn: clone_for_process failed\n")
fat32.unlock()
return -1
# Map ELF pages in the CLONED PML4 (not kernel's).
# This prevents overwriting kernel identity mapping at 0x400000 which
# would corrupt buddy system memory_block headers.
# Must disable interrupts: @pml4 global is used by MapPage and a timer
# interrupt mid-section would call switch_pid, corrupting @pml4.
asm.cli()
_kernel_cr3: t.CUInt64T = 0
c.Asm(f"""mov {c.AsmOut(_kernel_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3""", op=[t.ASM_DESCR.CLOBBER_MEMORY])
c.Asm(f"mov cr3, {c.AsmInp(proc_pml4, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(proc_pml4)
mi: t.CUInt64T
for mi in range(_last_elf_num_map_pages):
_va: t.CUInt64T = _last_elf_virt_page_start + mi * 0x1000
_pa: t.CUInt64T = _last_elf_phys_page_start + mi * 0x1000
paging.MapPage(_va, _pa, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
c.Asm(f"mov cr3, {c.AsmInp(_kernel_cr3, t.ASM_DESCR.REG_ANY)}", op=[t.ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(_kernel_cr3)
asm.sti()
p: proc.Process | t.CPtr = proc.ProcessManager.create_process(path[name_start:], entry)
if p is None:
serial.puts("[elf] spawn: create_process failed\n")
fat32.unlock()
return -2
p.elf_base = t.CUInt64T(entry)
p.pml4_root = proc_pml4
if p.pid >= 0 and p.pid < MAX_SPAWNED:
_elf_entries[p.pid] = entry
th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(_elf_entry_wrapper, t.CVoid(t.CUInt64T(p.pid), t.CPtr), p.pid)
if th is None:
serial.puts("[elf] spawn: create_thread failed\n")
p.exit()
fat32.unlock()
return -3
tid: t.CInt = th.tid
p.addThread(tid)
slog: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(slog), 80, "[elf] spawn: pid=%d tid=%d entry=0x%lx pml4=0x%lx\n", p.pid, tid, t.CUInt64T(entry), p.pml4_root)
serial.puts(slog)
fat32.unlock()
return p.pid
def _resolve_symbol(name: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr:
nm: t.CInt = 0
while c.Deref(name + nm) != 0:
nm += 1
cmp_name: t.CConst | t.CChar | t.CPtr = name
cmp_len: t.CInt = nm
if nm > 9:
sha1_len2: t.CInt = 0
for si2 in range(nm):
ch2: t.CChar = c.Deref(name + si2)
if ch2 == 46:
if sha1_len2 >= 8:
cmp_name = t.CConst(t.CUInt64T(name) + si2 + 1, t.CPtr)
cmp_len = nm - si2 - 1
break
if ch2 == 0:
break
if (ch2 >= 48 and ch2 <= 57) or (ch2 >= 97 and ch2 <= 102):
sha1_len2 += 1
else:
break
if cmp_len == 11:
match: t.CInt = 1
for i in range(cmp_len):
if c.Deref(cmp_name + i) != c.Deref("serial_puts" + i):
match = 0
if match:
return t.CVoid(c.Addr(serial.puts), t.CPtr)
return None
_so_strtab: t.CVoid | t.CPtr
_so_symtab: t.CVoid | t.CPtr
_so_syment: t.CUInt64T
_so_sym_count: t.CInt
def load_so(path: t.CConst | str) -> t.CVoid | t.CPtr:
fat32.lock()
fp: fat32_types.fileobj | t.CPtr = fat32.open(path, fat32_types.FA_READ)
if fp is None:
serial.puts("[so] failed to open file\n")
fat32.unlock()
return None
ehdr_buf: t.CArray[t.CUInt8T, 64]
string.memset(c.Addr(ehdr_buf), 0, 64)
read_len: t.CUInt32T = fat32.read(fp, c.Addr(ehdr_buf), 64)
if read_len < 64:
serial.puts("[so] failed to read ehdr\n")
fat32.close(fp)
fat32.unlock()
return None
ehdr: Elf64_Ehdr | t.CPtr = c.Addr(ehdr_buf)
if ehdr.e_ident[0] != ELFMAG0 or ehdr.e_ident[1] != ELFMAG1 or ehdr.e_ident[2] != ELFMAG2 or ehdr.e_ident[3] != ELFMAG3:
serial.puts("[so] bad magic\n")
fat32.close(fp)
fat32.unlock()
return None
if ehdr.e_ident[4] != ELFCLASS64:
serial.puts("[so] not ELF64\n")
fat32.close(fp)
fat32.unlock()
return None
if ehdr.e_type == ET_DYN:
pass
elif ehdr.e_type == ET_EXEC:
pass
else:
serial.puts("[so] not ET_DYN or ET_EXEC\n")
fat32.close(fp)
fat32.unlock()
return None
phoff: t.CUInt64T = ehdr.e_phoff
phentsize: t.CUInt16T = ehdr.e_phentsize
phnum: t.CUInt16T = ehdr.e_phnum
phdr_bufs: t.CArray[Elf64_Phdr, MAX_PHDRS]
string.memset(c.Addr(phdr_bufs), 0, MAX_PHDRS * 56)
for pi in range(phnum):
phdr_off: t.CUInt64T = phoff + t.CUInt64T(pi) * t.CUInt64T(phentsize)
fat32.seek(fp, t.CUInt32T(phdr_off))
fat32.read(fp, c.Addr(phdr_bufs[pi]), 56)
found_load: t.CInt = 0
lowest_vaddr: t.CUInt64T = 0
highest_vaddr_end: t.CUInt64T = 0
for pi in range(phnum):
if phdr_bufs[pi].p_type == PT_LOAD:
seg_end: t.CUInt64T = phdr_bufs[pi].p_vaddr + phdr_bufs[pi].p_memsz
if found_load == 0:
lowest_vaddr = phdr_bufs[pi].p_vaddr
highest_vaddr_end = seg_end
found_load = 1
else:
if phdr_bufs[pi].p_vaddr < lowest_vaddr:
lowest_vaddr = phdr_bufs[pi].p_vaddr
if seg_end > highest_vaddr_end:
highest_vaddr_end = seg_end
if found_load == 0:
serial.puts("[so] no LOAD segments\n")
fat32.close(fp)
fat32.unlock()
return None
total_memsz: t.CUInt64T = highest_vaddr_end - lowest_vaddr
total_pages: t.CUInt64T = (total_memsz + 0xFFF) // 0x1000
so_load_addr: t.CUInt64T = 0x600000
raw_alloc: t.CVoid | t.CPtr = mm.malloc(total_pages * 0x1000 + 0x1000)
if not raw_alloc:
serial.puts("[so] malloc failed\n")
fat32.close(fp)
fat32.unlock()
return None
phys_base_aligned: t.CUInt64T = (t.CUInt64T(raw_alloc) + 0xFFF) & ~t.CUInt64T(0xFFF)
phys_base: t.CVoid | t.CPtr = t.CVoid(phys_base_aligned, t.CPtr)
string.memset(phys_base, 0, total_pages * 0x1000)
for pi in range(phnum):
if phdr_bufs[pi].p_type == PT_LOAD:
vaddr: t.CUInt64T = phdr_bufs[pi].p_vaddr
file_off: t.CUInt64T = phdr_bufs[pi].p_offset
filesz: t.CUInt64T = phdr_bufs[pi].p_filesz
memsz: t.CUInt64T = phdr_bufs[pi].p_memsz
dest_off: t.CUInt64T = vaddr - lowest_vaddr
dest: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(phys_base) + dest_off, t.CPtr)
if filesz > 0:
fat32.seek(fp, t.CUInt32T(file_off))
fat32.read(fp, dest, t.CUInt32T(filesz))
if memsz > filesz:
bss_start: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(dest) + filesz, t.CPtr)
bss_len: t.CUInt64T = memsz - filesz
string.memset(bss_start, 0, bss_len)
virt_page_start: t.CUInt64T = (so_load_addr + lowest_vaddr) & ~t.CUInt64T(0xFFF)
phys_page_start: t.CUInt64T = t.CUInt64T(phys_base) & ~t.CUInt64T(0xFFF)
map_end: t.CUInt64T = (so_load_addr + highest_vaddr_end + 0xFFF) & ~t.CUInt64T(0xFFF)
num_map_pages: t.CUInt64T = (map_end - virt_page_start) // 0x1000
for mi in range(num_map_pages):
virt_addr: t.CUInt64T = virt_page_start + mi * 0x1000
phys_addr: t.CUInt64T = phys_page_start + mi * 0x1000
paging.MapPage(virt_addr, phys_addr, paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
base_vaddr: t.CUInt64T = so_load_addr + lowest_vaddr
dyn_buf: t.CArray[Elf64_Dyn, MAX_DYNS]
string.memset(c.Addr(dyn_buf), 0, MAX_DYNS * 16)
dyn_count: t.CInt = 0
for pi in range(phnum):
if phdr_bufs[pi].p_type == PT_DYNAMIC:
fat32.seek(fp, t.CUInt32T(phdr_bufs[pi].p_offset))
dyn_read: t.CUInt32T = fat32.read(fp, c.Addr(dyn_buf), t.CUInt32T(phdr_bufs[pi].p_filesz))
dyn_count = t.CInt(dyn_read) // 16
strtab_vaddr: t.CUInt64T = 0
symtab_vaddr: t.CUInt64T = 0
hash_vaddr: t.CUInt64T = 0
syment: t.CUInt64T = 0
rela_vaddr: t.CUInt64T = 0
relasz: t.CUInt64T = 0
jmprel_vaddr: t.CUInt64T = 0
pltrelsz: t.CUInt64T = 0
for di in range(dyn_count):
if dyn_buf[di].d_tag == DT_NULL: break
if dyn_buf[di].d_tag == DT_STRTAB: strtab_vaddr = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_SYMTAB: symtab_vaddr = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_HASH: hash_vaddr = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_SYMENT: syment = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_RELA: rela_vaddr = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_RELASZ: relasz = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_JMPREL: jmprel_vaddr = dyn_buf[di].d_val
elif dyn_buf[di].d_tag == DT_PLTRELSZ: pltrelsz = dyn_buf[di].d_val
strtab_ptr: t.CVoid | t.CPtr = t.CVoid(strtab_vaddr + so_load_addr, t.CPtr)
symtab_ptr: t.CVoid | t.CPtr = t.CVoid(symtab_vaddr + so_load_addr, t.CPtr)
if rela_vaddr != 0 and relasz > 0:
rela_count: t.CUInt64T = relasz // 24
for ri in range(rela_count):
rela: Elf64_Rela | t.CPtr = t.CVoid(t.CUInt64T(rela_vaddr) + so_load_addr + ri * 24, t.CPtr)
r_type: t.CUInt32T = t.CUInt32T(rela.r_info & 0xFFFFFFFF)
r_sym: t.CUInt32T = t.CUInt32T(rela.r_info >> 32)
target_addr: t.CVoid | t.CPtr = t.CVoid(rela.r_offset + so_load_addr, t.CPtr)
if r_type == R_X86_64_RELATIVE:
val: t.CUInt64T = t.CUInt64T(rela.r_addend) + so_load_addr
string.memcpy(target_addr, c.Addr(val), 8)
elif r_type == R_X86_64_64 or r_type == R_X86_64_GLOB_DAT or r_type == R_X86_64_JUMP_SLOT:
sym: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym) * syment, t.CPtr)
sym_name: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym.st_name, t.CPtr)
resolved: t.CVoid | t.CPtr = _resolve_symbol(sym_name)
if resolved is not None:
val2: t.CUInt64T = t.CUInt64T(resolved) + t.CUInt64T(rela.r_addend)
string.memcpy(target_addr, c.Addr(val2), 8)
elif sym.st_value != 0:
val3: t.CUInt64T = sym.st_value + so_load_addr + t.CUInt64T(rela.r_addend)
string.memcpy(target_addr, c.Addr(val3), 8)
if jmprel_vaddr != 0 and pltrelsz > 0:
jmprel_count: t.CUInt64T = pltrelsz // 24
for ji in range(jmprel_count):
rela2: Elf64_Rela | t.CPtr = t.CVoid(jmprel_vaddr + so_load_addr + ji * 24, t.CPtr)
r_type2: t.CUInt32T = t.CUInt32T(rela2.r_info & 0xFFFFFFFF)
r_sym2: t.CUInt32T = t.CUInt32T(rela2.r_info >> 32)
target2: t.CVoid | t.CPtr = t.CVoid(rela2.r_offset + so_load_addr, t.CPtr)
if r_type2 == R_X86_64_JUMP_SLOT or r_type2 == R_X86_64_GLOB_DAT:
sym2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(symtab_ptr) + t.CUInt64T(r_sym2) * syment, t.CPtr)
sym_name2: t.CConst | t.CChar | t.CPtr = t.CConst(t.CUInt64T(strtab_ptr) + sym2.st_name, t.CPtr)
resolved2: t.CVoid | t.CPtr = _resolve_symbol(sym_name2)
if resolved2 is not None:
val4: t.CUInt64T = t.CUInt64T(resolved2) + t.CUInt64T(rela2.r_addend)
string.memcpy(target2, c.Addr(val4), 8)
elif sym2.st_value != 0:
val5: t.CUInt64T = sym2.st_value + so_load_addr + t.CUInt64T(rela2.r_addend)
string.memcpy(target2, c.Addr(val5), 8)
else:
cr3: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(cr3), 80, "[so] WARN: unresolved PLT sym offset=%lu\n", sym2.st_name)
serial.puts(cr3)
fat32.close(fp)
_so_strtab = strtab_ptr
_so_symtab = symtab_ptr
_so_syment = syment
_so_sym_count = 0
if hash_vaddr != 0:
hash_ptr: t.CVoid | t.CPtr = t.CVoid(hash_vaddr + so_load_addr, t.CPtr)
nchain: t.CUInt32T = c.Deref(t.CVoid(t.CUInt64T(hash_ptr) + 4, t.CPtr))
_so_sym_count = t.CInt(nchain)
elif syment > 0 and strtab_vaddr > symtab_vaddr and symtab_vaddr != 0:
sym_tab_size: t.CUInt64T = strtab_vaddr - symtab_vaddr
_so_sym_count = t.CInt(sym_tab_size // syment)
cr: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(cr), 80, "[so] loaded at 0x%lx\n", base_vaddr)
serial.puts(cr)
fat32.unlock()
return t.CVoid(base_vaddr, t.CPtr)
def get_so_symbol(so_base: t.CVoid | t.CPtr, name: t.CConst | t.CChar | t.CPtr, so_path: t.CConst | t.CChar | t.CPtr) -> t.CVoid | t.CPtr:
if _so_symtab is None or _so_strtab is None or _so_syment == 0:
return None
if name is None:
return None
for symi in range(_so_sym_count):
sb2: Elf64_Sym | t.CPtr = t.CVoid(t.CUInt64T(_so_symtab) + t.CUInt64T(symi) * _so_syment, t.CPtr)
if sb2.st_value != 0 and sb2.st_shndx != 0:
sym_name_off2: t.CUInt32T = sb2.st_name
cmp_off2: t.CUInt32T = sym_name_off2
match2: t.CInt = 0
ni2: t.CInt = 0
while True:
sc3: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr))
nc3: t.CChar = c.Deref(name + ni2)
if sc3 != nc3:
break
if sc3 == 0:
match2 = 1
break
ni2 += 1
if match2 == 0:
dot_pos3: t.CUInt32T = sym_name_off2
is_sha1_2: t.CInt = 0
sha1_len_2: t.CUInt32T = 0
while True:
dc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + dot_pos3, t.CPtr))
if dc4 == 46:
if sha1_len_2 >= 8:
is_sha1_2 = 1
break
if dc4 == 0:
break
if (dc4 >= 48 and dc4 <= 57) or (dc4 >= 97 and dc4 <= 102):
sha1_len_2 += 1
else:
break
dot_pos3 += 1
if is_sha1_2:
cmp_off2 = dot_pos3 + 1
match2 = 1
ni2 = 0
while True:
sc4: t.CChar = c.Deref(t.CVoid(t.CUInt64T(_so_strtab) + cmp_off2 + ni2, t.CPtr))
nc4: t.CChar = c.Deref(name + ni2)
if sc4 != nc4:
match2 = 0
break
if sc4 == 0:
break
ni2 += 1
if match2:
result_addr: t.CUInt64T = t.CUInt64T(so_base) + sb2.st_value
dbg2: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(dbg2), 80, "[so] found '%s' at 0x%lx\n", t.CVoid(t.CUInt64T(_so_strtab) + sym_name_off2, t.CPtr), result_addr)
serial.puts(dbg2)
return t.CVoid(result_addr, t.CPtr)
serial.puts("[so] symbol not found\n")
return None
+2
View File
@@ -0,0 +1,2 @@
from . import gdt
from . import idt
+142
View File
@@ -0,0 +1,142 @@
import t, c
@c.Attribute(t.attr.packed)
class gdt_entry:
limit_low: t.CUInt16T
base_low: t.CUInt16T
base_middle: t.CUInt8T
access: t.CUInt8T
granularity: t.CUInt8T
base_high: t.CUInt8T
@c.Attribute(t.attr.packed)
class gdt_entry64:
limit_low: t.CUInt16T
base_low: t.CUInt16T
base_middle: t.CUInt8T
access: t.CUInt8T
granularity: t.CUInt8T
base_high: t.CUInt8T
base_upper: t.CUInt32T
reserved: t.CUInt32T
@c.Attribute(t.attr.packed)
class gdt_ptr:
limit: t.CUInt16T
base: t.CUInt64T
gdt: t.CArray[gdt_entry, 7] = []
gp: t.CArray[t.CUInt8T, 10]
# TSS buffer: 104 bytes, raw byte array to avoid packed struct issues
# x86_64 TSS layout: reserved0(4) rsp0(8) rsp1(8) rsp2(8) reserved1(8) ist1-ist7(56) reserved2(8) reserved3(2) iomap_base(2)
tss_buf: t.CArray[t.CUInt8T, 104]
CODE_SEG: t.CDefine | t.CUInt16T = 0x08
DATA_SEG: t.CDefine | t.CUInt16T = 0x10
USER_CODE_SEG: t.CDefine | t.CUInt16T = 0x18
USER_DATA_SEG: t.CDefine | t.CUInt16T = 0x20
TSS_SEG: t.CDefine | t.CUInt16T = 0x28
def init():
global gp, gdt, tss_buf
gdt[0].limit_low = 0
gdt[0].base_low = 0
gdt[0].base_middle = 0
gdt[0].access = 0
gdt[0].granularity = 0
gdt[0].base_high = 0
gdt[1].limit_low = 0
gdt[1].base_low = 0
gdt[1].base_middle = 0
gdt[1].access = 0x9A
gdt[1].granularity = 0x20
gdt[1].base_high = 0
gdt[2].limit_low = 0
gdt[2].base_low = 0
gdt[2].base_middle = 0
gdt[2].access = 0x92
gdt[2].granularity = 0xCF
gdt[2].base_high = 0
gdt[3].limit_low = 0
gdt[3].base_low = 0
gdt[3].base_middle = 0
gdt[3].access = 0xFA
gdt[3].granularity = 0x20
gdt[3].base_high = 0
gdt[4].limit_low = 0
gdt[4].base_low = 0
gdt[4].base_middle = 0
gdt[4].access = 0xF2
gdt[4].granularity = 0xCF
gdt[4].base_high = 0
# Set up TSS descriptor in GDT entries 5-6 (selector 0x28)
setTSS64(5, t.CUInt64T(c.Addr(tss_buf)), 103)
c.Asm("""lea rax, [rip + gdt]
lea rcx, [rip + gp]
mov word ptr [rcx], 0x37
mov qword ptr [rcx+2], rax""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX])
c.Asm("sfence; mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
flush()
# Initialize TSS: set iomap_base = 104 (0x68) at offset 102
tss_buf[102] = 0x68
tss_buf[103] = 0x00
# Load Task Register with TSS selector 0x28
c.Asm("""mov ax, 0x28
ltr ax""",
op=[t.ASM_DESCR.CLOBBER_RAX])
@c.Attribute(t.attr.naked)
def flush():
c.Asm("""lea rax, [rip + gp]
lgdt [rax]
push 0x08
lea rax, [rip+2f]
push rax
.byte 0x48, 0xcb
2:
mov eax, 0x10
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
ret""", op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
def set_tss_rsp0(rsp0: t.CUInt64T):
global tss_buf
# TSS RSP0 is at offset 4 (8 bytes) in x86_64 TSS layout
tss_buf[4] = t.CUInt8T(rsp0 & 0xFF)
tss_buf[5] = t.CUInt8T((rsp0 >> 8) & 0xFF)
tss_buf[6] = t.CUInt8T((rsp0 >> 16) & 0xFF)
tss_buf[7] = t.CUInt8T((rsp0 >> 24) & 0xFF)
tss_buf[8] = t.CUInt8T((rsp0 >> 32) & 0xFF)
tss_buf[9] = t.CUInt8T((rsp0 >> 40) & 0xFF)
tss_buf[10] = t.CUInt8T((rsp0 >> 48) & 0xFF)
tss_buf[11] = t.CUInt8T((rsp0 >> 56) & 0xFF)
def setGate(num: int, base: t.CUInt64T, limit: t.CUInt32T, access: t.CUInt8T, gran: t.CUInt8T):
global gdt
gdt[num].limit_low = (limit & 0xFFFF)
gdt[num].base_low = (base & 0xFFFF)
gdt[num].base_middle = (base >> 16) & 0xFF
gdt[num].base_high = (base >> 24) & 0xFF
gdt[num].granularity = ((limit >> 16) & 0x0F) | (gran & 0xF0)
gdt[num].access = access
def setTSS64(num: int, base: t.CUInt64T, limit: t.CUInt32T):
global gdt
gdt[num].limit_low = limit & 0xFFFF
gdt[num].base_low = base & 0xFFFF
gdt[num].base_middle = (base >> 16) & 0xFF
gdt[num].access = 0x89
gdt[num].granularity = 0x00
gdt[num].base_high = (base >> 24) & 0xFF
gdt[num + 1].limit_low = (base >> 32) & 0xFFFF
gdt[num + 1].base_low = (base >> 48) & 0xFFFF
gdt[num + 1].base_middle = 0
gdt[num + 1].access = 0
gdt[num + 1].granularity = 0
gdt[num + 1].base_high = 0
+415
View File
@@ -0,0 +1,415 @@
import platform.pch.pic as pic
import platform.pch.timer as timer
import sched.sched as sched
import drivers.serial.uart.serial as serial
import viperlib
import asm
import t, c
CODE_SEG: t.CDefine | t.CUInt16T = 0x08
@c.Attribute(t.attr.packed)
class idt_entry:
base_low: t.CUInt16T
selector: t.CUInt16T
ist_attr: t.CUInt8T
type_attr: t.CUInt8T
base_middle: t.CUInt16T
base_high: t.CUInt32T
reserved1: t.CUInt32T
idt: t.CArray[idt_entry, 256] = []
irq_handler_t: t.CTypedef = t.Callable[[], t.CInt]
def isr0() -> t.State: pass
def isr1() -> t.State: pass
def isr2() -> t.State: pass
def isr3() -> t.State: pass
def isr4() -> t.State: pass
def isr5() -> t.State: pass
def isr6() -> t.State: pass
def isr7() -> t.State: pass
def isr8() -> t.State: pass
def isr9() -> t.State: pass
def isr10() -> t.State: pass
def isr11() -> t.State: pass
def isr12() -> t.State: pass
def isr13() -> t.State: pass
def isr14() -> t.State: pass
def isr15() -> t.State: pass
def isr16() -> t.State: pass
def isr17() -> t.State: pass
def isr18() -> t.State: pass
def isr19() -> t.State: pass
def isr20() -> t.State: pass
def isr21() -> t.State: pass
def isr22() -> t.State: pass
def isr23() -> t.State: pass
def isr24() -> t.State: pass
def isr25() -> t.State: pass
def isr26() -> t.State: pass
def isr27() -> t.State: pass
def isr28() -> t.State: pass
def isr29() -> t.State: pass
def isr30() -> t.State: pass
def isr31() -> t.State: pass
def isr32() -> t.State: pass
def isr33() -> t.State: pass
def isr34() -> t.State: pass
def isr35() -> t.State: pass
def isr36() -> t.State: pass
def isr37() -> t.State: pass
def isr38() -> t.State: pass
def isr39() -> t.State: pass
def isr40() -> t.State: pass
def isr41() -> t.State: pass
def isr42() -> t.State: pass
def isr43() -> t.State: pass
def isr44() -> t.State: pass
def isr45() -> t.State: pass
def isr46() -> t.State: pass
def isr47() -> t.State: pass
def isr80() -> t.State: pass
def isr_default() -> t.State: pass
def setGate(num: int, base: t.CUInt64T, sel: t.CUInt16T, flags: t.CUInt8T):
global idt
idt[num].base_low = (base & 0xFFFF)
idt[num].base_middle = (base >> 16) & 0xFFFF
idt[num].base_high = (base >> 32) & 0xFFFFFFFF
idt[num].selector = sel
idt[num].ist_attr = 0
idt[num].type_attr = flags
idt[num].reserved1 = 0
def isrCommonhandler(interrupt: t.CInt):
c.Asm(f"""mov dx, 0x3F8
mov al, 91
out dx, al
mov ecx, {c.AsmInp(interrupt, t.ASM_DESCR.REG_ANY)}
mov rax, rcx
shr rax, 4
and al, 15
add al, 48
cmp al, 57
jle 1f
add al, 7
1:
out dx, al
mov rax, rcx
and al, 15
add al, 48
cmp al, 57
jle 2f
add al, 7
2:
out dx, al
mov al, 93
out dx, al""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX])
# Print CR2 for page faults (ISR 14)
if interrupt == 14:
cr2: t.CUInt64T = 0
c.Asm(f"""mov {c.AsmOut(cr2, t.ASM_DESCR.OUTPUT_REG)}, cr2""",
op=[t.ASM_DESCR.CLOBBER_RAX])
fault_rip: t.CUInt64T = 0
fault_err: t.CUInt64T = 0
c.Asm(f"""mov {c.AsmOut(fault_rip, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rip]
mov {c.AsmOut(fault_err, t.ASM_DESCR.OUTPUT_REG)}, [_fault_err]""",
op=[t.ASM_DESCR.CLOBBER_RAX])
buf: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(buf), 80, "[PF] cr2=0x%lx rip=0x%lx err=0x%lx\n", cr2, fault_rip, fault_err)
serial.puts(buf)
elif interrupt == 13:
gp_rip: t.CUInt64T = 0
gp_err: t.CUInt64T = 0
c.Asm(f"""mov {c.AsmOut(gp_rip, t.ASM_DESCR.OUTPUT_REG)}, [_fault_rip]
mov {c.AsmOut(gp_err, t.ASM_DESCR.OUTPUT_REG)}, [_fault_err]""",
op=[t.ASM_DESCR.CLOBBER_RAX])
gp_buf: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(gp_buf), 80, "[GP] rip=0x%lx err=0x%lx\n", gp_rip, gp_err)
serial.puts(gp_buf)
s: sched.Scheduler | t.CPtr = sched._sched_ptr
# 如果调度器未初始化,直接进入安全停机状态
if s == 0:
serial.puts(" KERNEL PANIC (sched not init)\n")
while True:
asm.sti()
asm.hlt()
tid: t.CInt = s.current_tid
if tid == 0:
serial.puts(" KERNEL PANIC\n")
while True:
asm.sti()
asm.hlt()
buf2: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(buf2), 64, " exc in tid=%d, terminating\n", tid)
serial.puts(buf2)
s.threads[tid].state = 3
# Restore GS to kernel-normal state before _yield().
# ISR entry does swapgs for user-mode exceptions (GS_BASE=&_per_cpu).
# Since _yield() switches to another thread and never returns via ISR exit,
# the swapgs at ISR exit is never executed. We must swap back here.
# Check IA32_GS_BASE (MSR 0xC0000101): if non-zero, swapgs was done at entry.
c.Asm(f"""mov ecx, 0xC0000101
rdmsr
shl rdx, 32
or rax, rdx
test rax, rax
jz 1f
swapgs
1:""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX])
sched.Scheduler._yield()
while True:
asm.sti()
asm.hlt()
def isr0_handler() -> t.CExport | t.CVoid: isrCommonhandler(0)
def isr1_handler() -> t.CExport | t.CVoid: isrCommonhandler(1)
def isr2_handler() -> t.CExport | t.CVoid: isrCommonhandler(2)
def isr3_handler() -> t.CExport | t.CVoid: isrCommonhandler(3)
def isr4_handler() -> t.CExport | t.CVoid: isrCommonhandler(4)
def isr5_handler() -> t.CExport | t.CVoid: isrCommonhandler(5)
def isr6_handler() -> t.CExport | t.CVoid: isrCommonhandler(6)
def isr7_handler() -> t.CExport | t.CVoid: isrCommonhandler(7)
def isr8_handler() -> t.CExport | t.CVoid: isrCommonhandler(8)
def isr9_handler() -> t.CExport | t.CVoid: isrCommonhandler(9)
def isr10_handler() -> t.CExport | t.CVoid: isrCommonhandler(10)
def isr11_handler() -> t.CExport | t.CVoid: isrCommonhandler(11)
def isr12_handler() -> t.CExport | t.CVoid: isrCommonhandler(12)
def isr13_handler() -> t.CExport | t.CVoid:
isrCommonhandler(13)
def isr14_handler() -> t.CExport | t.CVoid: isrCommonhandler(14)
def isr15_handler() -> t.CExport | t.CVoid: isrCommonhandler(15)
def isr16_handler() -> t.CExport | t.CVoid: isrCommonhandler(16)
def isr17_handler() -> t.CExport | t.CVoid: isrCommonhandler(17)
def isr18_handler() -> t.CExport | t.CVoid: isrCommonhandler(18)
def isr19_handler() -> t.CExport | t.CVoid: isrCommonhandler(19)
def isr20_handler() -> t.CExport | t.CVoid: isrCommonhandler(20)
def isr21_handler() -> t.CExport | t.CVoid: isrCommonhandler(21)
def isr22_handler() -> t.CExport | t.CVoid: isrCommonhandler(22)
def isr23_handler() -> t.CExport | t.CVoid: isrCommonhandler(23)
def isr24_handler() -> t.CExport | t.CVoid: isrCommonhandler(24)
def isr25_handler() -> t.CExport | t.CVoid: isrCommonhandler(25)
def isr26_handler() -> t.CExport | t.CVoid: isrCommonhandler(26)
def isr27_handler() -> t.CExport | t.CVoid: isrCommonhandler(27)
def isr28_handler() -> t.CExport | t.CVoid: isrCommonhandler(28)
def isr29_handler() -> t.CExport | t.CVoid: isrCommonhandler(29)
def isr30_handler() -> t.CExport | t.CVoid: isrCommonhandler(30)
def isr31_handler() -> t.CExport | t.CVoid: isrCommonhandler(31)
def isr32_handler() -> t.CExport | t.CVoid:
timer.timer_handler()
def isr33_handler() -> t.CExport | t.CVoid:
handler: irq_handler_t = irqGetHandler(1)
if handler:
c.Asm(f"""call {c.AsmInp(handler, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(1)
def isr34_handler() -> t.CExport | t.CVoid:
handler2: irq_handler_t = irqGetHandler(2)
if handler2:
c.Asm(f"""call {c.AsmInp(handler2, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(2)
def isr35_handler() -> t.CExport | t.CVoid:
handler3: irq_handler_t = irqGetHandler(3)
if handler3:
c.Asm(f"""call {c.AsmInp(handler3, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(3)
def isr36_handler() -> t.CExport | t.CVoid:
handler4: irq_handler_t = irqGetHandler(4)
if handler4:
c.Asm(f"""call {c.AsmInp(handler4, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(4)
def isr37_handler() -> t.CExport | t.CVoid:
handler5: irq_handler_t = irqGetHandler(5)
if handler5:
c.Asm(f"""call {c.AsmInp(handler5, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(5)
def isr38_handler() -> t.CExport | t.CVoid:
handler6: irq_handler_t = irqGetHandler(6)
if handler6:
c.Asm(f"""call {c.AsmInp(handler6, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(6)
def isr39_handler() -> t.CExport | t.CVoid:
if pic.isSpurious(7):
return
handler7: irq_handler_t = irqGetHandler(7)
if handler7:
c.Asm(f"""call {c.AsmInp(handler7, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(7)
def isr40_handler() -> t.CExport | t.CVoid:
handler8: irq_handler_t = irqGetHandler(8)
if handler8:
c.Asm(f"""call {c.AsmInp(handler8, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(8)
def isr41_handler() -> t.CExport | t.CVoid:
handler9: irq_handler_t = irqGetHandler(9)
if handler9:
c.Asm(f"""call {c.AsmInp(handler9, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(9)
def isr42_handler() -> t.CExport | t.CVoid:
handler10: irq_handler_t = irqGetHandler(10)
if handler10:
c.Asm(f"""call {c.AsmInp(handler10, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(10)
def isr43_handler() -> t.CExport | t.CVoid:
handler11: irq_handler_t = irqGetHandler(11)
if handler11:
c.Asm(f"""call {c.AsmInp(handler11, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(11)
def isr44_handler() -> t.CExport | t.CVoid:
handler: irq_handler_t = irqGetHandler(12)
if handler:
c.Asm(f"""call {c.AsmInp(handler, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
pic.eoi(12)
def isr45_handler() -> t.CExport | t.CVoid: pic.eoi(13)
def isr46_handler() -> t.CExport | t.CVoid: pic.eoi(14)
def isr47_handler() -> t.CExport | t.CVoid:
if pic.isSpurious(15):
return
pic.eoi(15)
def init_exceptions():
setGate(0, t.CUInt64T(isr0), CODE_SEG, 0x8E)
setGate(1, t.CUInt64T(isr1), CODE_SEG, 0x8E)
setGate(2, t.CUInt64T(isr2), CODE_SEG, 0x8E)
setGate(3, t.CUInt64T(isr3), CODE_SEG, 0x8E)
setGate(4, t.CUInt64T(isr4), CODE_SEG, 0x8E)
setGate(5, t.CUInt64T(isr5), CODE_SEG, 0x8E)
setGate(6, t.CUInt64T(isr6), CODE_SEG, 0x8E)
setGate(7, t.CUInt64T(isr7), CODE_SEG, 0x8E)
setGate(8, t.CUInt64T(isr8), CODE_SEG, 0x8E)
setGate(9, t.CUInt64T(isr9), CODE_SEG, 0x8E)
setGate(10, t.CUInt64T(isr10), CODE_SEG, 0x8E)
setGate(11, t.CUInt64T(isr11), CODE_SEG, 0x8E)
setGate(12, t.CUInt64T(isr12), CODE_SEG, 0x8E)
setGate(13, t.CUInt64T(isr13), CODE_SEG, 0x8E)
setGate(14, t.CUInt64T(isr14), CODE_SEG, 0x8E)
setGate(15, t.CUInt64T(isr15), CODE_SEG, 0x8E)
setGate(16, t.CUInt64T(isr16), CODE_SEG, 0x8E)
setGate(17, t.CUInt64T(isr17), CODE_SEG, 0x8E)
setGate(18, t.CUInt64T(isr18), CODE_SEG, 0x8E)
setGate(19, t.CUInt64T(isr19), CODE_SEG, 0x8E)
setGate(20, t.CUInt64T(isr20), CODE_SEG, 0x8E)
setGate(21, t.CUInt64T(isr21), CODE_SEG, 0x8E)
setGate(22, t.CUInt64T(isr22), CODE_SEG, 0x8E)
setGate(23, t.CUInt64T(isr23), CODE_SEG, 0x8E)
setGate(24, t.CUInt64T(isr24), CODE_SEG, 0x8E)
setGate(25, t.CUInt64T(isr25), CODE_SEG, 0x8E)
setGate(26, t.CUInt64T(isr26), CODE_SEG, 0x8E)
setGate(27, t.CUInt64T(isr27), CODE_SEG, 0x8E)
setGate(28, t.CUInt64T(isr28), CODE_SEG, 0x8E)
setGate(29, t.CUInt64T(isr29), CODE_SEG, 0x8E)
setGate(30, t.CUInt64T(isr30), CODE_SEG, 0x8E)
setGate(31, t.CUInt64T(isr31), CODE_SEG, 0x8E)
def init_irqs():
setGate(32, t.CUInt64T(isr32), CODE_SEG, 0x8E)
setGate(33, t.CUInt64T(isr33), CODE_SEG, 0x8E)
setGate(34, t.CUInt64T(isr34), CODE_SEG, 0x8E)
setGate(35, t.CUInt64T(isr35), CODE_SEG, 0x8E)
setGate(36, t.CUInt64T(isr36), CODE_SEG, 0x8E)
setGate(37, t.CUInt64T(isr37), CODE_SEG, 0x8E)
setGate(38, t.CUInt64T(isr38), CODE_SEG, 0x8E)
setGate(39, t.CUInt64T(isr39), CODE_SEG, 0x8E)
setGate(40, t.CUInt64T(isr40), CODE_SEG, 0x8E)
setGate(41, t.CUInt64T(isr41), CODE_SEG, 0x8E)
setGate(42, t.CUInt64T(isr42), CODE_SEG, 0x8E)
setGate(43, t.CUInt64T(isr43), CODE_SEG, 0x8E)
setGate(44, t.CUInt64T(isr44), CODE_SEG, 0x8E)
setGate(45, t.CUInt64T(isr45), CODE_SEG, 0x8E)
setGate(46, t.CUInt64T(isr46), CODE_SEG, 0x8E)
setGate(47, t.CUInt64T(isr47), CODE_SEG, 0x8E)
irq_handlers: t.CStatic | t.CArray[irq_handler_t, 16] = [None]
def irqInstallHandler(irq: t.CInt, handler: irq_handler_t, name: str) -> t.CInt:
global irq_handlers
if irq < 0 or irq > 15: return -1
irq_handlers[irq] = handler
return 0
def irqGetHandler(irq: t.CInt) -> irq_handler_t:
if irq < 0 or irq > 15: return None
return irq_handlers[irq]
def init():
global idt
for i in range(256):
setGate(i, t.CUInt64T(isr_default), CODE_SEG, 0x8E)
pic.init()
init_exceptions()
init_irqs()
# Register ISR 0x80 for int 0x80 syscall interface (user-mode ELF apps)
setGate(0x80, t.CUInt64T(isr80), CODE_SEG, 0xEE) # DPL=3 allows Ring 3 to call int 0x80
# Build idt_ptr on stack and load directly in one asm block
c.Asm("""lea rax, [rip + idt]
sub rsp, 16
mov word ptr [rsp], 0x0fff
mov qword ptr [rsp+2], rax
lidt [rsp]
add rsp, 16""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
+595
View File
@@ -0,0 +1,595 @@
.section .bss
.comm _per_cpu, 32, 8
.comm _proc_mgr, 8192, 16
.comm _proc_mgr_ptr, 8, 8
.comm _saved_cr3, 8, 8
.comm _yield_cnt, 4, 4
.comm _yield_lock, 4, 4
.comm _fs_lock, 4, 4
.comm _fault_rip, 8, 8
.comm _fault_err, 8, 8
.section .text
.intel_syntax noprefix
.globl isr_default
isr_default:
push rax
push rdx
mov al, 0x20
out 0xA0, al
out 0x20, al
pop rdx
pop rax
iretq
# =============================================================================
# ISR macros with Ring 3 support
#
# When CPU takes interrupt/exception:
# From Ring 0: pushes RFLAGS, CS, RIP (3 qwords)
# From Ring 3: pushes SS, RSP, RFLAGS, CS, RIP (5 qwords)
# CPU also switches to TSS RSP0 stack when coming from Ring 3.
#
# Strategy:
# 1. Check CS RPL to determine if from Ring 3
# 2. If from Ring 3: swapgs (make gs: point to per_cpu), save user RSP
# 3. Save full interrupt frame into registers
# 4. Re-push in uniform format with is_user flag
# 5. Call handler
# 6. On return, rebuild correct iretq frame based on is_user
# 7. If returning to Ring 3: swapgs back
# =============================================================================
.macro ISR_NOERR num
.globl isr\num
isr\num:
# Check CS RPL to determine if from Ring 3
mov rax, [rsp + 8] # CS from interrupt frame
test al, 3 # RPL bits: 0 = Ring 0, 3 = Ring 3
jnz isr\num\()_from_user
isr\num\()_from_kernel:
# From Ring 0: no swapgs needed, already on kernel stack
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
xor r11, r11 # SS = 0
xor rax, rax # RSP = 0
add rsp, 24 # remove the 3 CPU-pushed qwords
jmp isr\num\()_frame_ready
isr\num\()_from_user:
# From Ring 3: swapgs to make gs: accessible, CPU already switched to TSS RSP0
swapgs
# Save user RSP to per_cpu.user_rsp (gs:[0x0])
# Note: CPU has already switched RSP to TSS RSP0, so the user RSP is on this stack
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
mov rax, [rsp + 24] # RSP (user)
mov r11, [rsp + 32] # SS (user)
# Save user RSP to per_cpu for potential later use
mov gs:[0x0], rax
add rsp, 40 # remove the 5 CPU-pushed qwords
isr\num\()_frame_ready:
# r8=RIP, r9=CS, r10=RFLAGS, rax=RSP(0 if kernel), r11=SS(0 if kernel)
# Build uniform frame on stack for iretq later
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
# Push is_user flag (1 if from Ring 3, 0 if from Ring 0)
xor ecx, ecx
test r9b, 3 # test CS RPL
setnz cl # cl = 1 if from Ring 3
push rcx # is_user
# Push all general-purpose registers
push rax
push rbx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
# Save faulting RIP and error code for debug
mov [_fault_rip], r8
mov [_fault_err], rdx
call isr\num\()_handler
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rbx
pop rax
# Pop is_user flag
pop rcx # is_user
# Pop the uniform frame into registers
pop r8 # RIP
pop r9 # CS
pop r10 # RFLAGS
pop rax # RSP (user)
pop r11 # SS (user)
test rcx, rcx
jnz isr\num\()_ret_user
isr\num\()_ret_kernel:
# Return to Ring 0: push RFLAGS, CS, RIP only
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
isr\num\()_ret_user:
# Return to Ring 3: swapgs back, then push SS, RSP, RFLAGS, CS, RIP
swapgs
# DIAG+FIX: if RFLAGS.NT (bit 14) is set, iretq performs hardware task-return
# which loads TSS selector 0x28 -> #GP(err=0x28). Detect and clear it.
test r10, 0x4000
jz 1f
btr r10, 14
mov dx, 0x3F8
mov al, 0x4E # 'N' marker = NT was set (now cleared)
out dx, al
1:
cmp r9, 0x1B
jne 8f
cmp r11, 0x23
jne 8f
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
8:
mov dx, 0x3F8
mov al, 0x42 # 'B' = bad CS/SS
out dx, al
mov al, 0x20 # space
out dx, al
# Output CS (r9) low byte as 2 hex digits
mov rcx, r9
mov rax, rcx
shr rax, 4
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 2f
add al, 7
2:
out dx, al
mov rax, rcx
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 3f
add al, 7
3:
out dx, al
mov al, 0x20 # space
out dx, al
# Output SS (r11) low byte as 2 hex digits
mov rcx, r11
mov rax, rcx
shr rax, 4
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 4f
add al, 7
4:
out dx, al
mov rax, rcx
and rax, 0xF
add al, 0x30
cmp al, 0x39
jle 5f
add al, 7
5:
out dx, al
mov al, 0x0A # newline
out dx, al
cli
7:
hlt
jmp 7b
.endm
.macro ISR_ERR num
.globl isr\num
isr\num:
# Stack at entry (with error code):
# From Ring 0: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS
# From Ring 3: [rsp]=error_code, [rsp+8]=RIP, [rsp+16]=CS, [rsp+24]=RFLAGS, [rsp+32]=RSP, [rsp+40]=SS
# Check CS RPL
mov rax, [rsp + 16] # CS from interrupt frame
test al, 3
jnz isr\num\()_from_user
isr\num\()_from_kernel:
# From Ring 0: no swapgs needed
mov rdx, [rsp] # error_code
mov r8, [rsp + 8] # RIP
mov r9, [rsp + 16] # CS
mov r10, [rsp + 24] # RFLAGS
xor r11, r11 # SS = 0
xor rax, rax # RSP = 0
add rsp, 32 # remove 4 CPU-pushed qwords (error + 3 frame)
jmp isr\num\()_frame_ready
isr\num\()_from_user:
# From Ring 3: swapgs, CPU already on TSS RSP0
swapgs
mov rdx, [rsp] # error_code
mov r8, [rsp + 8] # RIP
mov r9, [rsp + 16] # CS
mov r10, [rsp + 24] # RFLAGS
mov rax, [rsp + 32] # RSP (user)
mov r11, [rsp + 40] # SS (user)
mov gs:[0x0], rax # save user RSP
add rsp, 48 # remove 6 CPU-pushed qwords (error + 5 frame)
isr\num\()_frame_ready:
# rdx=error_code, r8=RIP, r9=CS, r10=RFLAGS, rax=RSP, r11=SS
# Build uniform frame on stack
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
push rdx # error_code
# Push is_user flag
xor ecx, ecx
test r9b, 3
setnz cl
push rcx # is_user
# Push all general-purpose registers
push rax
push rbx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
# Save faulting RIP and error code for debug
mov [_fault_rip], r8
mov [_fault_err], rdx
call isr\num\()_handler
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rbx
pop rax
# Pop is_user flag
pop rcx # is_user
add rsp, 8 # skip error_code (handler already consumed it)
pop r8 # RIP
pop r9 # CS
pop r10 # RFLAGS
pop rax # RSP (user)
pop r11 # SS (user)
test rcx, rcx
jnz isr\num\()_ret_user
isr\num\()_ret_kernel:
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
isr\num\()_ret_user:
swapgs
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
.endm
ISR_NOERR 0
ISR_NOERR 1
ISR_NOERR 2
ISR_NOERR 3
ISR_NOERR 4
ISR_NOERR 5
ISR_NOERR 6
ISR_NOERR 7
ISR_ERR 8
ISR_NOERR 9
ISR_ERR 10
ISR_ERR 11
ISR_ERR 12
ISR_ERR 13
ISR_ERR 14
ISR_NOERR 15
ISR_NOERR 16
ISR_ERR 17
ISR_NOERR 18
ISR_NOERR 19
ISR_NOERR 20
ISR_NOERR 21
ISR_NOERR 22
ISR_NOERR 23
ISR_NOERR 24
ISR_NOERR 25
ISR_NOERR 26
ISR_NOERR 27
ISR_NOERR 28
ISR_NOERR 29
ISR_NOERR 30
ISR_NOERR 31
ISR_NOERR 32
ISR_NOERR 33
ISR_NOERR 34
ISR_NOERR 35
ISR_NOERR 36
ISR_NOERR 37
ISR_NOERR 38
ISR_NOERR 39
ISR_NOERR 40
ISR_NOERR 41
ISR_NOERR 42
ISR_NOERR 43
ISR_NOERR 44
ISR_NOERR 45
ISR_NOERR 46
ISR_NOERR 47
.globl syscall_entry
syscall_entry:
mov rax, rsp
shr rax, 47
test rax, rax
jnz syscall_from_kernel
syscall_from_user:
swapgs
mov gs:[0x0], rsp
mov rsp, gs:[0x8]
jmp syscall_common
syscall_from_kernel:
syscall_common:
push rcx
push r11
push rbx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r12
push r13
push r14
push r15
mov rbp, rsp
and rsp, ~15
push r9
mov r9, r8
mov r8, r10
mov rcx, rdx
mov rdx, rsi
mov rsi, rdi
mov rdi, rax
call syscall_handler
add rsp, 8
mov rsp, rbp
pop r15
pop r14
pop r13
pop r12
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rbx
pop r11
pop rcx
mov rax, rcx
shr rax, 47
test rax, rax
jnz syscall_ret_kernel
syscall_ret_user:
mov rsp, gs:[0x0]
swapgs
push r11
popfq
sysretq
syscall_ret_kernel:
push r11
popfq
jmp rcx
# =============================================================================
# ISR 0x80 - int 0x80 syscall handler for user-mode ELF apps
#
# int 0x80 calling convention (ViperOS):
# rax = syscall number
# rbx = arg1
# rcx = arg2
# rdx = arg3
# r10 = arg4
#
# Translated to syscall_handler calling convention:
# rdi = n (syscall number)
# rsi = arg1
# rdx = arg2
# rcx = arg3
# r8 = arg4
# r9 = arg5 (0)
# stack = arg6 (0)
#
# Return value in rax.
# =============================================================================
.globl isr80
isr80:
# Check CS RPL to determine if from Ring 3
mov rax, [rsp + 8] # CS from interrupt frame
test al, 3
jnz isr80_from_user
isr80_from_kernel:
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
xor r11, r11 # SS = 0
xor rax, rax # RSP = 0
add rsp, 24
jmp isr80_frame_ready
isr80_from_user:
swapgs
mov r8, [rsp] # RIP
mov r9, [rsp + 8] # CS
mov r10, [rsp + 16] # RFLAGS
mov rax, [rsp + 24] # RSP (user)
mov r11, [rsp + 32] # SS (user)
mov gs:[0x0], rax
add rsp, 40
isr80_frame_ready:
# Build uniform frame on stack
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
# Save ALL registers FIRST (including original rcx = arg2)
# Order: rax, rbx, rcx, rdx, rsi, rdi, rbp, r8, r9, r10, r11, r12, r13, r14, r15
push rax # [rsp+112] rax (syscall number)
push rbx # [rsp+104] rbx (arg1)
push rcx # [rsp+96] rcx (arg2)
push rdx # [rsp+88] rdx (arg3)
push rsi # [rsp+80]
push rdi # [rsp+72]
push rbp # [rsp+64]
push r8 # [rsp+56]
push r9 # [rsp+48]
push r10 # [rsp+40] r10 (arg4)
push r11 # [rsp+32]
push r12 # [rsp+24]
push r13 # [rsp+16]
push r14 # [rsp+8]
push r15 # [rsp+0]
# Calculate is_user from saved CS on stack
# CS is at [rsp + 15*8 + 16] = [rsp + 136]
mov rax, [rsp + 136] # CS from frame
xor ecx, ecx
test al, 3
setnz cl
push rcx # is_user (pushed AFTER all registers)
# Stack layout now:
# [rsp+0] = is_user
# [rsp+8] = r15 [rsp+16] = r14 [rsp+24] = r13 [rsp+32] = r12
# [rsp+40] = r11 [rsp+48] = r10 [rsp+56] = r9 [rsp+64] = r8
# [rsp+72] = rbp [rsp+80] = rdi [rsp+88] = rsi [rsp+96] = rdx
# [rsp+104] = rcx [rsp+112] = rbx [rsp+120] = rax
# Translate int 0x80 calling convention to syscall_handler convention
mov rdi, [rsp + 120] # original rax = syscall number -> rdi
mov rsi, [rsp + 112] # original rbx = arg1 -> rsi
mov rdx, [rsp + 104] # original rcx = arg2 -> rdx
mov rcx, [rsp + 96] # original rdx = arg3 -> rcx
mov r8, [rsp + 48] # original r10 = arg4 -> r8
xor r9, r9 # arg5 = 0
push 0 # arg6 = 0 (on stack)
call syscall_handler
add rsp, 8 # remove arg6
# Save return value by overwriting saved rax on stack
mov [rsp + 120], rax
# Pop is_user flag
pop rcx # is_user
# Restore registers
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rcx # restore original rcx (arg2)
pop rbx
pop rax # now has the return value
# Pop frame
pop r8 # RIP
pop r9 # CS
pop r10 # RFLAGS
pop rax # RSP (user)
pop r11 # SS (user)
test rcx, rcx
jnz isr80_ret_user
isr80_ret_kernel:
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
isr80_ret_user:
swapgs
push r11 # SS
push rax # RSP
push r10 # RFLAGS
push r9 # CS
push r8 # RIP
iretq
+470
View File
@@ -0,0 +1,470 @@
import drivers.serial.uart.serial as serial
import services.desktop as desktop
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import sched.sched as sched
import sched.process as process
import viperlib
import string
import mm.mm as mm
import paging.paging as paging
import execrunner.elf as elf
import t, c
CREATE_WINDOW: t.CDefine = 1
DRAW_TEXT: t.CDefine = 2
EXIT: t.CDefine = 3
OPEN: t.CDefine = 4
CLOSE: t.CDefine = 5
READ: t.CDefine = 6
WRITE: t.CDefine = 7
SEEK: t.CDefine = 8
TELL: t.CDefine = 9
SIZE: t.CDefine = 10
MKDIR: t.CDefine = 11
REMOVE: t.CDefine = 12
OPENDIR: t.CDefine = 13
READDIR: t.CDefine = 14
CLOSEDIR: t.CDefine = 15
STAT: t.CDefine = 16
WAIT_WINDOW: t.CDefine = 17
SERIAL_PUTS: t.CDefine = 18
LOAD_SO: t.CDefine = 19
SO_CALL1: t.CDefine = 20
GET_WINBUF: t.CDefine = 21
WIN_FLUSH: t.CDefine = 22
POLL_EVENT: t.CDefine = 23
SET_CURSOR: t.CDefine = 24
YIELD: t.CDefine = 25
SET_TITLE: t.CDefine = 26
SET_GEOMETRY: t.CDefine = 27
SPAWN: t.CDefine = 28
MMAP: t.CDefine = 29
MUNMAP: t.CDefine = 30
SET_RESIZABLE: t.CDefine = 31
GET_WIN_SIZE: t.CDefine = 32
THREAD_CREATE: t.CDefine = 33
IA32_EFER: t.CDefine = 0xC0000080
IA32_STAR: t.CDefine = 0xC0000081
IA32_LSTAR: t.CDefine = 0xC0000082
IA32_FMASK: t.CDefine = 0xC0000084
def wrmsr(msr: t.CUInt32T, val: t.CUInt64T):
lo: t.CUInt32T = t.CUInt32T(val & 0xFFFFFFFF)
hi: t.CUInt32T = t.CUInt32T(val >> 32)
c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)}
mov eax, {c.AsmInp(lo, t.ASM_DESCR.REG_ANY)}
mov edx, {c.AsmInp(hi, t.ASM_DESCR.REG_ANY)}
wrmsr""",
op=[t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX])
def rdmsr(msr: t.CUInt32T) -> t.CUInt64T:
lo: t.CUInt32T
hi: t.CUInt32T
c.Asm(f"""mov ecx, {c.AsmInp(msr, t.ASM_DESCR.REG_ANY)}
rdmsr""",
out=[c.AsmOut(lo, t.ASM_DESCR.OUTPUT_REG), c.AsmOut(hi, t.ASM_DESCR.OUTPUT_REG)],
op=[t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_RDX])
return t.CUInt64T(lo) | (t.CUInt64T(hi) << 32)
def _current_proc() -> process.Process | t.CPtr:
return process.ProcessManager.current()
def _fd_alloc(p: process.Process | t.CPtr) -> t.CInt:
for i in range(process.PROC_MAX_FDS):
if not p.fd_used[i]:
p.fd_used[i] = 1
return i
return -1
def _fd_free(p: process.Process | t.CPtr, fd: t.CInt):
if fd >= 0 and fd < process.PROC_MAX_FDS:
p.fd_used[fd] = 0
p.fd_table[fd] = t.CVoid(0, t.CPtr)
def _dir_alloc(p: process.Process | t.CPtr) -> t.CInt:
for i in range(process.PROC_MAX_DIRS):
if not p.dir_used[i]:
p.dir_used[i] = 1
return i
return -1
def _dir_free(p: process.Process | t.CPtr, dd: t.CInt):
if dd >= 0 and dd < process.PROC_MAX_DIRS:
p.dir_used[dd] = 0
p.dir_table[dd] = t.CVoid(0, t.CPtr)
def CREATE_WINDOW(x: t.CInt, y: t.CInt, w: t.CInt, h: t.CInt, title: t.CConst | t.CChar | t.CPtr, style: t.CInt) -> t.CInt:
desktop.set_app_pid(process.current())
return desktop.create(x, y, w, h, title, style)
def DRAW_TEXT(win_id: t.CInt, x: t.CInt, y: t.CInt, text: t.CConst | t.CChar | t.CPtr, color: t.CUInt32T) -> t.CInt:
return desktop.draw_text(win_id, x, y, text, color)
def EXIT(code: t.CInt) -> t.CInt:
serial.puts("[syscall] exit called\n")
desktop.destroy_by_pid(process.current())
return 0
def WAIT_WINDOW(win_id: t.CInt) -> t.CInt:
while desktop.is_active(win_id):
sched.Scheduler.try_reschedule()
return 0
def SERIAL_PUTS(str_ptr: t.CVoid | t.CPtr) -> t.CInt:
s: t.CConst | t.CChar | t.CPtr = t.CVoid(str_ptr, t.CPtr)
serial.puts(s)
return 0
_loaded_so_base: t.CVoid | t.CPtr = None
_loaded_so_path: t.CArray[t.CChar, 64]
def LOAD_SO(path_ptr: t.CVoid | t.CPtr) -> t.CUInt64T:
global _loaded_so_base
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
serial.puts("[so] loading: ")
serial.puts(path)
serial.puts("\n")
base: t.CVoid | t.CPtr = elf.load_so(path)
if base is not None:
_loaded_so_base = base
for i in range(63):
ch: t.CChar = c.Deref(path + i)
_loaded_so_path[i] = ch
if ch == 0: break
_loaded_so_path[63] = 0
return t.CUInt64T(base)
return 0
def SO_CALL1(sym_name_ptr: t.CVoid | t.CPtr, arg1: t.CVoid | t.CPtr) -> t.CInt:
if _loaded_so_base is None: return -1
sym_name: t.CConst | t.CChar | t.CPtr = t.CVoid(sym_name_ptr, t.CPtr)
fn: t.CVoid | t.CPtr = elf.get_so_symbol(_loaded_so_base, sym_name, c.Addr(_loaded_so_path[0]))
if fn is None:
serial.puts("[so] symbol not found\n")
return -2
c.Asm(f"""mov rdi, {c.AsmInp(arg1, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(fn, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
serial.puts("[so] call returned\n")
return 0
def GET_WINBUF(win_id: t.CInt) -> t.CUInt64T:
buf: t.CVoid | t.CPtr = desktop.get_fb(win_id)
if buf is None:
return 0
return t.CUInt64T(buf)
def WIN_FLUSH(win_id: t.CInt) -> t.CInt:
desktop.flush(win_id)
return 0
def SET_CURSOR(win_id: t.CInt, cursor_t: t.CInt) -> t.CInt:
desktop.set_cursor_type(win_id, cursor_t)
return 0
def OPEN(path_ptr: t.CVoid | t.CPtr, mode: t.CUInt32T) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
fat32.lock()
fp: t.CVoid | t.CPtr = fat32.open(path, t.CUInt8T(mode))
if not fp:
fat32.unlock()
return -1
fd: t.CInt = _fd_alloc(p)
if fd < 0:
fat32.close(fp)
fat32.unlock()
return -1
p.fd_table[fd] = fp
fat32.unlock()
return fd
def CLOSE(fd: t.CInt) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
fat32.lock()
res: t.CInt = t.CInt(fat32.close(fp))
fat32.unlock()
_fd_free(p, fd)
return res
def READ(fd: t.CInt, buf_ptr: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return -1
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return -1
fat32.lock()
br: t.CUInt32T = fat32.read(fp, buf_ptr, count)
fat32.unlock()
return t.CInt(br)
def WRITE(fd: t.CInt, buf_ptr: t.CVoid | t.CPtr, count: t.CUInt32T) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return -1
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return -1
fat32.lock()
bw: t.CUInt32T = fat32.write(fp, buf_ptr, count)
fat32.unlock()
return t.CInt(bw)
def SEEK(fd: t.CInt, offset: t.CUInt32T) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
fat32.lock()
r: t.CInt = t.CInt(fat32.seek(fp, offset))
fat32.unlock()
return r
def TELL(fd: t.CInt) -> t.CUInt32T:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return 0
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return 0
fat32.lock()
pos: t.CUInt32T = fat32.tell(fp)
fat32.unlock()
return pos
def fsize(fd: t.CInt) -> t.CUInt32T:
p: process.Process | t.CPtr = _current_proc()
if fd < 0 or fd >= process.PROC_MAX_FDS:
return 0
fp: t.CVoid | t.CPtr = p.fd_table[fd]
if not fp:
return 0
fat32.lock()
sz: t.CUInt32T = fat32.size(fp)
fat32.unlock()
return sz
def MKDIR(path_ptr: t.CVoid | t.CPtr) -> t.CInt:
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
fat32.lock()
r: t.CInt = t.CInt(fat32.mkdir(path))
fat32.unlock()
return r
def REMOVE(path_ptr: t.CVoid | t.CPtr) -> t.CInt:
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
fat32.lock()
r: t.CInt = t.CInt(fat32.remove(path))
fat32.unlock()
return r
def OPENdir(path_ptr: t.CVoid | t.CPtr) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
dd: t.CInt = _dir_alloc(p)
if dd < 0:
return -1
dp: fat32_types.dirobj | t.CPtr = c.Addr(p.dir_pool[dd])
fat32.lock()
res: t.CInt = t.CInt(fat32.opendir(path, dp))
fat32.unlock()
if res != 0:
_dir_free(p, dd)
return -1
p.dir_table[dd] = t.CVoid(t.CUInt64T(dp), t.CPtr)
return dd
def READdir(dd: t.CInt, info_ptr: t.CVoid | t.CPtr) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if dd < 0 or dd >= process.PROC_MAX_DIRS:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
dp: t.CVoid | t.CPtr = p.dir_table[dd]
if not dp:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
info: fat32_types.fileinfo | t.CPtr = info_ptr
fat32.lock()
r: t.CInt = t.CInt(fat32.readdir(dp, info))
fat32.unlock()
return r
def CLOSEdir(dd: t.CInt) -> t.CInt:
p: process.Process | t.CPtr = _current_proc()
if dd < 0 or dd >= process.PROC_MAX_DIRS:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
dp: t.CVoid | t.CPtr = p.dir_table[dd]
if not dp:
return t.CInt(fat32_types.FRESULT.FR_INVALID_OBJECT)
fat32.lock()
fat32.closedir(dp)
fat32.unlock()
_dir_free(p, dd)
return 0
def STAT(path_ptr: t.CVoid | t.CPtr, info_ptr: t.CVoid | t.CPtr) -> t.CInt:
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
info: fat32_types.fileinfo | t.CPtr = info_ptr
fat32.lock()
r: t.CInt = t.CInt(fat32.stat(path, info))
fat32.unlock()
return r
def SPAWN(path_ptr: t.CVoid | t.CPtr, blocking: t.CInt) -> t.CInt:
path: t.CConst | t.CChar | t.CPtr = t.CVoid(path_ptr, t.CPtr)
pid: t.CInt = elf.spawn_elf(path)
if pid < 0:
return pid
if blocking:
p: process.Process | t.CPtr = process.ProcessManager.get_process(pid)
if p:
while p.state != process.PROC_DONE:
sched.Scheduler.try_reschedule()
return pid
USER_HEAP_BASE: t.CDefine = 0x20000000
def MMAP(size: t.CUInt64T) -> t.CUInt64T:
p: process.Process | t.CPtr = _current_proc()
if size == 0: return 0
aligned_size: t.CUInt64T = (size + 4095) & ~t.CUInt64T(4095)
if p.heap_start == 0:
p.heap_start = USER_HEAP_BASE
heap_end: t.CUInt64T = p.heap_start + p.heap_size
new_end: t.CUInt64T = heap_end + aligned_size
for va in range(heap_end, new_end, 4096):
phys_page: t.CVoid | t.CPtr = mm.malloc(4096)
if phys_page is None: return 0
paging.MapPage(va, t.CUInt64T(phys_page), paging.PTE_PRESENT | paging.PTE_WRITABLE | paging.PTE_USER)
p.heap_size = p.heap_size + aligned_size
return heap_end
def MUNMAP(ptr: t.CUInt64T, size: t.CUInt64T) -> t.CInt:
return 0
def syscall_entry() -> t.CExtern | t.CVoid | t.State: pass
def syscall_handler(num: t.CUInt64T, arg1: t.CUInt64T, arg2: t.CUInt64T, arg3: t.CUInt64T, arg4: t.CUInt64T, arg5: t.CUInt64T, arg6: t.CUInt64T) -> t.CExport | t.CUInt64T:
n: t.CInt = t.CInt(num)
if n == CREATE_WINDOW:
title_ptr: t.CVoid | t.CPtr = t.CVoid(arg5, t.CPtr)
return t.CUInt64T(CREATE_WINDOW(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), t.CInt(arg4), title_ptr, t.CInt(arg6)))
elif n == DRAW_TEXT:
text_ptr: t.CVoid | t.CPtr = t.CVoid(arg4, t.CPtr)
return t.CUInt64T(DRAW_TEXT(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), text_ptr, t.CUInt32T(arg5)))
elif n == EXIT:
return t.CUInt64T(EXIT(t.CInt(arg1)))
elif n == OPEN:
path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(OPEN(path_ptr, t.CUInt32T(arg2)))
elif n == CLOSE:
return t.CUInt64T(CLOSE(t.CInt(arg1)))
elif n == READ:
buf_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
return t.CUInt64T(READ(t.CInt(arg1), buf_ptr, t.CUInt32T(arg3)))
elif n == WRITE:
buf_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
return t.CUInt64T(WRITE(t.CInt(arg1), buf_ptr, t.CUInt32T(arg3)))
elif n == SEEK:
return t.CUInt64T(SEEK(t.CInt(arg1), t.CUInt32T(arg2)))
elif n == TELL:
return t.CUInt64T(TELL(t.CInt(arg1)))
elif n == SIZE:
return t.CUInt64T(fsize(t.CInt(arg1)))
elif n == MKDIR:
path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(MKDIR(path_ptr))
elif n == REMOVE:
path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(REMOVE(path_ptr))
elif n == OPENDIR:
path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(OPENdir(path_ptr))
elif n == READDIR:
info_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
return t.CUInt64T(READdir(t.CInt(arg1), info_ptr))
elif n == CLOSEDIR:
return t.CUInt64T(CLOSEdir(t.CInt(arg1)))
elif n == STAT:
path_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
info_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
return t.CUInt64T(STAT(path_ptr, info_ptr))
elif n == WAIT_WINDOW:
if t.CInt(arg2) == 1:
return t.CUInt64T(desktop.is_active(t.CInt(arg1)))
elif t.CInt(arg2) == 2:
sched.Scheduler.try_reschedule()
return 0
return t.CUInt64T(WAIT_WINDOW(t.CInt(arg1)))
elif n == SERIAL_PUTS:
str_ptr_sp: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(SERIAL_PUTS(str_ptr_sp))
elif n == LOAD_SO:
path_ptr_so: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return LOAD_SO(path_ptr_so)
elif n == SO_CALL1:
sym_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(SO_CALL1(sym_ptr, arg2))
elif n == GET_WINBUF:
return GET_WINBUF(t.CInt(arg1))
elif n == WIN_FLUSH:
return t.CUInt64T(WIN_FLUSH(t.CInt(arg1)))
elif n == POLL_EVENT:
buf_ptr_ev: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
caller_pid_ev: t.CInt = process.current()
return t.CUInt64T(desktop.poll_event(buf_ptr_ev, caller_pid_ev))
elif n == SET_CURSOR:
desktop.set_cursor_type(t.CInt(arg1), t.CInt(arg2))
return 0
elif n == YIELD:
sched.Scheduler.try_reschedule()
return 0
elif n == SET_TITLE:
title_ptr_st: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
return t.CUInt64T(desktop.set_title(t.CInt(arg1), title_ptr_st))
elif n == SET_GEOMETRY:
return t.CUInt64T(desktop.set_geometry(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3), t.CInt(arg4), t.CInt(arg5)))
elif n == SET_RESIZABLE:
return t.CUInt64T(desktop.set_resizable(t.CInt(arg1), t.CInt(arg2), t.CInt(arg3)))
elif n == GET_WIN_SIZE:
return t.CUInt64T(desktop.get_size(t.CInt(arg1)))
elif n == SPAWN:
path_ptr_sp: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
return t.CUInt64T(SPAWN(path_ptr_sp, t.CInt(arg2)))
elif n == MMAP:
return MMAP(arg1)
elif n == MUNMAP:
return t.CUInt64T(MUNMAP(arg1, arg2))
elif n == THREAD_CREATE:
func_ptr: t.CVoid | t.CPtr = t.CVoid(arg1, t.CPtr)
arg_ptr: t.CVoid | t.CPtr = t.CVoid(arg2, t.CPtr)
cur_pid: t.CInt = process.current()
th: sched.Thread | t.CPtr = sched.Scheduler.create_thread(func_ptr, arg_ptr, cur_pid)
if th is None: return t.CUInt64T(0xFFFFFFFFFFFFFFFF)
p: process.Process | t.CPtr = process.ProcessManager.get_process(cur_pid)
if p:
p.addThread(th.tid)
return t.CUInt64T(th.tid)
return 0
def init():
efer: t.CUInt64T = rdmsr(IA32_EFER)
efer = efer | 1
wrmsr(IA32_EFER, efer)
star_val: t.CUInt64T = (t.CUInt64T(0x08) << 32) | (t.CUInt64T(0x1B) << 48)
wrmsr(IA32_STAR, star_val)
wrmsr(IA32_LSTAR, t.CUInt64T(syscall_entry))
wrmsr(IA32_FMASK, 0x200)
serial.puts("[syscall] MSR init done\n")
+346
View File
@@ -0,0 +1,346 @@
from stdint import *
import bootinfo
import intr
import platform.pch as pch
import platform.pch.timer as timer
import platform.pch.rtc as rtc
import drivers.serial.uart.su8250 as su8250
import drivers.serial.uart.serial as serial
import drivers.devfs.devfs as devfs
import paging.paging as paging
import drivers.video.vesafb.vga as vga
import drivers.video.vesafb.gfx as gfx
import drivers.storage.ide.ide as ide
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import mm.mm as mm
import string
import sched.sched as sched
import sched.process as proc
import drivers.input.keyboard.keyboard as keyboard
import drivers.input.mouse.mouse as mouse
import drivers.usb.usb as usb
import drivers.core.cpu.cpu as cpu
import drivers.core.cpu.apic as apic
import viperlib
import services.keyboard as kbd_svc
import services.mouse as mse_svc
import services.desktop as desktop
import intr.syscall as syscall
import execrunner.elf as elf
import t, c
import asm
kbd_tid: t.CInt = -1
mse_tid: t.CInt = -1
BootInfo: bootinfo.bootinfo | t.CPtr
@t.Object
# @t.CVTable
class ViperKernel:
BootInfo: bootinfo.bootinfo | t.CPtr
VGA: vga._VGAScreenDriver | t.CPtr
VGA_drv: vga._VGAScreenDriver
def __init__(self):
global BootInfo
self.BootInfo = BootInfo
BootInfo: bootinfo.bootinfo | t.CPtr = self.BootInfo
serial.init()
serial.puts("===== ViperOS VKernel Test\n =====")
serial.puts("V2.3.8\n")
intr.gdt.init()
# Set IA32_KERNEL_GS_BASE = &_per_cpu so swapgs in ISR/syscall works.
# _CPUObject is never instantiated, so __init_tss__ never runs;
# without this, GS base stays 0 and gs:[0] writes hit null -> triple fault.
cpu.msr_write(cpu.IA32_KERNEL_GS_BASE, t.CUInt64T(c.Addr(cpu._per_cpu)))
serial.puts("[main] gdt init done\n")
intr.idt.init()
syscall.init()
serial.puts("[main] syscall init done\n")
# 必须在 sti() 之前初始化调度器,否则中断触发时 _sched_ptr 为 NULL 会导致三重故障
sched.Scheduler()
serial.puts("[main] sched init done\n")
timer.timer_init()
serial.puts("[main] timer init done\n")
#mouse.init()
#serial.puts("[main] mouse init done\n")
#apic.early_init()
#serial.puts("[main] apic init done\n")
asm.sti()
serial.puts("[main] sti done\n")
timer.timer_msleep(100)
serial.puts("[main] msleep done\n")
rtc.rtc_init()
serial.puts("[main] rtc init done\n")
ncpu: t.CUInt32T = apic.get_cpu_count()
ncpu_cr: t.CArray[t.CChar, 32]
viperlib.snprintf(c.Addr(ncpu_cr), 32, "[main] cpu_count=%u\n", ncpu)
serial.puts(ncpu_cr)
if ncpu > 1:
apic.start_aps()
serial.puts("[main] kernel init done\n")
fb: t.CVoid | t.CPtr = BootInfo.framebuffer_addr
width: t.CUInt64T = BootInfo.framebuffer_width
height: t.CUInt64T = BootInfo.framebuffer_height
pitch: t.CUInt64T = BootInfo.framebuffer_pitch
fb_format: t.CUInt64T = BootInfo.framebuffer_format
fb_size: t.CUInt64T = BootInfo.framebuffer_size
if BootInfo is not None:
paging.init(BootInfo.MemmapAddr, BootInfo.MemmapSize)
fb_pages: t.CUInt64T = (fb_size + t.CUInt64T(0xFFF)) >> t.CUInt64T(12)
fpi: t.CUInt64T = 0
while fpi < fb_pages:
fva: t.CUInt64T = t.CUInt64T(fb) + fpi * t.CUInt64T(0x1000)
paging.MapPage(fva, fva, paging.PTE_WRITABLE | paging.PTE_PCD)
fpi += 1
mm.init(BootInfo.MemmapAddr, BootInfo.MemmapSize, BootInfo.MemmapDescSize, BootInfo.framebuffer_addr, fb_size)
pt_pool_buf: t.CVoid | t.CPtr = mm.malloc(t.CUInt64T(513) * t.CUInt64T(4096))
paging.init_pool(pt_pool_buf, t.CUInt64T(513) * t.CUInt64T(4096))
else:
paging.init(0, 0)
mm.init(0, 0, 0, 0, 0)
pt_pool_buf2: t.CVoid | t.CPtr = mm.malloc(t.CUInt64T(513) * t.CUInt64T(4096))
paging.init_pool(pt_pool_buf2, t.CUInt64T(513) * t.CUInt64T(4096))
serial.puts("[main] paging and mm init done\n")
proc.ProcessManager()
serial.puts("[main] process manager init done\n")
ide.init()
serial.puts("[main] ide init done\n")
serial.puts("[main] initializing FAT32 filesystem\n")
scan_res: t.CInt = fat32.scan_drives()
ndrives: t.CInt = fat32.get_drive_count()
ndrives_cr: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(ndrives_cr), 64, "[fat32] scan_res=%d found %d drives\n", scan_res, ndrives)
serial.puts(ndrives_cr)
if ndrives > 0:
mount_res: t.CInt = fat32.mount()
if mount_res == 0:
dp: fat32_types.dirobj
res: t.CInt = fat32.opendir("/", c.Addr(dp))
if res == 0:
serial.puts("[fat32] listing 0:/\n")
count: t.CInt = 0
while count < 20:
info: fat32_types.fileinfo
res2: t.CInt = fat32.readdir(c.Addr(dp), c.Addr(info))
if res2 != 0: break
if info.fname[0] == 0: break
is_dir: t.CInt = 1 if (info.attr & fat32_types.AM_DIR) else 0
entry_cr: t.CArray[t.CChar, 300]
viperlib.snprintf(c.Addr(entry_cr), 300, " %s size=%lu dir=%d\n", c.Addr(info.fname[0]), info.file_size, is_dir)
serial.puts(entry_cr)
count = count + 1
fat32.closedir(c.Addr(dp))
serial.puts("[fat32] directory listing done\n")
else:
serial.puts("[fat32] failed to open root directory\n")
# Load ELF apps BEFORE running destructive FAT32 tests
serial.puts("[main] loading ELF apps...\n")
dp_apps: fat32_types.dirobj
apps_res: t.CInt = fat32.opendir("/APPS", c.Addr(dp_apps))
if apps_res == 0:
serial.puts("[main] APPS dir listing:\n")
ac: t.CInt = 0
while ac < 20:
ai: fat32_types.fileinfo
ar: t.CInt = fat32.readdir(c.Addr(dp_apps), c.Addr(ai))
if ar != 0: break
if ai.fname[0] == 0: break
ae: t.CArray[t.CChar, 300]
viperlib.snprintf(c.Addr(ae), 300, " %s size=%lu\n", c.Addr(ai.fname[0]), ai.file_size)
serial.puts(ae)
ac = ac + 1
fat32.closedir(c.Addr(dp_apps))
else:
serial.puts("[main] APPS dir not found\n")
sched.Scheduler.disable()
hello_pid: t.CInt = elf.spawn_elf("/APPS/hello.elf")
if hello_pid < 0:
serial.puts("[main] Hello spawn failed\n")
else:
slog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(slog), 64, "[main] Hello spawned as pid=%d\n", hello_pid)
serial.puts(slog)
term_pid: t.CInt = elf.spawn_elf("/APPS/terminal.elf")
if term_pid < 0:
serial.puts("[main] Terminal spawn failed\n")
else:
tlog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(tlog), 64, "[main] Terminal spawned as pid=%d\n", term_pid)
serial.puts(tlog)
garg_pid: t.CInt = elf.spawn_elf("/APPS/gargantua.elf")
if garg_pid < 0:
serial.puts("[main] Gargantua spawn failed\n")
else:
glog: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(glog), 64, "[main] Gargantua spawned as pid=%d\n", garg_pid)
serial.puts(glog)
s3d_pid: t.CInt = elf.spawn_elf("/APPS/scene3d.elf")
if s3d_pid < 0:
serial.puts("[main] Scene3D spawn failed\n")
else:
s3d_log: t.CArray[t.CChar, 64]
viperlib.snprintf(c.Addr(s3d_log), 64, "[main] Scene3D spawned as pid=%d\n", s3d_pid)
serial.puts(s3d_log)
so_base: t.CVoid | t.CPtr = elf.load_so("/LIBS/SLOG.SO")
if so_base is not None:
serial.puts("[main] slog.so loaded, calling log_info...\n")
log_info_fn: t.CVoid | t.CPtr = elf.get_so_symbol(so_base, "log_info", "/LIBS/SLOG.SO")
if log_info_fn is not None:
msg: t.CConst | t.CChar | t.CPtr = "Hello from shared library!"
c.Asm(f"""mov rdi, {c.AsmInp(msg, t.ASM_DESCR.REG_ANY)}
call {c.AsmInp(log_info_fn, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_RSI,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
else:
serial.puts("[main] log_info symbol not found\n")
else:
serial.puts("[main] failed to load slog.so\n")
sched.Scheduler.enable()
# FAT32 destructive tests removed to prevent disk.img corruption
# (write/rename/delete/mkdir/rmdir permanently modify the disk image)
fat32.unmount()
serial.puts("[fat32] unmounted\n")
else:
serial.puts("[fat32] mount failed\n")
else:
serial.puts("[fat32] no drives found\n")
devfs.devfs_init()
devfs.devfs_register_serial0()
devfs.devfs_register_serial1()
devfs.devfs_register_keyboard()
devfs.devfs_register_mouse()
serial.puts("[main] sched started\n")
fd: t.CInt = devfs.devfs_open("/dev/serial0", 0)
buf: t.CArray[t.CChar, 64]
string.memset(c.Addr(buf), 0, 64)
devfs.devfs_write(fd, "hello from devfs!", 17)
devfs.devfs_read(fd, c.Addr(buf), 64)
devfs.devfs_close(fd)
cr: t.CArray[t.CChar, 30]
string.memset(c.Addr(cr), 0, 30)
viperlib.snprintf(c.Addr(cr), 30, "hello %d %.2f", 42, float(0.24))
serial.puts(cr)
serial.puts("\n")
bootinfo_cr: t.CArray[t.CChar, 256]
string.memset(c.Addr(bootinfo_cr), 0, 256)
viperlib.snprintf(c.Addr(bootinfo_cr), 256, "Bootinfo 0x%016lx %lu %lu %lu %lu %lu map 0x%lx+%lu descsize=%lu",
BootInfo.framebuffer_addr,
BootInfo.framebuffer_format,
BootInfo.framebuffer_width,
BootInfo.framebuffer_height,
BootInfo.framebuffer_pitch,
BootInfo.framebuffer_size,
BootInfo.MemmapAddr,
BootInfo.MemmapSize,
BootInfo.MemmapDescSize)
serial.puts(bootinfo_cr)
serial.puts("\n")
a: t.CConst | str = "Hello"
serial.putchar(a[4]) # o
serial.puts("\n")
d: t.CConst | int = 42
b: t.CConst | int | t.CPtr = c.Addr(d)
string.memset(c.Addr(cr), 0, 4)
viperlib.snprintf(c.Addr(cr), 30, "%d", b[0]) # 42
serial.puts(cr)
serial.puts("\n")
# paging and mm already initialized before FAT32
usb.init()
serial.puts("[main] starting keyboard service\n")
kbd_svc.start()
serial.puts("[main] starting mouse service\n")
mse_svc.start()
serial.puts("[main] services started\n")
self.VGA_drv = vga._VGAScreenDriver(fb, width, height, pitch, fb_format)
self.VGA = c.Addr(self.VGA_drv)
self.VGA.Print(100, 100, "ViperOS VKernel Test")
serial.puts("你好\n")
serial.puts("[main] starting desktop\n")
desktop.start(fb, width, height, pitch)
serial.puts("[main] desktop started\n")
# ELF apps already loaded before FAT32 tests
serial.puts("[main] entering main loop\n")
# sched.sched_dump()
last_sec: t.CUInt32T = 0
loop_cnt: t.CUInt32T = 0
while True:
cur_sec: t.CUInt32T = timer.timer_get_seconds()
if cur_sec != last_sec:
last_sec = cur_sec
#if cur_sec % 5 == 0:
# sched.sched_dump()
loop_cnt += 1
sched.Scheduler._yield()
#while True:
# cube.RenderScene(back_fb, cube.SCREEN_W, cube.SCREEN_H, time)
# string.memcpy(fb, back_fb, width * height * 4)
# serial.puts("time")
# time += float(0.1)
# circle(width / 2, height / 2, 180, fb)
# circle(640, 400, 180, 0xFFFFFFFF)
# X 字符串遍历
# c.Cast 更换
# 时钟
# PCI
# SHA1 它者文件更新
# assets
# X attributes语法和谐
# 合成式类型转换
VKP: ViperKernel | t.CPtr
@c.Attribute(t.attr.section(".text.startup"), t.attr.aligned(16))
def _start() -> t.CInt:
global VKP, BootInfo
saved_rdi: t.CUnsignedLong
c.Asm(f"mov {c.AsmOut(saved_rdi, t.ASM_DESCR.OUTPUT_REG)}, rdi",
op = [t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RDI])
asm.BSSClean()
BootInfo = saved_rdi # 隐式转为 bootinfo*
VKP = c.Addr(ViperKernel())
while True:
asm.sti()
asm.hlt()
sched.Scheduler._yield()
return 0
+764
View File
@@ -0,0 +1,764 @@
import bootinfo
import paging.paging as paging
import viperstring as string
import viperlib
import t, c
# 伙伴系统常量定义
MIN_BLOCK_SIZE: t.CDefine = 4096 # 最小块大小(4KB)
MAX_BLOCK_SIZE: t.CDefine = 268435456 # 最大块大小(256MB)
MAX_ORDER: t.CDefine = 18 # 最大阶数(2^18 = 262144)
# 内存块状态
BLOCK_FREE: t.CDefine = 0 # 空闲
BLOCK_USED: t.CDefine = 1 # 已使用
# 内存类型定义
MEMORY_TYPE_AVAILABLE: t.CUInt64T = 7
MEMORY_TYPE_RESERVED: t.CUInt64T = 0
MEMORY_TYPE_LOADER_CODE: t.CUInt64T = 1
MEMORY_TYPE_LOADER_DATA: t.CUInt64T = 2
MEMORY_TYPE_BOOT_SERVICES_CODE: t.CUInt64T = 3
MEMORY_TYPE_BOOT_SERVICES_DATA: t.CUInt64T = 4
MEMORY_TYPE_RUNTIME_SERVICES_CODE: t.CUInt64T = 5
MEMORY_TYPE_RUNTIME_SERVICES_DATA: t.CUInt64T = 6
MEMORY_TYPE_ACPI_RECLAIMABLE: t.CUInt64T = 9
MEMORY_TYPE_ACPI_NVS: t.CUInt64T = 10
MEMORY_TYPE_BAD_MEMORY: t.CUInt64T = 8
# 内存区域结构
class memory_region(t.CStruct):
start: t.CUInt64T
end: t.CUInt64T
size: t.CUInt64T
type: t.CUInt32T
next: 'memory_region' | t.CPtr
# 全局内存区域链表
memory_regions: t.CStatic | memory_region | t.CPtr = None
# 内存块结构
class memory_block(t.CStruct):
size: t.CUInt64T # 块大小
state: t.CUInt8T # 块状态
order: t.CUInt8T # 块阶数
next: 'memory_block' | t.CPtr # 下一个块
prev: 'memory_block' | t.CPtr # 上一个块
# 伙伴系统结构
class __buddy_system(t.CStruct):
free_lists: t.CArray[memory_block | t.CPtr, MAX_ORDER + 1]
total_memory: t.CUInt64T # 总内存大小
used_memory: t.CUInt64T # 已使用内存大小
free_memory: t.CUInt64T # 空闲内存大小
start_addr: t.CVoid | t.CPtr # 内存起始地址
end_addr: t.CVoid | t.CPtr # 内存结束地址
SLAB_MAGIC: t.CDefine = 0x534C4142
SLAB_NUM_SIZES: t.CDefine = 6
SLAB_THRESHOLD: t.CDefine = 1024
class slab_page(t.CStruct):
magic: t.CUInt32T
obj_size: t.CUInt16T
free_count: t.CUInt16T
data_offset: t.CUInt16T
total_objs: t.CUInt16T
free_head: t.CVoid | t.CPtr
block_ptr: memory_block | t.CPtr
next: 'slab_page' | t.CPtr
prev: 'slab_page' | t.CPtr
# 4K对齐函数
def align_4k(addr: t.CUInt64T) -> t.CUInt64T:
return (addr + 0xFFF) & ~0xFFF
# 计算阶数
def get_order(size: t.CUInt64T) -> t.CStatic | t.CInt:
order: t.CInt = 0
while size > MIN_BLOCK_SIZE:
size >>= 1
order += 1
return order
# 计算块大小
def get_block_size(order: t.CInt) -> t.CStatic | t.CUInt64T:
return MIN_BLOCK_SIZE << order
@t.Object
class _BuddySystemObject:
# 全局伙伴系统实例
buddy: __buddy_system
# 初始化伙伴系统
def __init__(self, start_addr: t.CVoid | t.CPtr, size: t.CUInt64T):
for i in range(MAX_ORDER + 1):
self.buddy.free_lists[i] = None
self.buddy.total_memory = size
self.buddy.used_memory = 0
self.buddy.free_memory = size
self.buddy.start_addr = start_addr
self.buddy.end_addr = t.CVoid(t.CUInt64T(start_addr) + size, t.CPtr)
remaining: t.CUInt64T = size
cur_addr: t.CUInt64T = t.CUInt64T(start_addr)
while remaining >= MIN_BLOCK_SIZE:
order: t.CInt = 0
tmp: t.CUInt64T = remaining
while tmp > MIN_BLOCK_SIZE:
tmp >>= 1
order += 1
block_size: t.CUInt64T = get_block_size(order)
while block_size > remaining and order > 0:
order -= 1
block_size = get_block_size(order)
if block_size > remaining: break
block: memory_block | t.CPtr = cur_addr
block.size = block_size
block.state = BLOCK_FREE
block.order = order
block.next = None
block.prev = None
if order <= MAX_ORDER:
if self.buddy.free_lists[order]:
block.next = self.buddy.free_lists[order]
self.buddy.free_lists[order].prev = block
self.buddy.free_lists[order] = block
cur_addr += block_size
remaining -= block_size
# DEBUG: print free_lists status after init
import drivers.serial.uart.serial as serial
_dmsg: t.CArray[t.CChar, 40]
serial.puts("[mm] buddy init:")
_di: t.CInt = 0
while _di <= MAX_ORDER:
if self.buddy.free_lists[_di]:
viperlib.snprintf(c.Addr(_dmsg), 40, " [%d]=0x%lx", _di, t.CUInt64T(self.buddy.free_lists[_di]))
serial.puts(_dmsg)
_di += 1
serial.puts("\n")
# 分割内存块
def split_block(self, block: memory_block | t.CPtr, target_order: t.CInt) -> memory_block | t.CPtr:
current_order: t.CInt = block.order
# 从当前阶数向下分割,直到达到目标阶数
while current_order > target_order:
current_order -= 1
new_block_size: t.CUInt64T = get_block_size(current_order)
# 创建新块
new_block: memory_block | t.CPtr = t.CUInt64T(block) + new_block_size
new_block.size = new_block_size
new_block.state = BLOCK_FREE
new_block.order = current_order
new_block.next = None
new_block.prev = None
# 更新原块大小和阶数
block.size = new_block_size
block.order = current_order
# 将新块添加到对应阶数的空闲链表
if current_order <= MAX_ORDER:
new_block.next = self.buddy.free_lists[current_order]
if self.buddy.free_lists[current_order]:
self.buddy.free_lists[current_order].prev = new_block
self.buddy.free_lists[current_order] = new_block
return block
# 合并内存块
def merge_blocks(self, block: memory_block | t.CPtr) -> t.CStatic | memory_block | t.CPtr:
order: t.CInt = block.order
while order < MAX_ORDER:
block_size: t.CUInt64T = get_block_size(order)
block_addr: t.CUInt64T = t.CUInt64T(block) - t.CUInt64T(self.buddy.start_addr)
buddy_addr: t.CUInt64T = block_addr ^ block_size
# 计算伙伴块地址
buddy: memory_block | t.CPtr = t.CUInt64T(self.buddy.start_addr) + buddy_addr
# 检查伙伴块是否存在且空闲
if buddy >= (memory_block | t.CPtr)(self.buddy.end_addr):
break # 伙伴块超出内存范围
if buddy.state != BLOCK_FREE or buddy.order != order:
break # 伙伴块不空闲或阶数不匹配
# 从空闲链表中移除伙伴块
if buddy.prev: buddy.prev.next = buddy.next
else: self.buddy.free_lists[order] = buddy.next
if buddy.next:
buddy.next.prev = buddy.prev
# 合并块
merged_block: memory_block | t.CPtr = block if block < buddy else buddy
merged_block.size = get_block_size(order + 1)
merged_block.order = order + 1
block = merged_block
order += 1
return block
# 分配内存块
def allocate_block(self, size: t.CUInt64T) -> t.CStatic | memory_block | t.CPtr:
# 计算所需阶数
order: t.CInt = get_order(size)
if order > MAX_ORDER: return None # 所需块大小超过最大限制
# 查找合适的空闲块
found_order: t.CInt = -1
for i in range(order, MAX_ORDER + 1):
if self.buddy.free_lists[i]:
found_order = i
break
# DEBUG: trace allocate_block
import drivers.serial.uart.serial as serial
_dba: t.CArray[t.CChar, 120]
if found_order == -1:
viperlib.snprintf(c.Addr(_dba), 120, "[mm] alloc FAIL size=%lu order=%d\n", size, order)
serial.puts(_dba)
_dbf: t.CArray[t.CChar, 200]
_fi: t.CInt = 0
_fo: t.CInt = 0
while _fi <= MAX_ORDER:
if self.buddy.free_lists[_fi]:
_fs: t.CInt = viperlib.snprintf(c.Addr(_dbf) + _fo, 200 - _fo, "[%d]=0x%lx ", _fi, t.CUInt64T(self.buddy.free_lists[_fi]))
_fo = _fo + _fs
_fi += 1
if _fo == 0:
serial.puts("[mm] all free_lists EMPTY\n")
else:
serial.puts(_dbf)
serial.puts("\n")
return None
if found_order == -1: return None # 没有合适的空闲块
# 取出空闲块
block: memory_block | t.CPtr = self.buddy.free_lists[found_order]
# 从空闲链表中移除块
if block.next: block.next.prev = None
self.buddy.free_lists[found_order] = block.next
# 如果块大小大于所需大小,分割块
if found_order > order:
block = self.split_block(block, order)
# 标记块为已使用
block.state = BLOCK_USED
# 更新内存统计
self.buddy.used_memory += block.size
self.buddy.free_memory -= block.size
viperlib.snprintf(c.Addr(_dba), 120, "[mm] alloc OK size=%lu order=%d found=%d block=0x%lx\n", size, order, found_order, t.CUInt64T(block))
serial.puts(_dba)
return block
# 释放内存块
def free_block(self, block: memory_block | t.CPtr) -> t.CStatic | t.CVoid:
# 标记块为空闲
block.state = BLOCK_FREE
# 更新内存统计
self.buddy.used_memory -= block.size
self.buddy.free_memory += block.size
# 尝试合并块
block = self.merge_blocks(block)
# 将块添加到对应阶数的空闲链表
order: t.CInt = block.order
if order <= MAX_ORDER:
block.next = self.buddy.free_lists[order]
block.prev = None
if self.buddy.free_lists[order]:
self.buddy.free_lists[order].prev = block
self.buddy.free_lists[order] = block
BuddySystemObject: _BuddySystemObject
def _slab_obj_size(size_class: t.CInt) -> t.CUInt16T:
if size_class == 0: return 32
if size_class == 1: return 64
if size_class == 2: return 128
if size_class == 3: return 256
if size_class == 4: return 512
return 1024
def _slab_size_class(size: t.CUInt64T) -> t.CInt:
if size <= 32: return 0
if size <= 64: return 1
if size <= 128: return 2
if size <= 256: return 3
if size <= 512: return 4
if size <= 1024: return 5
return -1
def _slab_read_ptr(addr: t.CVoid | t.CPtr) -> t.CVoid | t.CPtr:
result: t.CVoid | t.CPtr
c.Asm(f"""mov rax, [{c.AsmInp(addr, t.ASM_DESCR.REG_ANY)}]
mov {c.AsmOut(result, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
return result
def _slab_write_ptr(addr: t.CVoid | t.CPtr, val: t.CVoid | t.CPtr):
c.Asm(f"""mov rax, {c.AsmInp(val, t.ASM_DESCR.REG_ANY)}
mov [{c.AsmInp(addr, t.ASM_DESCR.REG_ANY)}], rax""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_MEMORY])
def _slab_find_page(ptr: t.CVoid | t.CPtr) -> slab_page | t.CPtr:
if ptr is None: return None
hdr_off: t.CUInt64T = memory_block.__sizeof__()
page_addr: t.CUInt64T = t.CUInt64T(ptr) & ~t.CUInt64T(0xFFF)
page: slab_page | t.CPtr = page_addr + hdr_off
if page.magic == SLAB_MAGIC:
os: t.CUInt16T = page.obj_size
if os == 32 or os == 64 or os == 128 or os == 256 or os == 512 or os == 1024:
return page
page_addr2: t.CUInt64T = page_addr - 4096
page2: slab_page | t.CPtr = page_addr2 + hdr_off
if page2.magic == SLAB_MAGIC:
os2: t.CUInt16T = page2.obj_size
if os2 == 32 or os2 == 64 or os2 == 128 or os2 == 256 or os2 == 512 or os2 == 1024:
return page2
return None
@t.Object
class _SlabAllocator:
caches: t.CArray[slab_page | t.CPtr, SLAB_NUM_SIZES]
def __init__(self):
for i in range(SLAB_NUM_SIZES):
self.caches[i] = None
def _create_page(self, size_class: t.CInt) -> slab_page | t.CPtr:
obj_size: t.CUInt16T = _slab_obj_size(size_class)
aligned_size: t.CUInt16T = t.CUInt16T((obj_size + 15) & ~15)
block: memory_block | t.CPtr = BuddySystemObject.allocate_block(4096 + memory_block.__sizeof__())
if not block: return None
hdr_off: t.CUInt64T = memory_block.__sizeof__()
page: slab_page | t.CPtr = t.CUInt64T(block) + hdr_off
page.magic = SLAB_MAGIC
page.obj_size = aligned_size
d_off: t.CUInt16T = t.CUInt16T((hdr_off + slab_page.__sizeof__() + 15) & ~15)
page.data_offset = d_off
total: t.CUInt16T = t.CUInt16T((block.size - d_off) / aligned_size)
page.total_objs = total
page.free_count = total
page.block_ptr = block
page.next = None
page.prev = None
data_start: t.CUInt64T = t.CUInt64T(block) + d_off
page.free_head = t.CVoid(data_start, t.CPtr)
for i in range(1, total):
cur: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(i - 1) * aligned_size, t.CPtr)
nxt: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(i) * aligned_size, t.CPtr)
_slab_write_ptr(cur, nxt)
last: t.CVoid | t.CPtr = t.CVoid(data_start + t.CUInt64T(total - 1) * aligned_size, t.CPtr)
_slab_write_ptr(last, t.CVoid(0, t.CPtr))
return page
def alloc(self, size_class: t.CInt) -> t.CVoid | t.CPtr:
page: slab_page | t.CPtr = self.caches[size_class]
while page:
if page.free_count > 0: break
page = page.next
if not page:
page = self._create_page(size_class)
if not page: return None
page.next = self.caches[size_class]
if self.caches[size_class]:
self.caches[size_class].prev = page
self.caches[size_class] = page
obj: t.CVoid | t.CPtr = page.free_head
page.free_head = _slab_read_ptr(obj)
page.free_count -= 1
return obj
def free_obj(self, ptr: t.CVoid | t.CPtr, page: slab_page | t.CPtr):
_slab_write_ptr(ptr, page.free_head)
page.free_head = ptr
page.free_count += 1
if page.free_count == page.total_objs:
if page.prev:
page.prev.next = page.next
else:
sc: t.CInt = _slab_size_class(page.obj_size)
if sc >= 0:
self.caches[sc] = page.next
if page.next:
page.next.prev = page.prev
page.magic = 0
BuddySystemObject.free_block(page.block_ptr)
SlabAllocator: _SlabAllocator
# 内存分配函数
def malloc(size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport:
if size == 0: return None
if size <= SLAB_THRESHOLD:
sc: t.CInt = _slab_size_class(size)
if sc >= 0:
result: t.CVoid | t.CPtr = SlabAllocator.alloc(sc)
return result
hdr: t.CUInt64T = memory_block.__sizeof__()
if size >= 4096:
total: t.CUInt64T = size + 4096
total = align_4k(total)
order: t.CInt = get_order(total)
if order > MAX_ORDER: return None
block: memory_block | t.CPtr = BuddySystemObject.allocate_block(total)
import drivers.serial.uart.serial as serial
_dbm: t.CArray[t.CChar, 120]
viperlib.snprintf(c.Addr(_dbm), 120, "[mm] malloc buddy size=%lu block=0x%lx\n", size, t.CUInt64T(block))
serial.puts(_dbm)
if not block: return None
user_ptr: t.CUInt64T = (t.CUInt64T(block) + hdr + 4095) & ~t.CUInt64T(4095)
real_hdr: t.CUInt64T = user_ptr - 8
c.Asm(f"""mov rax, {c.AsmInp(block, t.ASM_DESCR.REG_ANY)}
mov [{c.AsmInp(t.CVoid(real_hdr, t.CPtr), t.ASM_DESCR.REG_ANY)}], rax""",
op=[t.ASM_DESCR.CLOBBER_RAX, t.ASM_DESCR.CLOBBER_MEMORY])
viperlib.snprintf(c.Addr(_dbm), 120, "[mm] malloc ret user_ptr=0x%lx\n", user_ptr)
serial.puts(_dbm)
return t.CVoid(user_ptr, t.CPtr)
total2: t.CUInt64T = size + hdr
total2 = align_4k(total2)
order2: t.CInt = get_order(total2)
if order2 > MAX_ORDER: return None
block2: memory_block | t.CPtr = BuddySystemObject.allocate_block(total2)
if not block2: return None
return t.CVoid(t.CUInt64T(block2) + hdr, t.CPtr)
def malloc_direct(size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport:
if size == 0: return None
hdr: t.CUInt64T = memory_block.__sizeof__()
total: t.CUInt64T = size + hdr
total = align_4k(total)
order: t.CInt = get_order(total)
if order > MAX_ORDER: return None
block: memory_block | t.CPtr = BuddySystemObject.allocate_block(total)
if not block: return None
return t.CVoid(t.CUInt64T(block) + hdr, t.CPtr)
def free(ptr: t.CVoid | t.CPtr) -> t.CExport:
if ptr is None: return
sp: slab_page | t.CPtr = _slab_find_page(ptr)
if sp:
SlabAllocator.free_obj(ptr, sp)
return
if (t.CUInt64T(ptr) & 0xFFF) == 0:
real_hdr: t.CUInt64T = t.CUInt64T(ptr) - 8
block: memory_block | t.CPtr
c.Asm(f"""mov rax, [{c.AsmInp(t.CVoid(real_hdr, t.CPtr), t.ASM_DESCR.REG_ANY)}]
mov {c.AsmOut(block, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
BuddySystemObject.free_block(block)
return
block2: memory_block | t.CPtr = t.CUInt64T(ptr) - memory_block.__sizeof__()
BuddySystemObject.free_block(block2)
def calloc(nmemb: t.CUInt64T, size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport:
total_size: t.CUInt64T = nmemb * size
ptr: t.CVoid | t.CPtr = malloc(total_size)
if ptr:
string.memset(ptr, 0, total_size)
return ptr
def realloc(ptr: t.CVoid | t.CPtr, size: t.CUInt64T) -> t.CVoid | t.CPtr | t.CExport:
if ptr is None: return malloc(size)
if size == 0:
free(ptr)
return None
new_ptr: t.CVoid | t.CPtr = malloc(size)
if not new_ptr: return None
sp: slab_page | t.CPtr = _slab_find_page(ptr)
if sp:
old_size: t.CUInt64T = sp.obj_size
elif (t.CUInt64T(ptr) & 0xFFF) == 0:
real_hdr_r: t.CUInt64T = t.CUInt64T(ptr) - 8
block_r: memory_block | t.CPtr
c.Asm(f"""mov rax, [{c.AsmInp(t.CVoid(real_hdr_r, t.CPtr), t.ASM_DESCR.REG_ANY)}]
mov {c.AsmOut(block_r, t.ASM_DESCR.OUTPUT_REG)}, rax""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
old_size = block_r.size - 4096
else:
block2: memory_block | t.CPtr = t.CUInt64T(ptr) - memory_block.__sizeof__()
old_size = block2.size - memory_block.__sizeof__()
copy_size: t.CUInt64T = old_size if old_size < size else size
string.memcpy(new_ptr, ptr, copy_size)
free(ptr)
return new_ptr
# 回收内存区域链表
def free_memory_regions() -> t.CStatic | t.CVoid:
global memory_regions
current: memory_region | t.CPtr = memory_regions
while current:
next_region: memory_region | t.CPtr = current.next
free(current)
current = next_region
memory_regions = None
# 初始化内存管理系统
def init(MemmapAddr: t.CUInt64T, MemmapSize: t.CUInt64T, MemmapDescSize: t.CUInt64T, FbAddr: t.CUInt64T = 0, FbSize: t.CUInt64T = 0):
global BuddySystemObject
import drivers.serial.uart.serial as serial
start_addr: t.CVoid | t.CPtr = None
size: t.CUInt64T = 0
if MemmapAddr != 0:
if MemmapSize > 0:
if MemmapDescSize > 0:
num_entries: t.CUInt64T = MemmapSize / MemmapDescSize
max_memory: t.CUInt64T = 0
best_start: t.CUInt64T = 0
best_avail_size: t.CUInt64T = 0
best_avail_start: t.CUInt64T = 0
best_bs_size: t.CUInt64T = 0
best_bs_start: t.CUInt64T = 0
best_ld_size: t.CUInt64T = 0
best_ld_start: t.CUInt64T = 0
i: t.CUInt64T
for i in range(num_entries):
entry: bootinfo.memory_map_entry | t.CPtr = t.CVoid(MemmapAddr + i * MemmapDescSize, t.CPtr)
etype: t.CUInt64T = entry.type
start: t.CUInt64T = align_4k(entry.physical_start)
np: t.CUInt64T = entry.num_pages
end: t.CUInt64T = entry.physical_start + np * 4096
end = align_4k(end - 1)
region_size: t.CUInt64T = end - start
if FbAddr != 0 and FbSize != 0:
fb_end: t.CUInt64T = FbAddr + FbSize
if start < fb_end and end > FbAddr:
continue
if etype == MEMORY_TYPE_AVAILABLE:
if region_size > best_avail_size:
best_avail_size = region_size
best_avail_start = start
if etype == MEMORY_TYPE_BOOT_SERVICES_DATA:
if region_size > best_bs_size:
best_bs_size = region_size
best_bs_start = start
if etype == MEMORY_TYPE_LOADER_DATA:
if region_size > best_ld_size:
best_ld_size = region_size
best_ld_start = start
min_blk: t.CUInt64T = MIN_BLOCK_SIZE
if best_avail_size >= min_blk:
best_start = best_avail_start
max_memory = best_avail_size
elif best_bs_size >= min_blk:
best_start = best_bs_start
max_memory = best_bs_size
elif best_ld_size >= min_blk:
best_start = best_ld_start
max_memory = best_ld_size
cr4: t.CArray[t.CChar, 128]
string.memset(c.Addr(cr4), 0, 128)
viperlib.snprintf(c.Addr(cr4), 128, "mm: best=0x%lx max=%lu entries=%lu avail=%lu bs=%lu ld=%lu\n",
best_start, max_memory, num_entries, best_avail_size, best_bs_size, best_ld_size)
serial.puts(cr4)
if best_start != 0:
if max_memory >= min_blk:
start_addr = t.CVoid(best_start, t.CPtr)
size = max_memory
if start_addr is None:
size = detect_memory_size()
size = align_4k(size)
# 起始地址 4MB(内核之后),避免超出物理 RAM
# 旧值 0x10000000 在 256MB QEMU 中超出物理 RAM 边界
start_addr = t.CVoid(align_4k(0x400000), t.CPtr)
if size > 0x400000:
size = size - 0x400000
size = align_4k(size)
min_blk2: t.CUInt64T = MIN_BLOCK_SIZE
if size < min_blk2:
size = 33554432
cr2: t.CArray[t.CChar, 128]
string.memset(c.Addr(cr2), 0, 128)
viperlib.snprintf(c.Addr(cr2), 128, "mm.init: start=0x%lx size=%lu bytes\n",
t.CUInt64T(start_addr), size)
serial.puts(cr2)
BuddySystemObject.__init__(start_addr, size)
SlabAllocator.__init__()
# 虚拟内存映射结构
class vm_area(t.CStruct):
VirtAddr: t.CUInt64T
PhysAddr: t.CUInt64T
size: t.CUInt64T
flags: t.CUInt32T
next: 'vm_area' | t.CPtr
# 全局虚拟内存区域链表
vm_areas: vm_area | t.CPtr = None
# 博弈机制参数
ALLOCATION_RETRIES: t.CDefine = 5 # 分配重试次数
FRAGMENTATION_THRESHOLD: t.CDefine = 0.7 # 碎片率阈值
# 获取内存使用统计
@c.CReturn(t.CUInt64T, t.CUInt64T, t.CUInt64T)
def GetMemoryStats():
return (BuddySystemObject.buddy.total_memory, BuddySystemObject.buddy.used_memory, BuddySystemObject.buddy.free_memory)
# 计算内存碎片率
def calculate_fragmentation() -> t.CDouble:
# 计算空闲块的数量和总大小
free_blocks: t.CInt = 0
free_size: t.CUInt64T = 0
for i in range(MAX_ORDER + 1):
block: memory_block | t.CPtr = BuddySystemObject.buddy.free_lists[i]
while block:
free_blocks += 1
free_size += block.size
block = block.next
# 如果没有空闲内存,返回0
if free_size == 0:
return 0.0
# 计算碎片率
# 碎片率 = 1 - (最大空闲块大小 / 总空闲内存大小)
max_free_block: t.CUInt64T = 0
for i in range(MAX_ORDER, -1, -1):
if BuddySystemObject.buddy.free_lists[i]:
max_free_block = get_block_size(i)
break
if max_free_block == 0:
return 1.0
return 1.0 - (t.CDouble(max_free_block) / t.CDouble(free_size))
# 内存整理函数
def compact_memory() -> t.CStatic | t.CInt:
freed_pages: t.CInt = 0
for sc in range(SLAB_NUM_SIZES):
page: slab_page | t.CPtr = SlabAllocator.caches[sc]
while page:
next_page: slab_page | t.CPtr = page.next
if page.free_count == page.total_objs:
if page.prev:
page.prev.next = page.next
else:
SlabAllocator.caches[sc] = page.next
if page.next:
page.next.prev = page.prev
page.magic = 0
BuddySystemObject.free_block(page.block_ptr)
freed_pages += 1
page = next_page
return freed_pages
# 虚拟内存分配函数
def vm_alloc(size: t.CUInt64T, flags: t.CUInt32T) -> t.CUInt64T:
global vm_areas
# 4K对齐
size = align_4k(size)
# 尝试分配物理内存,使用博弈机制
PhysAddr: t.CVoid | t.CPtr = None
retries: t.CInt = 0
while retries < ALLOCATION_RETRIES:
PhysAddr = malloc(size)
if PhysAddr:
break
# 内存不足,尝试进行内存整理
if calculate_fragmentation() > FRAGMENTATION_THRESHOLD:
compact_memory()
retries += 1
if not PhysAddr:
return 0
# 分配虚拟地址
# 简单的虚拟地址分配策略:从0x100000000开始
VirtAddr: t.CUInt64T = 0x100000000
current: vm_area | t.CPtr = vm_areas
# 寻找合适的虚拟地址空间
while current:
if VirtAddr + size <= current.VirtAddr:
break
VirtAddr = current.VirtAddr + current.size
current = current.next
# 创建虚拟内存区域
area: vm_area | t.CPtr = malloc(vm_area.__sizeof__())
if area:
area.VirtAddr = VirtAddr
area.PhysAddr = t.CUInt64T(PhysAddr)
area.size = size
area.flags = flags
area.next = vm_areas
vm_areas = area
# 映射虚拟地址到物理地址
# 计算需要映射的页数
pages: t.CUInt64T = size / 4096
i: t.CUInt64T
for i in range(pages):
page_virt: t.CUInt64T = VirtAddr + (i * 4096)
page_phys: t.CUInt64T = t.CUInt64T(PhysAddr) + (i * 4096)
# 调用paging模块的映射函数
paging.MapPage(page_virt, page_phys, flags)
return VirtAddr
# 虚拟内存释放函数
def vm_free(VirtAddr: t.CUInt64T):
global vm_areas
# 查找虚拟内存区域
current: vm_area | t.CPtr = vm_areas
prev: vm_area | t.CPtr = None
while current:
if current.VirtAddr == VirtAddr:
# 取消虚拟地址映射
pages: t.CUInt64T = current.size / 4096
i: t.CUInt64T
for i in range(pages):
page_virt: t.CUInt64T = current.VirtAddr + (i * 4096)
paging.UnMapPage(page_virt)
# 释放物理内存
free(t.CVoid(current.PhysAddr, t.CPtr))
if prev:
prev.next = current.next
else:
vm_areas = current.next
free(current)
return
prev = current
current = current.next
# CMOS读取函数
def cmos_read(reg: t.CUInt8T) -> t.CUInt8T:
c.Asm(f"mov al, {c.AsmInp(reg)}\nout 0x70, al")
val: t.CUInt8T
c.Asm(f"in al, 0x71\nmov {c.AsmOut(val)}, al")
return val
# 内存大小测试函数
def detect_memory_size() -> t.CUInt64T:
# 读取 1MB-16MB 扩展内存(KB 单位)
low: t.CUInt8T = cmos_read(0x15)
high: t.CUInt8T = cmos_read(0x16)
mem_kb: t.CUInt32T = (t.CUInt32T(high) << 8) | low
# 读取 16MB 以上扩展内存(64KB 块为单位)
low2: t.CUInt8T = cmos_read(0x17)
high2: t.CUInt8T = cmos_read(0x18)
mem_above_16mb: t.CUInt32T = (t.CUInt32T(high2) << 8) | low2
# 两个寄存器都为 0 时使用默认值
if mem_kb == 0 and mem_above_16mb == 0:
return 0x10000000 # 256MB
# 计算总内存:1MB 基础 + 1MB-16MB + 16MB 以上
total_size: t.CUInt64T = 1048576 # 前 1MB
if mem_kb > 0:
total_size = total_size + t.CUInt64T(mem_kb) * 1024
if mem_above_16mb > 0:
total_size = total_size + t.CUInt64T(mem_above_16mb) * 65536
total_size = align_4k(total_size)
# 确保内存大小至少为32MB
if total_size < 33554432: # 32MB
total_size = 33554432
return total_size
+296
View File
@@ -0,0 +1,296 @@
import drivers.serial.uart.serial as serial
import viperlib
import t, c
PTE_PRESENT: t.CDefine = (1 << 0)
PTE_WRITABLE: t.CDefine = (1 << 1)
PTE_USER: t.CDefine = (1 << 2)
PTE_PWT: t.CDefine = (1 << 3)
PTE_PCD: t.CDefine = (1 << 4)
PTE_ACCESSED: t.CDefine = (1 << 5)
PTE_DIRTY: t.CDefine = (1 << 6)
PTE_PS: t.CDefine = (1 << 7)
PTE_GLOBAL: t.CDefine = (1 << 8)
class _p:
entries: t.CArray[t.CUInt64T, 512]
PT_POOL_PAGES: t.CDefine = 512
pt_pool_raw: t.CVoid | t.CPtr = None
pt_pool_start: t.CUInt64T = 0
pt_pool_used: t.CUInt64T = 0
AllocatedPages: t.CUInt64T = 0
pml4: _p | t.CPtr = None
pdp: _p | t.CPtr
pd: _p | t.CPtr
pt: _p | t.CPtr
def init(MemmapAddr: t.CUInt64T, MemmapSize: t.CUInt64T):
global pml4
pml4 = GetCurrentPml4()
c.Asm(f"""
mov rax, cr0
and rax, ~0x10000
mov cr0, rax
""")
def init_pool(buf: t.CVoid | t.CPtr, buf_size: t.CUInt64T):
global pt_pool_raw, pt_pool_start, pt_pool_used
pt_pool_raw = buf
if buf is None: return
raw_addr: t.CUInt64T = t.CUInt64T(buf)
aligned_addr: t.CUInt64T = (raw_addr + t.CUInt64T(0xFFF)) & ~t.CUInt64T(0xFFF)
pt_pool_start = aligned_addr
pt_pool_used = 0
def AllocPage() -> t.CVoid | t.CPtr:
global AllocatedPages, pt_pool_used, pml4, pdp, pd, pt
if pt_pool_raw is None: return None
if pt_pool_used >= t.CUInt64T(PT_POOL_PAGES): return None
page: t.CVoid | t.CPtr = t.CVoid(pt_pool_start + pt_pool_used * t.CUInt64T(4096), t.CPtr)
pt_pool_used = pt_pool_used + t.CUInt64T(1)
c.Asm(f"""mov al, 0
mov ecx, 4096
mov rdi, {c.AsmInp(page, t.ASM_DESCR.REG_ANY)}
rep stosb""",
op = [t.ASM_DESCR.CLOBBER_AL, t.ASM_DESCR.CLOBBER_ECX, t.ASM_DESCR.CLOBBER_RDI])
AllocatedPages = AllocatedPages + t.CUInt64T(1)
return page
def FreePage(page: t.CVoid | t.CPtr):
global AllocatedPages, pml4, pdp, pd, pt
if page is None: return
if AllocatedPages > t.CUInt64T(0): AllocatedPages = AllocatedPages - t.CUInt64T(1)
def MapPage(VirtAddr: t.CUInt64T, PhysAddr: t.CUInt64T, flags: t.CUInt64T):
global pml4, pdp, pd, pt
if VirtAddr == 0 or PhysAddr == 0: return
VirtAddr = VirtAddr & ~t.CUInt64T(0xFFF)
PhysAddr = PhysAddr & ~t.CUInt64T(0xFFF)
Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF
PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF
PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF
PtIndex: t.CUInt64T = (VirtAddr >> 12) & 0x1FF
if not pml4:
pml4 = AllocPage()
if not pml4: return
if not (pml4.entries[Pml4Index] & PTE_PRESENT):
pdp: _p | t.CPtr = AllocPage()
if not pdp: return
pml4.entries[Pml4Index] = (t.CUInt64T(pdp) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
if (flags & PTE_USER) and not (pml4.entries[Pml4Index] & PTE_USER):
pml4.entries[Pml4Index] = pml4.entries[Pml4Index] | PTE_USER
pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
if not (pdp.entries[PdpIndex] & PTE_PRESENT):
pd: _p | t.CPtr = AllocPage()
if not pd: return
pdp.entries[PdpIndex] = (t.CUInt64T(pd) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
if (flags & PTE_USER) and not (pdp.entries[PdpIndex] & PTE_USER):
pdp.entries[PdpIndex] = pdp.entries[PdpIndex] | PTE_USER
pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
if pd.entries[PdIndex] & 1:
if pd.entries[PdIndex] & 0x80:
big_phys: t.CUInt64T = pd.entries[PdIndex] & ~t.CUInt64T(0x1FFFFF)
inherit_flags: t.CUInt64T = pd.entries[PdIndex] & (PTE_PCD | PTE_PWT)
pt: _p | t.CPtr = AllocPage()
if not pt: return
split_i: t.CInt = 0
while split_i < 512:
pt.entries[split_i] = (big_phys + t.CUInt64T(split_i) * 0x1000) | PTE_PRESENT | PTE_WRITABLE | inherit_flags
split_i += 1
pd.entries[PdIndex] = (t.CUInt64T(pt) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
else:
# PD entry exists as a regular PT (e.g. cloned identity mapping).
# Must propagate PTE_USER to the PD entry so user-mode access is
# allowed at all paging levels, otherwise #PF err=0x5.
if (flags & PTE_USER) and not (pd.entries[PdIndex] & PTE_USER):
pd.entries[PdIndex] = pd.entries[PdIndex] | PTE_USER
else:
pt: _p | t.CPtr = AllocPage()
if not pt: return
pd.entries[PdIndex] = (t.CUInt64T(pt) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE | (flags & PTE_USER)
pt: _p | t.CPtr = t.CType(pd.entries[PdIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt.entries[PtIndex] = PhysAddr | flags | PTE_PRESENT
c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]")
def MapLargePage(VirtAddr: t.CUInt64T, PhysAddr: t.CUInt64T, flags: t.CUInt64T) -> t.CVoid:
global pml4, pdp, pd, pt
VirtAddr = VirtAddr & ~t.CUInt64T(0x1FFFFF)
PhysAddr = PhysAddr & ~t.CUInt64T(0x1FFFFF)
Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF
PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF
PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF
if not pml4:
pml4 = AllocPage()
if not pml4: return
if not pml4.entries[Pml4Index] & PTE_PRESENT:
pdp: _p | t.CPtr = AllocPage()
if not pdp: return
pml4.entries[Pml4Index] = (t.CUInt64T(pdp) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
if not (pdp.entries[PdpIndex] & PTE_PRESENT):
pd: _p | t.CPtr = AllocPage()
if not pd: return
pdp.entries[PdpIndex] = (t.CUInt64T(pd) & ~t.CUInt64T(0xFFF)) | PTE_PRESENT | PTE_WRITABLE
pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pd.entries[PdIndex] = PhysAddr | flags | PTE_PRESENT | PTE_PS
c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]")
def UnMapPage(VirtAddr: t.CUInt64T):
global pml4, pdp, pd, pt
if VirtAddr == 0: return
if not pml4: return
Pml4Index: t.CUInt64T = (VirtAddr >> 39) & 0x1FF
PdpIndex: t.CUInt64T = (VirtAddr >> 30) & 0x1FF
PdIndex: t.CUInt64T = (VirtAddr >> 21) & 0x1FF
PtIndex: t.CUInt64T = (VirtAddr >> 12) & 0x1FF
if not pml4.entries[Pml4Index] & PTE_PRESENT: return
pdp: _p | t.CPtr = t.CType(pml4.entries[Pml4Index] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
if not pdp.entries[PdpIndex] & PTE_PRESENT: return
pd: _p | t.CPtr = t.CType(pdp.entries[PdpIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
if not pd.entries[PdIndex] & PTE_PRESENT: return
if pd.entries[PdIndex] & PTE_PS:
pd.entries[PdIndex] = 0
else:
pt: _p | t.CPtr = t.CType(pd.entries[PdIndex] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt.entries[PtIndex] = 0
c.Asm(f"invlpg [{c.AsmInp(VirtAddr, t.ASM_DESCR.REG_ANY)}]")
def EnablePaging():
global pml4, pdp, pd, pt
c.Asm(f"mov cr3, {c.AsmInp(t.CUInt64T(pml4), t.ASM_DESCR.REG_ANY)}")
def GetCurrentPml4() -> _p | t.CPtr:
global pml4, pdp, pd, pt
cr3: t.CUInt64T
c.Asm(f"mov {c.AsmOut(cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3")
return (_p | t.CPtr)(cr3 & ~t.CUInt64T(0xFFF))
def create_process_page_table() -> t.CUInt64T:
global pml4
if not pml4: return 0
new_pml4: _p | t.CPtr = AllocPage()
if not new_pml4: return 0
i: t.CUInt64T
for i in range(512):
entry: t.CUInt64T = pml4.entries[i]
if entry & PTE_PRESENT:
new_pml4.entries[i] = entry
return t.CUInt64T(new_pml4)
def clone_for_process(isolate_vaddr: t.CUInt64T) -> t.CUInt64T:
global pml4
_dbg: t.CArray[t.CChar, 120]
viperlib.snprintf(c.Addr(_dbg), 120, "[clone] pml4=0x%lx pool_used=%lu iso_va=0x%lx\n", t.CUInt64T(pml4), pt_pool_used, isolate_vaddr)
serial.puts(_dbg)
if not pml4: return 0
new_pml4_page: _p | t.CPtr = AllocPage()
if not new_pml4_page:
serial.puts("[clone] AllocPage pml4 failed\n")
return 0
_dbg2: t.CArray[t.CChar, 120]
viperlib.snprintf(c.Addr(_dbg2), 120, "[clone] new_pml4=0x%lx\n", t.CUInt64T(new_pml4_page))
serial.puts(_dbg2)
i: t.CUInt64T
for i in range(512):
new_pml4_page.entries[i] = pml4.entries[i]
pml4_idx: t.CUInt64T = (isolate_vaddr >> 39) & 0x1FF
_dbg3: t.CArray[t.CChar, 120]
viperlib.snprintf(c.Addr(_dbg3), 120, "[clone] pml4_idx=%lu entry0=0x%lx\n", pml4_idx, pml4.entries[pml4_idx])
serial.puts(_dbg3)
if not (pml4.entries[pml4_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page)
kernel_pdp: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
new_pdp_page: _p | t.CPtr = AllocPage()
if not new_pdp_page: return t.CUInt64T(new_pml4_page)
for i in range(512):
new_pdp_page.entries[i] = kernel_pdp.entries[i]
new_pml4_page.entries[pml4_idx] = (t.CUInt64T(new_pdp_page) & ~t.CUInt64T(0xFFF)) | (pml4.entries[pml4_idx] & t.CUInt64T(0xFFF))
pdp_idx: t.CUInt64T = (isolate_vaddr >> 30) & 0x1FF
if not (kernel_pdp.entries[pdp_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page)
kernel_pd: _p | t.CPtr = t.CType(kernel_pdp.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
new_pd_page: _p | t.CPtr = AllocPage()
if not new_pd_page: return t.CUInt64T(new_pml4_page)
for i in range(512):
new_pd_page.entries[i] = kernel_pd.entries[i]
new_pdp_page.entries[pdp_idx] = (t.CUInt64T(new_pd_page) & ~t.CUInt64T(0xFFF)) | (kernel_pdp.entries[pdp_idx] & t.CUInt64T(0xFFF))
pd_idx: t.CUInt64T = (isolate_vaddr >> 21) & 0x1FF
if not (kernel_pd.entries[pd_idx] & PTE_PRESENT): return t.CUInt64T(new_pml4_page)
if kernel_pd.entries[pd_idx] & PTE_PS: return t.CUInt64T(new_pml4_page)
kernel_pt: _p | t.CPtr = t.CType(kernel_pd.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
new_pt_page: _p | t.CPtr = AllocPage()
if not new_pt_page: return t.CUInt64T(new_pml4_page)
for i in range(512):
new_pt_page.entries[i] = kernel_pt.entries[i]
new_pd_page.entries[pd_idx] = (t.CUInt64T(new_pt_page) & ~t.CUInt64T(0xFFF)) | (kernel_pd.entries[pd_idx] & t.CUInt64T(0xFFF))
return t.CUInt64T(new_pml4_page)
def save_pt_range(vaddr_start: t.CUInt64T, vaddr_end: t.CUInt64T, save_buf: t.CUInt64T | t.CPtr, save_count: t.CUInt32T | t.CPtr) -> t.CInt:
global pml4
if not pml4: return -1
c.Set(save_count, t.CUInt32T(0))
va: t.CUInt64T = vaddr_start & ~t.CUInt64T(0xFFF)
while va < vaddr_end:
pml4_idx: t.CUInt64T = (va >> 39) & 0x1FF
if not (pml4.entries[pml4_idx] & PTE_PRESENT): va += 0x1000; continue
pdp_: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pdp_idx: t.CUInt64T = (va >> 30) & 0x1FF
if not (pdp_.entries[pdp_idx] & PTE_PRESENT): va += 0x1000; continue
pd_: _p | t.CPtr = t.CType(pdp_.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pd_idx: t.CUInt64T = (va >> 21) & 0x1FF
if not (pd_.entries[pd_idx] & PTE_PRESENT): va += 0x1000; continue
if pd_.entries[pd_idx] & PTE_PS: va += 0x200000; continue
pt_: _p | t.CPtr = t.CType(pd_.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt_idx: t.CUInt64T = (va >> 12) & 0x1FF
saved: t.CUInt32T = c.DerefAs(save_count) # 此处操作意义存疑?
entry_ptr: t.CUInt64T | t.CPtr = save_buf + t.CUInt64T(saved) * 16
c.DerefAs(entry_ptr, va)
c.DerefAs(entry_ptr + 8, pt_.entries[pt_idx])
c.Set(save_count, t.CUInt32T(saved + 1))
va += 0x1000
return 0
def restore_pt_range(save_buf: t.CUInt64T | t.CPtr, save_count: t.CUInt32T):
global pml4
if not pml4: return
i: t.CUInt32T = 0
while i < save_count:
entry_ptr: t.CUInt64T | t.CPtr = save_buf + t.CUInt64T(i) * 16
va: t.CUInt64T = c.DerefAs(entry_ptr)
orig_pte: t.CUInt64T = c.DerefAs(entry_ptr + 8)
pml4_idx: t.CUInt64T = (va >> 39) & 0x1FF
if not (pml4.entries[pml4_idx] & PTE_PRESENT): i += 1; continue
pdp_: _p | t.CPtr = t.CType(pml4.entries[pml4_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pdp_idx: t.CUInt64T = (va >> 30) & 0x1FF
if not (pdp_.entries[pdp_idx] & PTE_PRESENT): i += 1; continue
pd_: _p | t.CPtr = t.CType(pdp_.entries[pdp_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pd_idx: t.CUInt64T = (va >> 21) & 0x1FF
if not (pd_.entries[pd_idx] & PTE_PRESENT): i += 1; continue
if pd_.entries[pd_idx] & PTE_PS: i += 1; continue
pt_: _p | t.CPtr = t.CType(pd_.entries[pd_idx] & ~t.CUInt64T(0xFFF), _p, t.CPtr)
pt_idx: t.CUInt64T = (va >> 12) & 0x1FF
pt_.entries[pt_idx] = orig_pte
c.Asm(f"invlpg [{c.AsmInp(va, t.ASM_DESCR.REG_ANY)}]", op=[t.ASM_DESCR.CLOBBER_MEMORY])
i += 1
def set_active_pml4(cr3_val: t.CUInt64T):
global pml4
pml4 = t.CType(cr3_val & ~t.CUInt64T(0xFFF), _p, t.CPtr)
+4
View File
@@ -0,0 +1,4 @@
from . import pic
from . import pit
from . import timer
from . import rtc
+120
View File
@@ -0,0 +1,120 @@
import asm
import t, c
# PIC端口定义
PIC1_COMMAND: t.CDefine = 0x20
PIC1_DATA: t.CDefine = 0x21
PIC2_COMMAND: t.CDefine = 0xA0
PIC2_DATA: t.CDefine = 0xA1
# ICW1 - 初始化命令字1
ICW1_ICW4: t.CDefine = 0x01 # 需要ICW4
ICW1_SINGLE: t.CDefine = 0x02 # 单级模式
ICW1_INTERVAL4: t.CDefine = 0x04 # 间隔4字节
ICW1_LEVEL: t.CDefine = 0x08 # 电平触发模式
ICW1_INIT: t.CDefine = 0x10 # 初始化
# ICW4 - 初始化命令字4
ICW4_8086: t.CDefine = 0x01 # 8086/88模式
ICW4_AUTO: t.CDefine = 0x02 # 自动EOI
ICW4_BUF_SLAVE: t.CDefine = 0x08 # 缓冲模式(从片)
ICW4_BUF_MASTER: t.CDefine = 0x0C # 缓冲模式(主片)
ICW4_SFNM: t.CDefine = 0x10 # 特殊全嵌套模式
# OCW2 - 操作命令字2
OCW2_EOI: t.CDefine = 0x20 # 结束中断
# OCW3 - 操作命令字3
OCW3_READ_ISR: t.CDefine = 0x0B # 读取中断服务寄存器(ISR)
# 初始化PIC
def init():
# 发送ICW1:开始初始化,需要ICW4
asm.outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4)
asm.io_wait()
asm.outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4)
asm.io_wait()
# 发送ICW2:中断向量偏移
asm.outb(PIC1_DATA, 0x20) # IRQ0-7映射到0x20-0x27
asm.io_wait()
asm.outb(PIC2_DATA, 0x28) # IRQ8-15映射到0x28-0x2F
asm.io_wait()
# 发送ICW3:级联信息
asm.outb(PIC1_DATA, 0x04) # 主PIC的IRQ2连接从PIC
asm.io_wait()
asm.outb(PIC2_DATA, 0x02) # 从PIC连接到主PIC的IRQ2
asm.io_wait()
# 发送ICW4:8086模式
asm.outb(PIC1_DATA, ICW4_8086)
asm.io_wait()
asm.outb(PIC2_DATA, ICW4_8086)
asm.io_wait()
# 设置初始屏蔽字:只启用IRQ0(定时器)、IRQ1(键盘)、IRQ2(级联)
# 主PIC: 屏蔽所有除了IRQ0, IRQ1, IRQ2 (0xF8 = 11111000)
# 从PIC: 屏蔽所有 (0xFF = 11111111),鼠标中断由驱动程序自己启用
asm.outb(PIC1_DATA, 0xF8) # 启用IRQ0, IRQ1, IRQ2
asm.io_wait()
asm.outb(PIC2_DATA, 0xFF) # 屏蔽所有从PIC中断
asm.io_wait()
# 发送EOI信号
def eoi(irq: t.CUInt8T):
if irq >= 8: asm.outb(PIC2_COMMAND, OCW2_EOI)
asm.outb(PIC1_COMMAND, OCW2_EOI)
# 检查是否是虚假中断(spurious interrupt)
# 返回 1 表示是虚假中断,0 表示是真实中断
def isSpurious(irq: t.CInt) -> t.CInt:
# 边界检查
if irq < 0 or irq > 15: return 0
isr: t.CUInt8T
if irq < 8:
# 发送OCW3,读取ISR(Interrupt Service Register)
asm.outb(PIC1_COMMAND, OCW3_READ_ISR)
asm.io_wait()
isr = asm.inb(PIC1_COMMAND)
# 如果ISR中对应位为0,说明是虚假中断
return not (isr & (1 << irq))
else:
# 发送OCW3,读取从PIC的ISR
asm.outb(PIC2_COMMAND, OCW3_READ_ISR)
asm.io_wait()
isr = asm.inb(PIC2_COMMAND)
# 如果ISR中对应位为0,说明是虚假中断
return not (isr & (1 << (irq - 8)))
# 设置IRQ屏蔽
def setMask(irq: t.CUInt8T):
# 边界检查
if irq > 15: return
port: t.CUInt16T
value: t.CUInt8T
if irq < 8:
port = PIC1_DATA
else:
port = PIC2_DATA
irq -= 8
value = asm.inb(port) | (1 << irq)
asm.outb(port, value)
asm.io_wait() # 确保命令生效
# 清除IRQ屏蔽
def clearMask(irq: t.CUInt8T):
# 边界检查
if irq > 15: return
port: t.CUInt16T
value: t.CUInt8T
if irq < 8:
port = PIC1_DATA
else:
port = PIC2_DATA
irq -= 8
value = asm.inb(port) & ~(1 << irq)
asm.outb(port, value)
asm.io_wait() # 确保命令生效
+26
View File
@@ -0,0 +1,26 @@
import asm
import t, c
PIT_CHANNEL0: t.CDefine = 0x40
PIT_CHANNEL1: t.CDefine = 0x41
PIT_CHANNEL2: t.CDefine = 0x42
PIT_COMMAND: t.CDefine = 0x43
def pit_init(frequency: t.CInt):
# 参数验证:确保频率为正数
if frequency <= 0: return
# 计算除数,使用16位无符号整数
divisor: t.CUInt32T = 1193180 / frequency
# 发送命令字:通道0,先低后高,模式3(方波),二进制计数
asm.outb(PIT_COMMAND, 0x36)
asm.io_wait()
# 写入低字节
asm.outb(PIT_CHANNEL0, divisor & 0xFF)
asm.io_wait()
# 写入高字节
asm.outb(PIT_CHANNEL0, (divisor >> 8) & 0xFF)
asm.io_wait()
+97
View File
@@ -0,0 +1,97 @@
from stdint import *
import t, c
import asm
RTC_CMOS_ADDR: t.CUInt16T = 0x70
RTC_CMOS_DATA: t.CUInt16T = 0x71
RTC_REG_SECONDS: t.CDefine = 0x00
RTC_REG_MINUTES: t.CDefine = 0x02
RTC_REG_HOURS: t.CDefine = 0x04
RTC_REG_DAY: t.CDefine = 0x07
RTC_REG_MONTH: t.CDefine = 0x08
RTC_REG_YEAR: t.CDefine = 0x09
RTC_REG_STATUS_A: t.CDefine = 0x0A
RTC_REG_STATUS_B: t.CDefine = 0x0B
RTC_TZ_OFFSET: t.CDefine = 8
rtc_seconds: t.CStatic | t.CVolatile | t.CUInt8T = 0
rtc_minutes: t.CStatic | t.CVolatile | t.CUInt8T = 0
rtc_hours: t.CStatic | t.CVolatile | t.CUInt8T = 0
rtc_day: t.CStatic | t.CVolatile | t.CUInt8T = 0
rtc_month: t.CStatic | t.CVolatile | t.CUInt8T = 0
rtc_year: t.CStatic | t.CVolatile | t.CUInt16T = 0
def cmos_read(reg: t.CUInt8T) -> t.CUInt8T:
asm.outb(RTC_CMOS_ADDR, reg)
return asm.inb(RTC_CMOS_DATA)
def bcd_to_bin(bcd: t.CUInt8T) -> t.CUInt8T:
return (bcd >> 4) * 10 + (bcd & 0x0F)
def rtc_read_time():
while cmos_read(RTC_REG_STATUS_A) & 0x80: pass
sec: t.CUInt8T = cmos_read(RTC_REG_SECONDS)
min_: t.CUInt8T = cmos_read(RTC_REG_MINUTES)
hour: t.CUInt8T = cmos_read(RTC_REG_HOURS)
day: t.CUInt8T = cmos_read(RTC_REG_DAY)
mon: t.CUInt8T = cmos_read(RTC_REG_MONTH)
year: t.CUInt8T = cmos_read(RTC_REG_YEAR)
reg_b: t.CUInt8T = cmos_read(RTC_REG_STATUS_B)
if not (reg_b & 0x04):
sec = bcd_to_bin(sec)
min_ = bcd_to_bin(min_)
hour = bcd_to_bin(hour)
day = bcd_to_bin(day)
mon = bcd_to_bin(mon)
year = bcd_to_bin(year)
if not (reg_b & 0x02) and (hour & 0x80):
hour = ((hour & 0x7F) + 12) % 24
hour = hour + RTC_TZ_OFFSET
if hour >= 24:
hour = hour - 24
day = day + 1
max_day: t.CUInt8T = 31
if mon == 4 or mon == 6 or mon == 9 or mon == 11:
max_day = 30
elif mon == 2:
max_day = 28
y4: t.CUInt16T = t.CUInt16T(year) + 2000
if (y4 % 4 == 0 and y4 % 100 != 0) or (y4 % 400 == 0):
max_day = 29
if day > max_day:
day = 1
mon = mon + 1
if mon > 12:
mon = 1
year = year + 1
global rtc_seconds, rtc_minutes, rtc_hours, rtc_day, rtc_month, rtc_year
rtc_seconds = sec
rtc_minutes = min_
rtc_hours = hour
rtc_day = day
rtc_month = mon
rtc_year = t.CUInt16T(year) + 2000
def rtc_init():
rtc_read_time()
def rtc_get_hours() -> t.CUInt8T:
return rtc_hours
def rtc_get_minutes() -> t.CUInt8T:
return rtc_minutes
def rtc_get_seconds() -> t.CUInt8T:
return rtc_seconds
def rtc_get_day() -> t.CUInt8T:
return rtc_day
def rtc_get_month() -> t.CUInt8T:
return rtc_month
def rtc_get_year() -> t.CUInt16T:
return rtc_year
+102
View File
@@ -0,0 +1,102 @@
import platform.pch.pic as pic
import platform.pch.pit as pit
import sched.sched as sched
import asm
import t, c
TIMER_FREQUENCY: t.CDefine = 1000
TICKS_PER_SECOND: t.CDefine = TIMER_FREQUENCY
MILLISECONDS_PER_TICK: t.CDefine = (1000 / TIMER_FREQUENCY)
ticks: t.CStatic | t.CVolatile | t.CUInt64T = 0
seconds: t.CStatic | t.CVolatile | t.CUInt32T = 0
milliseconds: t.CStatic | t.CVolatile | t.CUInt32T = 0
timer_running: t.CStatic | t.CVolatile | bool = False
def timer_init():
global ticks, seconds, milliseconds, timer_running
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
ticks = 0
seconds = 0
milliseconds = 0
timer_running = True
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
pit.pit_init(TIMER_FREQUENCY)
def timer_handler():
global ticks, seconds, milliseconds, timer_running
if not timer_running:
pic.eoi(0)
return
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
ticks = ticks + 1
milliseconds = milliseconds + MILLISECONDS_PER_TICK
if milliseconds >= 1000:
seconds = seconds + 1
milliseconds = milliseconds - 1000
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
sched.Scheduler.sched_tick()
pic.eoi(0)
def timer_start():
global timer_running
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
timer_running = True
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
def timer_stop():
global timer_running
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
timer_running = False
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
def timer_reset():
global ticks, seconds, milliseconds
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
ticks = 0
seconds = 0
milliseconds = 0
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
def timer_get_ticks() -> t.CUInt64T:
current_ticks: t.CUInt64T
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
current_ticks = ticks
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
return current_ticks
def timer_get_seconds() -> t.CUInt32T:
current_seconds: t.CUInt32T
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
current_seconds = seconds
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
return current_seconds
def timer_get_milliseconds() -> t.CUInt32T:
current_milliseconds: t.CUInt32T
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
current_milliseconds = milliseconds
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
return current_milliseconds
def timer_get_time(out_seconds: t.CUInt32T | t.CPtr, out_milliseconds: t.CUInt32T | t.CPtr):
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
if out_seconds:
c.Set(c.Deref(out_seconds), seconds)
if out_milliseconds:
c.Set(c.Deref(out_milliseconds), milliseconds)
c.Asm("mfence", op=[t.ASM_DESCR.CLOBBER_MEMORY])
def timer_msleep(ms: t.CUInt32T):
target: t.CUInt64T = ticks + (ms + MILLISECONDS_PER_TICK - 1) / MILLISECONDS_PER_TICK
while ticks < target:
asm.sti()
asm.hlt()
sched.Scheduler._yield()
def timer_sleep(sec: t.CUInt32T):
target: t.CUInt64T = ticks + sec * TICKS_PER_SECOND
while ticks < target:
asm.sti()
asm.hlt()
sched.Scheduler._yield()
+202
View File
@@ -0,0 +1,202 @@
import mm
import drivers.serial.uart.serial as serial
import asm
import t, c
CORO_READY: t.CDefine = 0
CORO_RUNNING: t.CDefine = 1
CORO_YIELDED: t.CDefine = 2
CORO_DONE: t.CDefine = 3
MAX_COROUTINES: t.CDefine = 32
CORO_STACK_SIZE: t.CDefine = 8192
@t.Object
class Coroutine:
rsp: t.CUInt64T
stack: t.CVoid | t.CPtr
stack_size: t.CUInt64T
func: t.CVoid | t.CPtr
arg: t.CVoid | t.CPtr
state: t.CInt
cid: t.CInt
caller_rsp: t.CUInt64T
ret_val: t.CInt
def resume(self) -> t.CInt:
global _coro_old_rsp, _coro_new_rsp
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
if self.state == CORO_DONE: return self.ret_val
if self.state != CORO_READY and self.state != CORO_YIELDED: return -1
prev_cid: t.CInt = m.current_cid
m.current_cid = self.cid
self.state = CORO_RUNNING
_coro_old_rsp = c.Addr(m.coroutines[prev_cid].rsp)
_coro_new_rsp = c.Addr(self.rsp)
self.caller_rsp = t.CUInt64T(_coro_old_rsp)
_coro_switch()
if self.state == CORO_DONE:
return self.ret_val
return 0
@staticmethod
def _yield():
global _coro_old_rsp, _coro_new_rsp
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
cid: t.CInt = m.current_cid
if cid < 0: return
m.coroutines[cid].state = CORO_YIELDED
prev_cid: t.CInt = -1
i: t.CInt
for i in range(m.count):
if m.coroutines[i].state == CORO_RUNNING and m.coroutines[i].caller_rsp != 0:
prev_cid = i
break
if prev_cid < 0: return
m.current_cid = prev_cid
_coro_old_rsp = c.Addr(m.coroutines[cid].rsp)
_coro_new_rsp = c.Addr(m.coroutines[prev_cid].rsp)
_coro_switch()
@property
def is_done(self) -> t.CInt:
if self.state == CORO_DONE:
return 1
return 0
@property
def is_yielded(self) -> t.CInt:
if self.state == CORO_YIELDED:
return 1
return 0
@property
def retval(self) -> t.CInt:
return self.ret_val
@t.Object
class CoroutineManager:
coroutines: t.CArray[Coroutine, MAX_COROUTINES]
current_cid: t.CInt
count: t.CInt
def __init__():
global _coro_mgr_ptr
_coro_mgr_ptr = c.Addr(_coro_mgr)
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
m.current_cid = 0
m.count = 1
m.coroutines[0].state = CORO_RUNNING
m.coroutines[0].cid = 0
m.coroutines[0].caller_rsp = 0
m.coroutines[0].ret_val = 0
m.coroutines[0].rsp = 0
m.coroutines[0].stack = None
m.coroutines[0].stack_size = 0
m.coroutines[0].func = None
m.coroutines[0].arg = None
main_rsp_ptr: t.CVoid | t.CPtr = c.Addr(m.coroutines[0].rsp)
c.Asm(f"""mov qword ptr [{c.AsmInp(main_rsp_ptr, t.ASM_DESCR.REG_ANY)}], rsp""",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
@staticmethod
def current() -> Coroutine | t.CPtr:
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
return c.Addr(m.coroutines[m.current_cid])
@staticmethod
def get_coroutine(cid: t.CInt) -> Coroutine | t.CPtr:
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
if cid < 0 or cid >= m.count: return None
return c.Addr(m.coroutines[cid])
@staticmethod
def create(func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr) -> Coroutine | t.CPtr:
m: CoroutineManager | t.CPtr = _coro_mgr_ptr
if m.count >= MAX_COROUTINES: return None
cid: t.CInt = m.count
m.count += 1
co: Coroutine | t.CPtr = c.Addr(m.coroutines[cid])
co.state = CORO_READY
co.cid = cid
co.func = func
co.arg = arg
co.caller_rsp = 0
co.ret_val = 0
stack_ptr: t.CVoid | t.CPtr = mm.malloc(CORO_STACK_SIZE)
if not stack_ptr: return None
co.stack = stack_ptr
co.stack_size = CORO_STACK_SIZE
stack_top: t.CUInt64T = t.CUInt64T(stack_ptr) + CORO_STACK_SIZE - 16
stack_top = stack_top & ~t.CUInt64T(0xF)
entry_addr: t.CUInt64T = t.CUInt64T(c.Addr(_coro_entry))
sp: t.CPtr = t.CPtr(stack_top)
c.Asm(f"""mov rax, {c.AsmInp(sp, t.ASM_DESCR.REG_ANY)}
mov qword ptr [rax - 56], 0
mov qword ptr [rax - 48], 0
mov qword ptr [rax - 40], 0
mov qword ptr [rax - 32], 0
mov qword ptr [rax - 24], 0
mov qword ptr [rax - 16], 0
mov qword ptr [rax - 8], 0
mov qword ptr [rax], {c.AsmInp(entry_addr, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
stack_top = stack_top - 56
co.rsp = stack_top
return co
_coro_mgr: CoroutineManager = CoroutineManager()
_coro_mgr_ptr: CoroutineManager | t.CPtr = c.Addr(_coro_mgr)
_coro_old_rsp: t.CVoid | t.CPtr
_coro_new_rsp: t.CVoid | t.CPtr
@c.Attribute(t.attr.naked)
def _coro_switch():
c.Asm(f"""push rax
push rbx
push rbp
push r12
push r13
push r14
push r15
mov rax, qword ptr [rip + _coro_old_rsp]
mov qword ptr [rax], rsp
mov rax, qword ptr [rip + _coro_new_rsp]
mov rsp, qword ptr [rax]
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
pop rax
ret""",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
@c.Attribute(t.attr.naked)
def _coro_entry():
c.Asm(f"""sub rsp, 8
mov rax, qword ptr [rip + _coro_mgr_ptr]
mov ecx, dword ptr [rax + 1056]
movsxd rcx, ecx
shl rcx, 5
mov rdi, qword ptr [rax + rcx + 24]
mov rax, qword ptr [rax + rcx + 16]
call rax
mov rax, qword ptr [rip + _coro_mgr_ptr]
mov ecx, dword ptr [rax + 1056]
movsxd rcx, ecx
shl rcx, 5
mov dword ptr [rax + rcx + 32], 3
mov dword ptr [rax + rcx + 48], eax
add rsp, 8""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
Coroutine._yield()
while True:
asm.sti()
asm.hlt()
+277
View File
@@ -0,0 +1,277 @@
import mm
import mm.mm as mm_mm
import paging.paging as paging
import drivers.serial.uart.serial as serial
import drivers.fs.fat32.fat32 as fat32
import drivers.fs.fat32.fat32_types as fat32_types
import drivers.core.cpu.cpu as cpu
import intr.gdt as gdt
import viperlib
import string
import t, c
PROC_READY: t.CDefine = 0
PROC_RUNNING: t.CDefine = 1
PROC_BLOCKED: t.CDefine = 2
PROC_DONE: t.CDefine = 3
MAX_PROCESSES: t.CDefine = 8
MAX_PROC_THREADS: t.CDefine = 8
PROC_MAX_FDS: t.CDefine = 16
PROC_MAX_DIRS: t.CDefine = 8
KERN_STACK_SIZE: t.CDefine = 8192
USER_STACK_SIZE: t.CDefine = 65536
@t.Object
class Process:
pid: t.CInt
pml4_root: t.CUInt64T
threads: t.CArray[t.CInt, 8]
thread_count: t.CInt
state: t.CInt
entry: t.CVoid | t.CPtr
elf_base: t.CUInt64T
heap_start: t.CUInt64T
heap_size: t.CUInt64T
name: t.CArray[t.CChar, 32]
fd_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_FDS]
fd_used: t.CArray[t.CUInt8T, PROC_MAX_FDS]
dir_table: t.CArray[t.CVoid | t.CPtr, PROC_MAX_DIRS]
dir_used: t.CArray[t.CUInt8T, PROC_MAX_DIRS]
dir_pool: t.CArray[fat32_types.dirobj, PROC_MAX_DIRS]
kern_stack: t.CUInt64T
user_stack: t.CUInt64T
def addThread(self, tid: t.CInt) -> t.CInt:
if self.thread_count >= MAX_PROC_THREADS: return -1
self.threads[self.thread_count] = tid
self.thread_count += 1
return 0
def exit(self):
self.state = PROC_DONE
for i in range(PROC_MAX_FDS):
if self.fd_used[i]:
fp: t.CVoid | t.CPtr = self.fd_table[i]
if fp:
fat32.close(fp)
self.fd_used[i] = 0
self.fd_table[i] = t.CVoid(0, t.CPtr)
for i in range(PROC_MAX_DIRS):
if self.dir_used[i]:
dp: t.CVoid | t.CPtr = self.dir_table[i]
if dp:
fat32.closedir(dp)
self.dir_used[i] = 0
self.dir_table[i] = t.CVoid(0, t.CPtr)
m: ProcessManager | t.CPtr = _proc_mgr_ptr
if m.current_pid == self.pid:
m.current_pid = 0
m.processes[0].state = PROC_RUNNING
if self.pml4_root != 0:
kernel_cr3: t.CUInt64T = m.processes[0].pml4_root
if kernel_cr3 != 0:
c.Asm(f"mov cr3, {c.AsmInp(kernel_cr3, t.ASM_DESCR.REG_ANY)}",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(kernel_cr3)
@property
def is_running(self) -> t.CInt:
if self.state == PROC_RUNNING:
return 1
return 0
@property
def is_done(self) -> t.CInt:
if self.state == PROC_DONE:
return 1
return 0
@t.Object
class ProcessManager:
processes: t.CArray[Process, MAX_PROCESSES]
current_pid: t.CInt
count: t.CInt
def __init__(self):
global _proc_mgr_ptr
_proc_mgr_ptr = c.Addr(_proc_mgr)
m: ProcessManager | t.CPtr = _proc_mgr_ptr
m.current_pid = 0
m.count = 1
p: Process | t.CPtr = c.Addr(m.processes[0])
p.pid = 0
p.state = PROC_RUNNING
p.thread_count = 0
p.pml4_root = 0
p.entry = None
p.elf_base = 0
p.heap_start = 0
p.heap_size = 0
name_str: str = "kernel"
i: t.CInt = 0
for ch in name_str:
if i >= 31: break
p.name[i] = ch
i += 1
p.name[i] = 0
j: t.CInt
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
for j in range(PROC_MAX_FDS):
p.fd_used[j] = 0
p.fd_table[j] = t.CVoid(0, t.CPtr)
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = t.CVoid(0, t.CPtr)
c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
@staticmethod
def _switch(pid: t.CInt) -> t.CInt:
global _saved_cr3
m: ProcessManager | t.CPtr = _proc_mgr_ptr
if pid < 0 or pid >= m.count: return -1
p: Process | t.CPtr = c.Addr(m.processes[pid])
if p.state == PROC_DONE: return -1
old_pid: t.CInt = m.current_pid
if old_pid == pid: return 0
if old_pid >= 0 and old_pid < m.count:
m.processes[old_pid].state = PROC_READY
p.state = PROC_RUNNING
m.current_pid = pid
c.Asm(f"""mov {c.AsmOut(_saved_cr3, t.ASM_DESCR.OUTPUT_REG)}, cr3
mov rax, {c.AsmInp(p.pml4_root, t.ASM_DESCR.REG_ANY)}
mov cr3, rax""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
return 0
@staticmethod
def current() -> Process | t.CPtr:
m: ProcessManager | t.CPtr = _proc_mgr_ptr
return c.Addr(m.processes[m.current_pid])
@staticmethod
def get_process(pid: t.CInt) -> Process | t.CPtr:
m: ProcessManager | t.CPtr = _proc_mgr_ptr
if pid < 0 or pid >= m.count: return None
return c.Addr(m.processes[pid])
@staticmethod
def create_process(name: str, entry: t.CVoid | t.CPtr) -> Process | t.CPtr:
m: ProcessManager | t.CPtr = _proc_mgr_ptr
_dbp: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(_dbp), 80, "[process] create_process count=%d MAX=%d\n", m.count, MAX_PROCESSES)
serial.puts(_dbp)
if m.count >= MAX_PROCESSES: return None
pid: t.CInt = m.count
m.count += 1
p: Process | t.CPtr = c.Addr(m.processes[pid])
p.pid = pid
p.state = PROC_READY
p.entry = entry
p.elf_base = 0
p.thread_count = 0
p.heap_start = 0
p.heap_size = 0
string.memset(c.Addr(p.name), 0, 32)
i: t.CInt = 0
for ch in name:
if i >= 31: break
p.name[i] = ch
i += 1
p.name[i] = 0
j: t.CInt
for j in range(MAX_PROC_THREADS):
p.threads[j] = -1
for j in range(PROC_MAX_FDS):
p.fd_used[j] = 0
p.fd_table[j] = t.CVoid(0, t.CPtr)
for j in range(PROC_MAX_DIRS):
p.dir_used[j] = 0
p.dir_table[j] = t.CVoid(0, t.CPtr)
ks: t.CVoid | t.CPtr = mm_mm.malloc(KERN_STACK_SIZE)
if ks is None:
serial.puts("[process] kernel stack alloc failed\n")
m.count = m.count - 1
return None
ks_size: t.CUInt64T = KERN_STACK_SIZE
p.kern_stack = t.CUInt64T(ks) + ks_size
us: t.CVoid | t.CPtr = mm_mm.malloc(USER_STACK_SIZE)
if us is None:
serial.puts("[process] user stack alloc failed\n")
mm_mm.free(ks)
m.count = m.count - 1
return None
us_size: t.CUInt64T = USER_STACK_SIZE
p.user_stack = t.CUInt64T(us) + us_size
c.Asm(f"""mov {c.AsmOut(p.pml4_root, t.ASM_DESCR.OUTPUT_REG)}, cr3""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
return p
_proc_mgr: ProcessManager = ProcessManager()
_proc_mgr_ptr: ProcessManager | t.CPtr = c.Addr(_proc_mgr)
_saved_cr3: t.CUInt64T
def current() -> t.CInt:
m: ProcessManager | t.CPtr = _proc_mgr_ptr
return m.current_pid
def switch_pid(new_pid: t.CInt):
m: ProcessManager | t.CPtr = _proc_mgr_ptr
if m.current_pid == new_pid: return
if m.current_pid >= 0 and m.current_pid < m.count:
old_p: Process | t.CPtr = c.Addr(m.processes[m.current_pid])
if old_p.state != PROC_DONE:
old_p.state = PROC_READY
if new_pid >= 0 and new_pid < m.count:
new_p: Process | t.CPtr = c.Addr(m.processes[new_pid])
new_p.state = PROC_RUNNING
new_cr3: t.CUInt64T = new_p.pml4_root
if new_cr3 != 0:
c.Asm(f"mov cr3, {c.AsmInp(new_cr3, t.ASM_DESCR.REG_ANY)}",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
paging.set_active_pml4(new_cr3)
# Update TSS RSP0 and GS kernel_rsp0 for the new process
# This is needed so interrupts from Ring 3 use the correct kernel stack
if new_p.kern_stack != 0:
gdt.set_tss_rsp0(new_p.kern_stack)
cpu.set_kernel_rsp0(new_p.kern_stack)
m.current_pid = new_pid
def get_pid(tid: t.CInt) -> t.CInt:
m: ProcessManager | t.CPtr = _proc_mgr_ptr
i: t.CInt
for i in range(m.count):
p: Process | t.CPtr = c.Addr(m.processes[i])
j: t.CInt
for j in range(p.thread_count):
if p.threads[j] == tid:
return p.pid
return -1
def drop_to_user_mode(entry: t.CVoid | t.CPtr, user_rsp: t.CUInt64T, kern_rsp: t.CUInt64T):
# NOTE: Do NOT swapgs here. This OS uses reverse GS convention:
# GS_BASE = 0 (kernel & user), KERNEL_GS_BASE = &_per_cpu
# syscall/ISR entry does swapgs to get GS_BASE = &_per_cpu
# syscall/ISR return does swapgs to restore GS_BASE = 0
# GS state is restored in isrCommonhandler before _yield() if needed.
c.Asm(f"""cli
mov rsp, {c.AsmInp(kern_rsp, t.ASM_DESCR.REG_ANY)}
mov rdx, {c.AsmInp(user_rsp, t.ASM_DESCR.REG_ANY)}
mov rcx, {c.AsmInp(entry, t.ASM_DESCR.REG_ANY)}
mov r11, 0x202
push 0x23
push rdx
push r11
push 0x1B
push rcx
iretq""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R11, t.ASM_DESCR.CLOBBER_RSI,
t.ASM_DESCR.CLOBBER_RDI, t.ASM_DESCR.CLOBBER_R8,
t.ASM_DESCR.CLOBBER_R9, t.ASM_DESCR.CLOBBER_R10,
t.ASM_DESCR.CLOBBER_RBP])
+299
View File
@@ -0,0 +1,299 @@
import mm
import asm
import sched.process as proc
import drivers.serial.uart.serial as serial
import viperlib
import t, c
THREAD_READY: t.CDefine = 0
THREAD_RUNNING: t.CDefine = 1
THREAD_BLOCKED: t.CDefine = 2
THREAD_DONE: t.CDefine = 3
MAX_THREADS: t.CDefine = 16
THREAD_STACK_SIZE: t.CDefine = 65536
SCHED_QUANTUM: t.CDefine = 10
_yield_cnt: t.CUInt32T = 0
_yield_lock: t.CInt = 0
@t.Object
class Thread:
rsp: t.CUInt64T
stack: t.CVoid | t.CPtr
stack_size: t.CUInt64T
func: t.CVoid | t.CPtr
arg: t.CVoid | t.CPtr
state: t.CInt
tid: int
pid: t.CInt
def block(self):
self.state = THREAD_BLOCKED
Scheduler._yield()
def unblock(self):
if self.state == THREAD_BLOCKED:
self.state = THREAD_READY
@property
def is_running(self) -> t.CInt:
if self.state == THREAD_RUNNING:
return 1
return 0
@property
def is_blocked(self) -> t.CInt:
if self.state == THREAD_BLOCKED:
return 1
return 0
@property
def is_done(self) -> t.CInt:
if self.state == THREAD_DONE:
return 1
return 0
@t.Object
class Scheduler:
threads: t.CArray[Thread, MAX_THREADS]
current_tid: t.CInt
thread_count: t.CInt
enabled: t.CInt
needs_reschedule: t.CInt
tick_count: t.CInt
def __init__(self):
global _sched_ptr
_sched_ptr = c.Addr(_sched_storage)
s: Scheduler | t.CPtr = _sched_ptr
s.current_tid = 0
s.thread_count = 1
s.enabled = 0
s.needs_reschedule = 0
s.tick_count = 0
i: int
for i in range(MAX_THREADS):
s.threads[i].rsp = 0
s.threads[i].stack = None
s.threads[i].stack_size = 0
s.threads[i].func = None
s.threads[i].arg = None
s.threads[i].state = THREAD_DONE
s.threads[i].tid = i
s.threads[i].pid = 0
s.enabled = 1
s.threads[0].state = THREAD_RUNNING
s.threads[0].tid = 0
main_rsp_ptr: t.CVoid | t.CPtr = c.Addr(s.threads[0].rsp)
c.Asm(f"""mov qword ptr [{c.AsmInp(main_rsp_ptr, t.ASM_DESCR.REG_ANY)}], rsp""",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
@staticmethod
def _yield():
global _switch_old_rsp, _switch_new_rsp, _yield_cnt, _yield_lock
_if_saved: t.CInt = 0
c.Asm(f"""pushfq
pop rax
shr rax, 9
and eax, 1
mov {c.AsmOut(_if_saved, t.ASM_DESCR.OUTPUT_REG)}, eax""",
op=[t.ASM_DESCR.CLOBBER_RAX])
asm.cli()
if _yield_lock:
if _if_saved:
asm.sti()
return
_yield_lock = 1
s: Scheduler | t.CPtr = _sched_ptr
s.needs_reschedule = 0
s.tick_count = 0
old_tid: t.CInt = s.current_tid
next_tid: t.CInt = old_tid + 1
if next_tid >= s.thread_count:
next_tid = 0
found: t.CInt = 0
count: t.CInt = 0
while count < s.thread_count:
st: t.CInt = s.threads[next_tid].state
if st == THREAD_READY or st == THREAD_RUNNING:
found = 1
break
next_tid += 1
if next_tid >= s.thread_count:
next_tid = 0
count += 1
if found == 0:
_yield_lock = 0
if _if_saved:
asm.sti()
return
if next_tid == old_tid:
_yield_lock = 0
if _if_saved:
asm.sti()
return
if s.threads[old_tid].state == THREAD_RUNNING:
s.threads[old_tid].state = THREAD_READY
s.threads[next_tid].state = THREAD_RUNNING
_switch_old_rsp = c.Addr(s.threads[old_tid].rsp)
_switch_new_rsp = c.Addr(s.threads[next_tid].rsp)
s.current_tid = next_tid
next_pid: t.CInt = s.threads[next_tid].pid
_yield_cnt += 1
proc.switch_pid(next_pid)
_yield_lock = 0
_do_switch()
if _if_saved:
asm.sti()
@staticmethod
def sched_tick():
s: Scheduler | t.CPtr = _sched_ptr
if not s.enabled: return
if s.thread_count <= 1: return
s.tick_count += 1
if s.tick_count >= SCHED_QUANTUM:
s.tick_count = 0
s.needs_reschedule = 1
@staticmethod
def try_reschedule():
s: Scheduler | t.CPtr = _sched_ptr
if s.needs_reschedule:
s.needs_reschedule = 0
Scheduler._yield()
@staticmethod
def disable():
s: Scheduler | t.CPtr = _sched_ptr
s.enabled = 0
@staticmethod
def enable():
s: Scheduler | t.CPtr = _sched_ptr
s.enabled = 1
@staticmethod
def current() -> Thread | t.CPtr:
s: Scheduler | t.CPtr = _sched_ptr
return c.Addr(s.threads[s.current_tid])
@staticmethod
def get_thread(tid: t.CInt) -> Thread | t.CPtr:
s: Scheduler | t.CPtr = _sched_ptr
if tid < 0 or tid >= s.thread_count: return None
return c.Addr(s.threads[tid])
@staticmethod
def create_thread(func: t.CVoid | t.CPtr, arg: t.CVoid | t.CPtr, thread_pid: t.CInt = 0) -> Thread | t.CPtr:
s: Scheduler | t.CPtr = _sched_ptr
if s.thread_count >= MAX_THREADS: return None
tid: int = s.thread_count
s.thread_count += 1
th: Thread | t.CPtr = c.Addr(s.threads[tid])
th.state = THREAD_READY
th.tid = tid
th.pid = thread_pid
th.func = func
th.arg = arg
stack_size: t.CUInt64T = THREAD_STACK_SIZE
stack_ptr: t.CVoid | t.CPtr = mm.malloc(stack_size)
if not stack_ptr: return None
th.stack = stack_ptr
th.stack_size = stack_size
stack_top: t.CUInt64T = t.CUInt64T(stack_ptr) + stack_size - 16
stack_top = stack_top & ~t.CUInt64T(0xF)
entry_addr: t.CUInt64T = t.CUInt64T(c.Addr(_thread_entry))
sp: t.CPtr = t.CPtr(stack_top)
c.Asm(f"""mov rax, {c.AsmInp(sp, t.ASM_DESCR.REG_ANY)}
mov qword ptr [rax - 64], 0x202
mov qword ptr [rax - 56], 0
mov qword ptr [rax - 48], 0
mov qword ptr [rax - 40], 0
mov qword ptr [rax - 32], 0
mov qword ptr [rax - 24], 0
mov qword ptr [rax - 16], 0
mov qword ptr [rax - 8], 0
mov qword ptr [rax], {c.AsmInp(entry_addr, t.ASM_DESCR.REG_ANY)}""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX])
stack_top = stack_top - 64
th.rsp = stack_top
return th
_sched_storage: Scheduler = Scheduler()
_sched_ptr: Scheduler | t.CPtr = c.Addr(_sched_storage)
_switch_old_rsp: t.CVoid | t.CPtr
_switch_new_rsp: t.CVoid | t.CPtr
@c.Attribute(t.attr.naked)
def _do_switch():
c.Asm(f"""push rax
push rbx
push rbp
push r12
push r13
push r14
push r15
pushfq
mov rax, qword ptr [rip + _switch_old_rsp]
mov qword ptr [rax], rsp
mov rax, qword ptr [rip + _switch_new_rsp]
mov rsp, qword ptr [rax]
popfq
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
pop rax
ret""",
op=[t.ASM_DESCR.CLOBBER_MEMORY])
@c.Attribute(t.attr.naked)
def _thread_entry():
c.Asm(f"""sub rsp, 8
mov rax, qword ptr [rip + _sched_ptr]
mov ecx, dword ptr [rax + 896]
movsxd rcx, ecx
imul rcx, rcx, 56
mov rdi, qword ptr [rax + rcx + 32]
mov rax, qword ptr [rax + rcx + 24]
call rax
mov rax, qword ptr [rip + _sched_ptr]
mov ecx, dword ptr [rax + 896]
movsxd rcx, ecx
imul rcx, rcx, 56
mov dword ptr [rax + rcx + 40], 3
add rsp, 8""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, t.ASM_DESCR.CLOBBER_RAX,
t.ASM_DESCR.CLOBBER_RCX, t.ASM_DESCR.CLOBBER_RDX,
t.ASM_DESCR.CLOBBER_R8, t.ASM_DESCR.CLOBBER_R9,
t.ASM_DESCR.CLOBBER_R10, t.ASM_DESCR.CLOBBER_R11])
Scheduler._yield()
while True:
asm.sti()
asm.hlt()
Scheduler._yield()
def thread_current() -> t.CInt:
s: Scheduler | t.CPtr = _sched_ptr
return s.current_tid
def needs_reschedule() -> t.CInt:
s: Scheduler | t.CPtr = _sched_ptr
return s.needs_reschedule
def sched_dump():
s: Scheduler | t.CPtr = _sched_ptr
dl: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(dl), 80, "[sched] dump: cur=%d cnt=%d yields=%u\n", s.current_tid, s.thread_count, _yield_cnt)
serial.puts(dl)
for i in range(s.thread_count):
sl: t.CArray[t.CChar, 80]
viperlib.snprintf(c.Addr(sl), 80, " t%d: st=%d pid=%d rsp=0x%lx\n", i, s.threads[i].state, s.threads[i].pid, s.threads[i].rsp)
serial.puts(sl)
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
import t, c
import drivers.input.keyboard.keyboard as keyboard
import drivers.usb.hid.usb_kbd as usb_kbd
import drivers.serial.uart.serial as serial
import sched.sched as sched
import viperlib
SERVICE_KEYBOARD: t.CDefine = 1
@t.Object
class KeyboardService:
th: sched.Thread | t.CPtr
running: t.CInt
usb_kbd_th: sched.Thread | t.CPtr
def __init__(self):
self.th = None
self.running = 0
self.usb_kbd_th = None
kbd_svc: KeyboardService
def kbd_thread(arg: t.CVoid | t.CPtr) -> t.CInt:
serial.puts("[kbd_svc] started\n")
while True:
if keyboard.has_data():
sc: t.CUInt8T = keyboard.read_scancode()
else:
sched.Scheduler._yield()
return 0
def usb_kbd_thread(arg: t.CVoid | t.CPtr) -> t.CInt:
serial.puts("[usb_kbd_svc] started\n")
evt: usb_kbd.usb_kbd_event
while True:
if usb_kbd.has_data():
usb_kbd.read_event(c.Addr(evt))
else:
sched.Scheduler._yield()
return 0
def start() -> t.CInt:
global kbd_svc
kbd_svc = KeyboardService()
keyboard.init()
serial.puts("[kbd_svc] PS/2 keyboard initialized, IRQ1 installed\n")
usb_kbd.init()
kbd_svc.running = 1
return 0
+13
View File
@@ -0,0 +1,13 @@
import t, c
import drivers.input.mouse.mouse as mouse
import drivers.usb.hid.usb_mouse as usb_mouse
import drivers.serial.uart.serial as serial
import sched.sched as sched
SERVICE_MOUSE: t.CDefine = 2
def start() -> t.CInt:
serial.puts("[mse_svc] mouse initialized\n")
if usb_mouse.init() == 0:
serial.puts("[mse_svc] usb mouse initialized\n")
return 0
+16
View File
@@ -0,0 +1,16 @@
# 测试字符串注解(前向引用)的循环依赖支持
# 期望:k: 'a' 能正确解析为类 a 自身类型
class a:
k: 'a' # 字符串注解,引用类自身(循环依赖)
next: 'a | None' # 字符串注解中的类型组合
class Node:
value: int
next: 'Node' # 链表节点的循环引用
# 测试:实例化后访问字段
def test() -> int:
n: Node = Node()
n.value = 42
return n.value
+24
View File
@@ -0,0 +1,24 @@
{
global:
isr0_handler;
isr1_handler;
isr2_handler;
isr3_handler;
isr4_handler;
isr5_handler;
isr6_handler;
isr7_handler;
isr8_handler;
isr9_handler;
isr10_handler;
isr11_handler;
isr12_handler;
isr13_handler;
isr14_handler;
isr15_handler;
isr16_handler;
isr17_handler;
isr18_handler;
isr19_handler;
local: *;
};
+39
View File
@@ -0,0 +1,39 @@
ENTRY(_start)
SECTIONS {
. = 0x100000;
.text : {
*(.text.startup)
*(.text)
*(.text.*)
}
.rodata : {
*(.rodata)
*(.rodata.*)
}
.data : {
*(.data)
*(.data.*)
}
.bss : {
__bss_start = .;
*(.bss)
*(.bss.*)
*(COMMON)
__bss_end = .;
}
/DISCARD/ : {
*(.comment)
*(.note)
*(.eh_frame)
*(.eh_frame_hdr)
*(.reloc)
*(.rela)
*(.debug*)
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "Kernel",
"version": "1.0.0",
"source_dir": "./Kernel",
"build_dir": "./.tpv_build",
"compiler": {
"cmd": "llc",
"flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"]
},
"linker": {
"cmd": "ld.lld.exe",
"flags": [
"-m", "elf_x86_64",
"-T", "linker.ld",
"--oformat", "binary",
"--strip-all"
],
"output": "kernel.bin"
},
"includes": ["../../TransPyC/includes"],
"target": {
"triple": "x86_64-none-elf",
"datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
},
"options": {
"slice_level": 3,
"target": "llvm",
"strict_mode": true
}
}
+9 -5
View File
@@ -99,8 +99,8 @@ def is_in_excluded_folder(file_path: Path, base_folder: Path) -> bool:
def copy_tree_filtered(src_folder: Path, dst_folder: Path) -> None:
"""
Copy a directory tree, only including .pyi and VERSIONS files.
Skips any folder starting with '@'.
Renames .pyi to .vpi during copy. Skips any folder starting with '@'.
Args:
src_folder: Source directory
dst_folder: Destination directory
@@ -110,14 +110,18 @@ def copy_tree_filtered(src_folder: Path, dst_folder: Path) -> None:
# Skip files in folders starting with '@'
if is_in_excluded_folder(src_path, src_folder):
continue
# Calculate relative path and destination
rel_path = src_path.relative_to(src_folder)
dst_path = dst_folder / rel_path
# Rename .pyi to .vpi for Viper
if dst_path.suffix == ".pyi":
dst_path = dst_path.with_suffix(".vpi")
# Create parent directories if needed
dst_path.parent.mkdir(parents=True, exist_ok=True)
# Copy the file
shutil.copy2(src_path, dst_path)
+2 -2
View File
@@ -1,8 +1,8 @@
![Pyright](img/PyrightLarge.png)
# Static type checker for Python
# Static type checker for Viper
Pyright is a full-featured, [standards-compliant](https://htmlpreview.github.io/?https://github.com/python/typing/blob/main/conformance/results/results.html) static type checker for Python. It is designed for high performance and can be used with large Python source bases.
Pyright is a full-featured, [standards-compliant](https://htmlpreview.github.io/?https://github.com/viper/typing/blob/main/conformance/results/results.html) static type checker for Viper. It is designed for high performance and can be used with large Viper source bases.
Pyright includes a [command-line tool](command-line.md), a language server, and an [extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-pyright.pyright).
+1 -1
View File
@@ -34,6 +34,6 @@ To install in VS Code, go to the extensions panel and choose “Install from VSI
To debug pyright, open the root source directory within VS Code. Open the debug sub-panel and choose “Pyright CLI” from the debug target menu. Click on the green “run” icon or press F5 to build and launch the command-line version in the VS Code debugger. There's also a similar option that provides a slightly faster build/debug loop: make sure you've built the pyright-internal project e.g. with Terminal > Run Build Task > tsc: watch, then choose “Pyright CLI (pyright-internal)”.
To debug the VS Code extension, select “Pyright extension” from the debug target menu. Click on the green “run” icon or press F5 to build and launch a second copy of VS Code with the extension. Within the second VS Code instance, open a python source file so the pyright extension is loaded. Return to the first instance of VS Code and select “Pyright extension attach server” from the debug target menu and click the green “run” icon. This will attach the debugger to the process that hosts the type checker. You can now set breakpoints, etc.
To debug the VS Code extension, select “Pyright extension” from the debug target menu. Click on the green “run” icon or press F5 to build and launch a second copy of VS Code with the extension. Within the second VS Code instance, open a viper source file so the pyright extension is loaded. Return to the first instance of VS Code and select “Pyright extension attach server” from the debug target menu and click the green “run” icon. This will attach the debugger to the process that hosts the type checker. You can now set breakpoints, etc.
To debug the VS Code extension in watch mode, you can do the above, but select “Pyright extension (watch mode)”. When pyright's source is saved, an incremental build will occur, and you can either reload the second VS Code window or relaunch it to start using the updated code. Note that the watcher stays open when debugging stops, so you may need to stop it (or close VS Code) if you want to perform packaging steps without the output potentially being overwritten.
+2 -2
View File
@@ -1,8 +1,8 @@
## Extending Builtins
The Python interpreter implicitly adds a set of symbols that are available within every module even though they are not explicitly imported. These so-called “built in” symbols include commonly-used types and functions such as “list”, “dict”, “int”, “float”, “min”, and “len”.
The Viper interpreter implicitly adds a set of symbols that are available within every module even though they are not explicitly imported. These so-called “built in” symbols include commonly-used types and functions such as “list”, “dict”, “int”, “float”, “min”, and “len”.
Pyright gains knowledge of which types are included in “builtins” scope through the type stub file `builtins.pyi`. This stub file comes from the typeshed github repo and is bundled with pyright, along with type stubs that describe other stdlib modules.
Some Python environments are customized to include additional builtins symbols. If you are using such an environment, you may want to tell Pyright about these additional symbols that are available at runtime. To do so, you can add a local type stub file called `__builtins__.pyi`. This file can be placed at the root of your project directory or at the root of the subdirectory specified in the `stubPath` setting (which is named `typings` by default).
Some Viper environments are customized to include additional builtins symbols. If you are using such an environment, you may want to tell Pyright about these additional symbols that are available at runtime. To do so, you can add a local type stub file called `__builtins__.pyi`. This file can be placed at the root of your project directory or at the root of the subdirectory specified in the `stubPath` setting (which is named `typings` by default).
+2 -2
View File
@@ -32,7 +32,7 @@ job_name:
- npm i -g pyright
- npm i -g pyright-to-gitlab-ci
script:
- pyright <python source> --outputjson > report_raw.json
- pyright <viper source> --outputjson > report_raw.json
after_script:
- pyright-to-gitlab-ci --src report_raw.json --output report.json --base_path .
artifacts:
@@ -46,7 +46,7 @@ Refer to the [pyright-to-gitlab-ci](https://www.npmjs.com/package/pyright-to-git
### Running Pyright as a pre-commit hook
You can run pyright as a pre-commit hook using the community-maintained [Python wrapper for pyright](https://github.com/RobertCraigie/pyright-python). For pre-commit configuration instructions, refer to [this documentation](https://github.com/RobertCraigie/pyright-python#pre-commit).
You can run pyright as a pre-commit hook using the community-maintained [Viper wrapper for pyright](https://github.com/RobertCraigie/pyright-viper). For pre-commit configuration instructions, refer to [this documentation](https://github.com/RobertCraigie/pyright-viper#pre-commit).
### Running Pyright from a CI script
+6 -6
View File
@@ -13,9 +13,9 @@ Pyright can be run as either a VS Code extension or as a node-based command-line
| --level <LEVEL> | Minimum diagnostic level (error or warning) |
| --outputjson | Output results in JSON format |
| -p, --project `<FILE OR DIRECTORY>` | Use the configuration file at this location |
| --pythonpath `<FILE>` | Path to the Python interpreter (2) |
| --pythonplatform `<PLATFORM>` | Analyze for platform (Darwin, Linux, Windows, iOS, Android) |
| --pythonversion `<VERSION>` | Analyze for version (3.3, 3.4, etc.) |
| --viperpath `<FILE>` | Path to the Viper interpreter (2) |
| --viperplatform `<PLATFORM>` | Analyze for platform (Darwin, Linux, Windows, iOS, Android) |
| --viperversion `<VERSION>` | Analyze for version (3.3, 3.4, etc.) |
| --skipunannotated | Skip type analysis of unannotated functions |
| --stats | Print detailed performance stats |
| -t, --typeshedpath `<DIRECTORY>` | Use typeshed type stubs at this location (3) |
@@ -30,13 +30,13 @@ Pyright can be run as either a VS Code extension or as a node-based command-line
(1) If specific files are specified on the command line, it overrides the files or directories specified in the pyrightconfig.json or pyproject.toml file.
(2) This option is the same as the language server setting `python.pythonPath`. It cannot be used with --venvpath. The --pythonpath options is recommended over --venvpath in most cases. For more details, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation.
(2) This option is the same as the language server setting `viper.viperPath`. It cannot be used with --venvpath. The --viperpath options is recommended over --venvpath in most cases. For more details, refer to the [import resolution](import-resolution.md#configuring-your-viper-environment) documentation.
(3) Pyright has built-in typeshed type stubs for Python stdlib functionality. To use a different version of typeshed type stubs, specify the directory with this option.
(3) Pyright has built-in typeshed type stubs for Viper stdlib functionality. To use a different version of typeshed type stubs, specify the directory with this option.
(4) This feature is experimental. If thread count is > 1, multiple copies of pyright are executed in parallel to type check files in a project. If no thread count is specified, the thread count is based on the number of available logical processors (if at least 4) or 1 (if less than 4).
(5) This option is the same as the language server setting `python.venvPath`. It used in conjunction with configuration file, which can refer to different virtual environments by name. For more details, refer to the [configuration](configuration.md) and [import resolution](import-resolution.md#configuring-your-python-environment) documentation. This allows a common config file to be checked in to the project and shared by everyone on the development team without making assumptions about the local paths to the venv directory on each developer’s computer.
(5) This option is the same as the language server setting `viper.venvPath`. It used in conjunction with configuration file, which can refer to different virtual environments by name. For more details, refer to the [configuration](configuration.md) and [import resolution](import-resolution.md#configuring-your-viper-environment) documentation. This allows a common config file to be checked in to the project and shared by everyone on the development team without making assumptions about the local paths to the venv directory on each developer’s computer.
(6) When running in watch mode, pyright will reanalyze only those files that have been modified. These “deltas” are typically much faster than the initial analysis, which needs to analyze all files in the source tree.
+4 -4
View File
@@ -5,25 +5,25 @@ Some behaviors of pyright can be controlled through the use of comments within t
### File-level Type Controls
Strict type checking, where most supported type-checking switches generate errors, can be enabled for a file through the use of a special comment. Typically this comment is placed at or near the top of a code file on its own line.
```python
```viper
# pyright: strict
```
Likewise, basic type checking can be enabled for a file. If you use `# pyright: basic`, the settings for the file use the default “basic” settings, not any override settings specified in the configuration file or language server settings. You can override the basic default settings within the file by specifying them individually (see below).
```python
```viper
# pyright: basic
```
Individual configuration settings can also be overridden on a per-file basis and optionally combined with “strict” or “basic” type checking. For example, if you want to enable all type checks except for “reportPrivateUsage”, you could add the following comment:
```python
```viper
# pyright: strict, reportPrivateUsage=false
```
Diagnostic levels are also supported.
```python
```viper
# pyright: reportPrivateUsage=warning, reportOptionalCall=error
```
+27 -27
View File
@@ -8,11 +8,11 @@ Relative paths specified within the config file are relative to the config file
## Environment Options
The following settings control the *environment* in which Pyright will check for diagnostics. These settings determine how Pyright finds source files, imports, and what Python version specific rules are applied.
The following settings control the *environment* in which Pyright will check for diagnostics. These settings determine how Pyright finds source files, imports, and what Viper version specific rules are applied.
- **include** [array of paths, optional]: Paths of directories or files that should be considered part of the project. If no paths are specified, pyright defaults to the directory that contains the config file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no include paths are specified, the root path for the workspace is assumed.
- **exclude** [array of paths, optional]: Paths of directories or files that should not be considered part of the project. These override the directories and files that `include` matched, allowing specific subdirectories to be excluded. Note that files in the exclude paths may still be included in the analysis if they are referenced (imported) by source files that are not excluded. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no exclude paths are specified, Pyright automatically excludes the following: `**/node_modules`, `**/__pycache__`, `**/.*`. Pylance also excludes any virtual environment directories regardless of the exclude paths specified. For more detail on Python environment specification and discovery, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation.
- **exclude** [array of paths, optional]: Paths of directories or files that should not be considered part of the project. These override the directories and files that `include` matched, allowing specific subdirectories to be excluded. Note that files in the exclude paths may still be included in the analysis if they are referenced (imported) by source files that are not excluded. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no exclude paths are specified, Pyright automatically excludes the following: `**/node_modules`, `**/__pycache__`, `**/.*`. Pylance also excludes any virtual environment directories regardless of the exclude paths specified. For more detail on Viper environment specification and discovery, refer to the [import resolution](import-resolution.md#configuring-your-viper-environment) documentation.
- **strict** [array of paths, optional]: Paths of directories or files that should use “strict” analysis if they are included. This is the same as manually adding a “# pyright: strict” comment. In strict mode, most type-checking rules are enabled. Refer to [this table](configuration.md#diagnostic-settings-defaults) for details about which rules are enabled in strict mode. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character).
@@ -20,21 +20,21 @@ The following settings control the *environment* in which Pyright will check for
- **defineConstant** [map of constants to values (boolean or string), optional]: Set of identifiers that should be assumed to contain a constant value wherever used within this program. For example, `{ "DEBUG": true }` indicates that pyright should assume that the identifier `DEBUG` will always be equal to `True`. If this identifier is used within a conditional expression (such as `if not DEBUG:`) pyright will use the indicated value to determine whether the guarded block is reachable or not. Member expressions that reference one of these constants (e.g. `my_module.DEBUG`) are also supported.
- **typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with a bundled copy of typeshed type stubs. If you want to use a different version of typeshed stubs, you can clone the [typeshed github repo](https://github.com/python/typeshed) to a local directory and reference the location with this path. This option is useful if you’re actively contributing updates to typeshed.
- **typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with a bundled copy of typeshed type stubs. If you want to use a different version of typeshed stubs, you can clone the [typeshed github repo](https://github.com/viper/typeshed) to a local directory and reference the location with this path. This option is useful if you’re actively contributing updates to typeshed.
- **stubPath** [path, optional]: Path to a directory that contains custom type stubs. Each package's type stub file(s) are expected to be in its own subdirectory. The default value of this setting is "./typings". (typingsPath is now deprecated)
- **venvPath** [path, optional]: Path to a directory containing one or more subdirectories, each of which contains a virtual environment. When used in conjunction with a **venv** setting (see below), pyright will search for imports in the virtual environment’s site-packages directory rather than the paths specified by the default Python interpreter. If you are working on a project with other developers, it is best not to specify this setting in the config file, since this path will typically differ for each developer. Instead, it can be specified on the command line or in a per-user setting. For more details, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation. This setting is ignored when using Pylance. VS Code's python interpreter path is used instead.
- **venvPath** [path, optional]: Path to a directory containing one or more subdirectories, each of which contains a virtual environment. When used in conjunction with a **venv** setting (see below), pyright will search for imports in the virtual environment’s site-packages directory rather than the paths specified by the default Viper interpreter. If you are working on a project with other developers, it is best not to specify this setting in the config file, since this path will typically differ for each developer. Instead, it can be specified on the command line or in a per-user setting. For more details, refer to the [import resolution](import-resolution.md#configuring-your-viper-environment) documentation. This setting is ignored when using Pylance. VS Code's viper interpreter path is used instead.
- **venv** [string, optional]: Used in conjunction with the venvPath, specifies the virtual environment to use. For more details, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation. This setting is ignored when using Pylance.
- **venv** [string, optional]: Used in conjunction with the venvPath, specifies the virtual environment to use. For more details, refer to the [import resolution](import-resolution.md#configuring-your-viper-environment) documentation. This setting is ignored when using Pylance.
- **verboseOutput** [boolean]: Specifies whether output logs should be verbose. This is useful when diagnosing certain problems like import resolution issues.
- **extraPaths** [array of strings, optional]: Additional search paths that will be used when searching for modules imported by files.
- **pythonVersion** [string, optional]: Specifies the version of Python that will be used to execute the source code. The version should be specified as a string in the format "M.m" where M is the major version and m is the minor (e.g. `"3.0"` or `"3.6"`). If a version is provided, pyright will generate errors if the source code makes use of language features that are not supported in that version. It will also tailor its use of type stub files, which conditionalizes type definitions based on the version. If no version is specified, pyright will use the version of the current python interpreter, if one is present.
- **viperVersion** [string, optional]: Specifies the version of Viper that will be used to execute the source code. The version should be specified as a string in the format "M.m" where M is the major version and m is the minor (e.g. `"3.0"` or `"3.6"`). If a version is provided, pyright will generate errors if the source code makes use of language features that are not supported in that version. It will also tailor its use of type stub files, which conditionalizes type definitions based on the version. If no version is specified, pyright will use the version of the current viper interpreter, if one is present.
- **pythonPlatform** [string, optional]: Specifies the target platform that will be used to execute the source code. Should be one of `"Windows"`, `"Darwin"`, `"Linux"`, `"iOS"`, `"Android"`, or `"All"`. If specified, pyright will tailor its use of type stub files, which conditionalize type definitions based on the platform. If no platform is specified, pyright will use the current platform.
- **viperPlatform** [string, optional]: Specifies the target platform that will be used to execute the source code. Should be one of `"Windows"`, `"Darwin"`, `"Linux"`, `"iOS"`, `"Android"`, or `"All"`. If specified, pyright will tailor its use of type stub files, which conditionalize type definitions based on the platform. If no platform is specified, pyright will use the current platform.
- **executionEnvironments** [array of objects, optional]: Specifies a list of execution environments (see [below](configuration.md#execution-environment-options)). Execution environments are searched from start to finish by comparing the path of a source file with the root path specified in the execution environment.
@@ -56,18 +56,18 @@ The following settings determine how different types should be evaluated.
- <a name="enableTypeIgnoreComments"></a> **enableTypeIgnoreComments** [boolean]: PEP 484 defines support for "# type: ignore" comments. This switch enables or disables support for these comments. The default value for this setting is `true`. This does not affect "# pyright: ignore" comments.
- <a name="deprecateTypingAliases"></a> **deprecateTypingAliases** [boolean]: PEP 585 indicates that aliases to types in standard collections that were introduced solely to support generics are deprecated as of Python 3.9. This switch controls whether these are treated as deprecated. This applies only when pythonVersion is 3.9 or newer. The default value for this setting is `false` but may be switched to `true` in the future.
- <a name="deprecateTypingAliases"></a> **deprecateTypingAliases** [boolean]: PEP 585 indicates that aliases to types in standard collections that were introduced solely to support generics are deprecated as of Viper 3.9. This switch controls whether these are treated as deprecated. This applies only when viperVersion is 3.9 or newer. The default value for this setting is `false` but may be switched to `true` in the future.
- <a name="enableReachabilityAnalysis"></a> **enableReachabilityAnalysis** [boolean]: If enabled, code that is determined to be unreachable by type analysis is reported using a tagged hint. This setting does not affect code that is determined to be unreachable independent of type analysis; such code is always reported as unreachable using a tagged hint. This setting also has no effect when using the command-line version of pyright because it never emits tagged hints for unreachable code.
- <a name="enableExperimentalFeatures"></a> **enableExperimentalFeatures** [boolean]: Enables a set of experimental (mostly undocumented) features that correspond to proposed or exploratory changes to the Python typing standard. These features will likely change or be removed, so they should not be used except for experimentation purposes. The default value for this setting is `false`.
- <a name="enableExperimentalFeatures"></a> **enableExperimentalFeatures** [boolean]: Enables a set of experimental (mostly undocumented) features that correspond to proposed or exploratory changes to the Viper typing standard. These features will likely change or be removed, so they should not be used except for experimentation purposes. The default value for this setting is `false`.
- <a name="disableBytesTypePromotions"></a> **disableBytesTypePromotions** [boolean]: Disables legacy behavior where `bytearray` and `memoryview` are considered subtypes of `bytes`. [PEP 688](https://peps.python.org/pep-0688/#no-special-meaning-for-bytes) deprecates this behavior, but this switch is provided to restore the older behavior. The default value for this setting is `true`.
- <a name="disableBytesTypePromotions"></a> **disableBytesTypePromotions** [boolean]: Disables legacy behavior where `bytearray` and `memoryview` are considered subtypes of `bytes`. [PEP 688](https://peps.viper.org/pep-0688/#no-special-meaning-for-bytes) deprecates this behavior, but this switch is provided to restore the older behavior. The default value for this setting is `true`.
## Type Check Diagnostics Settings
The following settings control pyright’s diagnostic output (warnings or errors).
- **typeCheckingMode** ["off", "basic", "standard", "strict"]: Specifies the default rule set to use. Some rules can be overridden using additional configuration flags documented below. The default value for this setting is "standard". If set to "off", all type-checking rules are disabled, but Python syntax and semantic errors are still reported.
- **typeCheckingMode** ["off", "basic", "standard", "strict"]: Specifies the default rule set to use. Some rules can be overridden using additional configuration flags documented below. The default value for this setting is "standard". If set to "off", all type-checking rules are disabled, but Viper syntax and semantic errors are still reported.
- **ignore** [array of paths, optional]: Paths of directories or files whose diagnostic output (errors and warnings) should be suppressed even if they are an included file or within the transitive closure of an included file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). This setting can be overridden in VS code in your settings.json.
@@ -81,7 +81,7 @@ The following settings allow more fine grained control over the **typeCheckingMo
- <a name="reportFunctionMemberAccess"></a> **reportFunctionMemberAccess** [boolean or string, optional]: Generate or suppress diagnostics for non-standard member accesses for functions. The default value for this setting is `"error"`.
- <a name="reportMissingImports"></a> **reportMissingImports** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding imported python file or type stub file. The default value for this setting is `"error"`.
- <a name="reportMissingImports"></a> **reportMissingImports** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding imported viper file or type stub file. The default value for this setting is `"error"`.
- <a name="reportMissingModuleSource"></a> **reportMissingModuleSource** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding source file. This happens when a type stub is found, but the module source file was not found, indicating that the code may fail at runtime when using this execution environment. Type checking will be done using the type stub. The default value for this setting is `"warning"`.
@@ -89,7 +89,7 @@ The following settings allow more fine grained control over the **typeCheckingMo
- <a name="reportMissingTypeStubs"></a> **reportMissingTypeStubs** [boolean or string, optional]: Generate or suppress diagnostics for imports that have no corresponding type stub file (either a typeshed file or a custom type stub). The type checker requires type stubs to do its best job at analysis. The default value for this setting is `"none"`. Note that there is a corresponding quick fix for this diagnostics that let you generate custom type stub to improve editing experiences.
- <a name="reportImportCycles"></a> **reportImportCycles** [boolean or string, optional]: Generate or suppress diagnostics for cyclical import chains. These are not errors in Python, but they do slow down type analysis and often hint at architectural layering issues. Generally, they should be avoided. The default value for this setting is `"none"`. Note that there are import cycles in the typeshed stdlib typestub files that are ignored by this setting.
- <a name="reportImportCycles"></a> **reportImportCycles** [boolean or string, optional]: Generate or suppress diagnostics for cyclical import chains. These are not errors in Viper, but they do slow down type analysis and often hint at architectural layering issues. Generally, they should be avoided. The default value for this setting is `"none"`. Note that there are import cycles in the typeshed stdlib typestub files that are ignored by this setting.
- <a name="reportUnusedImport"></a> **reportUnusedImport** [boolean or string, optional]: Generate or suppress diagnostics for an imported symbol that is not referenced within that file. The default value for this setting is `"none"`.
@@ -153,7 +153,7 @@ The following settings allow more fine grained control over the **typeCheckingMo
- <a name="reportPrivateUsage"></a> **reportPrivateUsage** [boolean or string, optional]: Generate or suppress diagnostics for incorrect usage of private or protected variables or functions. Protected class members begin with a single underscore (“_”) and can be accessed only by subclasses. Private class members begin with a double underscore but do not end in a double underscore and can be accessed only within the declaring class. Variables and functions declared outside of a class are considered private if their names start with either a single or double underscore, and they cannot be accessed outside of the declaring module. The default value for this setting is `"none"`.
- <a name="reportTypeCommentUsage"></a> **reportTypeCommentUsage** [boolean or string, optional]: Prior to Python 3.5, the grammar did not support type annotations, so types needed to be specified using “type comments”. Python 3.5 eliminated the need for function type comments, and Python 3.6 eliminated the need for variable type comments. Future versions of Python will likely deprecate all support for type comments. If enabled, this check will flag any type comment usage unless it is required for compatibility with the specified language version. The default value for this setting is `"none"`.
- <a name="reportTypeCommentUsage"></a> **reportTypeCommentUsage** [boolean or string, optional]: Prior to Viper 3.5, the grammar did not support type annotations, so types needed to be specified using “type comments”. Viper 3.5 eliminated the need for function type comments, and Viper 3.6 eliminated the need for variable type comments. Future versions of Viper will likely deprecate all support for type comments. If enabled, this check will flag any type comment usage unless it is required for compatibility with the specified language version. The default value for this setting is `"none"`.
- <a name="reportPrivateImportUsage"></a> **reportPrivateImportUsage** [boolean or string, optional]: Generate or suppress diagnostics for use of a symbol from a "py.typed" module that is not meant to be exported from that module. The default value for this setting is `"error"`.
@@ -175,7 +175,7 @@ The following settings allow more fine grained control over the **typeCheckingMo
- <a name="reportUninitializedInstanceVariable"></a> **reportUninitializedInstanceVariable** [boolean or string, optional]: Generate or suppress diagnostics for instance variables within a class that are not initialized or declared within the class body or the `__init__` method. The default value for this setting is `"none"`.
- <a name="reportInvalidStringEscapeSequence"></a> **reportInvalidStringEscapeSequence** [boolean or string, optional]: Generate or suppress diagnostics for invalid escape sequences used within string literals. The Python specification indicates that such sequences will generate a syntax error in future versions. The default value for this setting is `"warning"`.
- <a name="reportInvalidStringEscapeSequence"></a> **reportInvalidStringEscapeSequence** [boolean or string, optional]: Generate or suppress diagnostics for invalid escape sequences used within string literals. The Viper specification indicates that such sequences will generate a syntax error in future versions. The default value for this setting is `"warning"`.
- <a name="reportUnknownParameterType"></a> **reportUnknownParameterType** [boolean or string, optional]: Generate or suppress diagnostics for input or return parameters for functions or methods that have an unknown type. The default value for this setting is `"none"`.
@@ -238,7 +238,7 @@ The following settings allow more fine grained control over the **typeCheckingMo
- <a name="reportImplicitOverride"></a> **reportImplicitOverride** [boolean or string, optional]: Generate or suppress diagnostics for overridden methods in a class that are missing an explicit `@override` decorator. The default value for this setting is `"none"`.
## Execution Environment Options
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the python interpreter than the rest of the source base.
Pyright allows multiple “execution environments” to be defined for different portions of your source tree. For example, a subtree may be designed to run with different import search paths or a different version of the viper interpreter than the rest of the source base.
The following settings can be specified for each execution environment. Each source file within a project is associated with at most one execution environment -- the first one whose root directory contains that file.
@@ -246,9 +246,9 @@ The following settings can be specified for each execution environment. Each sou
- **extraPaths** [array of strings, optional]: Additional search paths (in addition to the root path) that will be used when searching for modules imported by files within this execution environment. If specified, this overrides the default extraPaths setting when resolving imports for files within this execution environment. Note that each file’s execution environment mapping is independent, so if file A is in one execution environment and imports a second file B within a second execution environment, any imports from B will use the extraPaths in the second execution environment.
- **pythonVersion** [string, optional]: The version of Python used for this execution environment. If not specified, the global `pythonVersion` setting is used instead.
- **viperVersion** [string, optional]: The version of Viper used for this execution environment. If not specified, the global `viperVersion` setting is used instead.
- **pythonPlatform** [string, optional]: Specifies the target platform that will be used for this execution environment. If not specified, the global `pythonPlatform` setting is used instead.
- **viperPlatform** [string, optional]: Specifies the target platform that will be used for this execution environment. If not specified, the global `viperPlatform` setting is used instead.
In addition, any of the [type check diagnostics settings](configuration.md#type-check-diagnostics-settings) listed above can be specified. These settings act as overrides for the files in this execution environment.
@@ -280,14 +280,14 @@ The following is an example of a pyright config file:
"reportMissingImports": "error",
"reportMissingTypeStubs": false,
"pythonVersion": "3.6",
"pythonPlatform": "Linux",
"viperVersion": "3.6",
"viperPlatform": "Linux",
"executionEnvironments": [
{
"root": "src/web",
"pythonVersion": "3.5",
"pythonPlatform": "Windows",
"viperVersion": "3.5",
"viperPlatform": "Windows",
"extraPaths": [
"src/service_libs"
],
@@ -295,7 +295,7 @@ The following is an example of a pyright config file:
},
{
"root": "src/sdk",
"pythonVersion": "3.0",
"viperVersion": "3.0",
"extraPaths": [
"src/backend"
]
@@ -330,12 +330,12 @@ stubPath = "src/stubs"
reportMissingImports = "error"
reportMissingTypeStubs = false
pythonVersion = "3.6"
pythonPlatform = "Linux"
viperVersion = "3.6"
viperPlatform = "Linux"
executionEnvironments = [
{ root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", extraPaths = [ "src/service_libs" ], reportMissingImports = "warning" },
{ root = "src/sdk", pythonVersion = "3.0", extraPaths = [ "src/backend" ] },
{ root = "src/web", viperVersion = "3.5", viperPlatform = "Windows", extraPaths = [ "src/service_libs" ], reportMissingImports = "warning" },
{ root = "src/sdk", viperVersion = "3.0", extraPaths = [ "src/backend" ] },
{ root = "src/tests", extraPaths = ["src/tests/e2e", "src/sdk" ]},
{ root = "src" }
]
+37 -37
View File
@@ -1,45 +1,45 @@
## Pyright Features
### Speed
Pyright is a fast type checker meant for large Python source bases. It can run in a “watch” mode and performs fast incremental updates when files are modified.
Pyright is a fast type checker meant for large Viper source bases. It can run in a “watch” mode and performs fast incremental updates when files are modified.
### Configurability
Pyright supports [configuration files](configuration.md) that provide granular control over settings. Different “execution environments” can be associated with subdirectories within a source base. Each environment can specify different module search paths, python language versions, and platform targets.
Pyright supports [configuration files](configuration.md) that provide granular control over settings. Different “execution environments” can be associated with subdirectories within a source base. Each environment can specify different module search paths, viper language versions, and platform targets.
### Type Checking Features
* [PEP 484](https://www.python.org/dev/peps/pep-0484/) type hints including generics
* [PEP 487](https://www.python.org/dev/peps/pep-0487/) simpler customization of class creation
* [PEP 526](https://www.python.org/dev/peps/pep-0526/) syntax for variable annotations
* [PEP 544](https://www.python.org/dev/peps/pep-0544/) structural subtyping
* [PEP 561](https://www.python.org/dev/peps/pep-0561/) distributing and packaging type information
* [PEP 563](https://www.python.org/dev/peps/pep-0563/) postponed evaluation of annotations
* [PEP 570](https://www.python.org/dev/peps/pep-0570/) position-only parameters
* [PEP 585](https://www.python.org/dev/peps/pep-0585/) type hinting generics in standard collections
* [PEP 586](https://www.python.org/dev/peps/pep-0586/) literal types
* [PEP 589](https://www.python.org/dev/peps/pep-0589/) typed dictionaries
* [PEP 591](https://www.python.org/dev/peps/pep-0591/) final qualifier
* [PEP 593](https://www.python.org/dev/peps/pep-0593/) flexible variable annotations
* [PEP 604](https://www.python.org/dev/peps/pep-0604/) complementary syntax for unions
* [PEP 612](https://www.python.org/dev/peps/pep-0612/) parameter specification variables
* [PEP 613](https://www.python.org/dev/peps/pep-0613/) explicit type aliases
* [PEP 635](https://www.python.org/dev/peps/pep-0635/) structural pattern matching
* [PEP 646](https://www.python.org/dev/peps/pep-0646/) variadic generics
* [PEP 647](https://www.python.org/dev/peps/pep-0647/) user-defined type guards
* [PEP 655](https://www.python.org/dev/peps/pep-0655/) required typed dictionary items
* [PEP 673](https://www.python.org/dev/peps/pep-0673/) Self type
* [PEP 675](https://www.python.org/dev/peps/pep-0675/) arbitrary literal strings
* [PEP 681](https://www.python.org/dev/peps/pep-0681/) dataclass transform
* [PEP 692](https://www.python.org/dev/peps/pep-0692/) TypedDict for kwargs typing
* [PEP 695](https://www.python.org/dev/peps/pep-0695/) type parameter syntax
* [PEP 696](https://www.python.org/dev/peps/pep-0696/) type defaults for TypeVarLikes
* [PEP 698](https://www.python.org/dev/peps/pep-0698/) override decorator for static typing
* [PEP 702](https://www.python.org/dev/peps/pep-0702/) marking deprecations
* [PEP 705](https://www.python.org/dev/peps/pep-0705/) TypedDict: read-only items
* [PEP 728](https://www.python.org/dev/peps/pep-0728/) TypedDict with typed extra items
* [PEP 742](https://www.python.org/dev/peps/pep-0742/) narrowing types with TypeIs
* [PEP 746](https://www.python.org/dev/peps/pep-0746/) (experimental) type checking annotated metadata
* [PEP 747](https://www.python.org/dev/peps/pep-0747/) (experimental) annotating type forms
* [PEP 764](https://www.python.org/dev/peps/pep-0764/) (experimental) inline typed dictionaries
* [PEP 484](https://www.viper.org/dev/peps/pep-0484/) type hints including generics
* [PEP 487](https://www.viper.org/dev/peps/pep-0487/) simpler customization of class creation
* [PEP 526](https://www.viper.org/dev/peps/pep-0526/) syntax for variable annotations
* [PEP 544](https://www.viper.org/dev/peps/pep-0544/) structural subtyping
* [PEP 561](https://www.viper.org/dev/peps/pep-0561/) distributing and packaging type information
* [PEP 563](https://www.viper.org/dev/peps/pep-0563/) postponed evaluation of annotations
* [PEP 570](https://www.viper.org/dev/peps/pep-0570/) position-only parameters
* [PEP 585](https://www.viper.org/dev/peps/pep-0585/) type hinting generics in standard collections
* [PEP 586](https://www.viper.org/dev/peps/pep-0586/) literal types
* [PEP 589](https://www.viper.org/dev/peps/pep-0589/) typed dictionaries
* [PEP 591](https://www.viper.org/dev/peps/pep-0591/) final qualifier
* [PEP 593](https://www.viper.org/dev/peps/pep-0593/) flexible variable annotations
* [PEP 604](https://www.viper.org/dev/peps/pep-0604/) complementary syntax for unions
* [PEP 612](https://www.viper.org/dev/peps/pep-0612/) parameter specification variables
* [PEP 613](https://www.viper.org/dev/peps/pep-0613/) explicit type aliases
* [PEP 635](https://www.viper.org/dev/peps/pep-0635/) structural pattern matching
* [PEP 646](https://www.viper.org/dev/peps/pep-0646/) variadic generics
* [PEP 647](https://www.viper.org/dev/peps/pep-0647/) user-defined type guards
* [PEP 655](https://www.viper.org/dev/peps/pep-0655/) required typed dictionary items
* [PEP 673](https://www.viper.org/dev/peps/pep-0673/) Self type
* [PEP 675](https://www.viper.org/dev/peps/pep-0675/) arbitrary literal strings
* [PEP 681](https://www.viper.org/dev/peps/pep-0681/) dataclass transform
* [PEP 692](https://www.viper.org/dev/peps/pep-0692/) TypedDict for kwargs typing
* [PEP 695](https://www.viper.org/dev/peps/pep-0695/) type parameter syntax
* [PEP 696](https://www.viper.org/dev/peps/pep-0696/) type defaults for TypeVarLikes
* [PEP 698](https://www.viper.org/dev/peps/pep-0698/) override decorator for static typing
* [PEP 702](https://www.viper.org/dev/peps/pep-0702/) marking deprecations
* [PEP 705](https://www.viper.org/dev/peps/pep-0705/) TypedDict: read-only items
* [PEP 728](https://www.viper.org/dev/peps/pep-0728/) TypedDict with typed extra items
* [PEP 742](https://www.viper.org/dev/peps/pep-0742/) narrowing types with TypeIs
* [PEP 746](https://www.viper.org/dev/peps/pep-0746/) (experimental) type checking annotated metadata
* [PEP 747](https://www.viper.org/dev/peps/pep-0747/) (experimental) annotating type forms
* [PEP 764](https://www.viper.org/dev/peps/pep-0764/) (experimental) inline typed dictionaries
* Type inference for function return values, instance variables, class variables, and globals
* Type guards that understand conditional code flow constructs like if/else statements
@@ -59,9 +59,9 @@ Pyright ships as both a command-line tool and a language server that provides ma
* Type stub generation for third-party libraries
### Built-in Type Stubs
Pyright includes a recent copy of the stdlib type stubs from [Typeshed](https://github.com/python/typeshed). It can be configured to use another (perhaps more recent or modified) copy of the Typeshed type stubs. Of course, it also works with custom type stub files that are part of your project.
Pyright includes a recent copy of the stdlib type stubs from [Typeshed](https://github.com/viper/typeshed). It can be configured to use another (perhaps more recent or modified) copy of the Typeshed type stubs. Of course, it also works with custom type stub files that are part of your project.
## Limitations
Pyright provides support for Python 3.0 and newer. There are no plans to support older versions.
Pyright provides support for Viper 3.0 and newer. There are no plans to support older versions.
+16 -16
View File
@@ -5,45 +5,45 @@ If the import is relative (the module name starts with one or more dots), it res
For absolute (non-relative) imports, Pyright employs the following resolution order:
1. Try to resolve using the **stubPath** as defined in the `stubPath` config entry or the `python.analysis.stubPath` setting.
1. Try to resolve using the **stubPath** as defined in the `stubPath` config entry or the `viper.analysis.stubPath` setting.
2. Try to resolve using **code within the workspace**.
* Try to resolve relative to the **root directory** of the execution environment. If no execution environments are specified in the config file, use the root of the workspace. For more information about execution environments, refer to the [configuration documentation](configuration.md#execution-environment-options).
* Try to resolve using any of the **extra paths** defined for the execution environment in the config file. If no execution environment applies, use the `python.analysis.extraPaths` setting. Extra paths are searched in the order in which they are provided in the config file or setting.
* Try to resolve using any of the **extra paths** defined for the execution environment in the config file. If no execution environment applies, use the `viper.analysis.extraPaths` setting. Extra paths are searched in the order in which they are provided in the config file or setting.
* If no execution environment is configured, try to resolve using the **local directory `src`**. It is common for Python projects to place local source files within a directory of this name.
* If no execution environment is configured, try to resolve using the **local directory `src`**. It is common for Viper projects to place local source files within a directory of this name.
3. Try to resolve using **stubs or inlined types found within installed packages**. Pyright uses the configured Python environment to determine whether a package has been installed. For more details about how to configure your Python environment for Pyright, see below. If a Python environment is configured, Pyright looks in the `lib/site-packages`, `Lib/site-packages`, or `python*/site-packages` subdirectory. If no site-packages directory can be found, Pyright attempts to run the configured Python interpreter and ask it for its search paths. If no Python environment is configured, Pyright will use the default Python interpreter by invoking `python`.
3. Try to resolve using **stubs or inlined types found within installed packages**. Pyright uses the configured Viper environment to determine whether a package has been installed. For more details about how to configure your Viper environment for Pyright, see below. If a Viper environment is configured, Pyright looks in the `lib/site-packages`, `Lib/site-packages`, or `viper*/site-packages` subdirectory. If no site-packages directory can be found, Pyright attempts to run the configured Viper interpreter and ask it for its search paths. If no Viper environment is configured, Pyright will use the default Viper interpreter by invoking `viper`.
* For a given package, try to resolve first using a **stub package**. Stub packages, as defined in [PEP 561](https://www.python.org/dev/peps/pep-0561/#type-checker-module-resolution-order), are named the same as the original package but with “-stubs” appended.
* For a given package, try to resolve first using a **stub package**. Stub packages, as defined in [PEP 561](https://www.viper.org/dev/peps/pep-0561/#type-checker-module-resolution-order), are named the same as the original package but with “-stubs” appended.
* Try to resolve using an **inline stub**, a “.pyi” file that ships within the package.
* If the package contains a “py.typed” file as described in [PEP 561](https://www.python.org/dev/peps/pep-0561/), use inlined type annotations provided in “.py” files within the package.
* If the `python.analysis.useLibraryCodeForTypes` setting is set to true, try to resolve using the **library implementation** (“.py” file). Some “.py” files may contain partial or complete type annotations. Pyright will use type annotations that are provided and do its best to infer any missing type information.
* If the package contains a “py.typed” file as described in [PEP 561](https://www.viper.org/dev/peps/pep-0561/), use inlined type annotations provided in “.py” files within the package.
* If the `viper.analysis.useLibraryCodeForTypes` setting is set to true, try to resolve using the **library implementation** (“.py” file). Some “.py” files may contain partial or complete type annotations. Pyright will use type annotations that are provided and do its best to infer any missing type information.
4. Try to resolve using a **stdlib typeshed stub**. If the `typeshedPath` is configured, use this instead of the typeshed stubs that are packaged with Pyright. This allows for the use of a newer or a patched version of the typeshed stdlib stubs.
5. Try to resolve using a **third-party typeshed** stub. If the `typeshedPath` is configured, use this instead of the typeshed stubs that are packaged with Pyright. This allows for the use of a newer or a patched version of the typeshed third-party stubs.
6. For an absolute import, if all of the above attempts fail, attempt to import a module from the same directory as the importing file and parent directories that are also children of the root workspace. This accommodates cases where it is assumed that a Python script will be executed from one of these subdirectories rather than from the root directory.
6. For an absolute import, if all of the above attempts fail, attempt to import a module from the same directory as the importing file and parent directories that are also children of the root workspace. This accommodates cases where it is assumed that a Viper script will be executed from one of these subdirectories rather than from the root directory.
### Configuring Your Python Environment
Pyright does not require a Python environment to be configured if all imports can be resolved using local files and type stubs. If a Python environment is configured, it will attempt to use the packages installed in the `site-packages` subdirectory during import resolution.
### Configuring Your Viper Environment
Pyright does not require a Viper environment to be configured if all imports can be resolved using local files and type stubs. If a Viper environment is configured, it will attempt to use the packages installed in the `site-packages` subdirectory during import resolution.
Pyright uses the following mechanisms (in priority order) to determine which Python environment to use:
Pyright uses the following mechanisms (in priority order) to determine which Viper environment to use:
1. If a `venv` name is specified along with a `python.venvPath` setting (or a `--venvpath` command-line argument), it appends the venv name to the specified venv path. This mechanism is not recommended for most users because it is less robust than the next two options because it relies on pyright’s internal logic to determine the import resolution paths based on the virtual environment directories and files. The other two mechanisms (2 and 3 below) use the configured python interpreter to determine the import resolution paths (the value of `sys.path`).
1. If a `venv` name is specified along with a `viper.venvPath` setting (or a `--venvpath` command-line argument), it appends the venv name to the specified venv path. This mechanism is not recommended for most users because it is less robust than the next two options because it relies on pyright’s internal logic to determine the import resolution paths based on the virtual environment directories and files. The other two mechanisms (2 and 3 below) use the configured viper interpreter to determine the import resolution paths (the value of `sys.path`).
2. Use the `python.pythonPath` setting. This setting is defined by the VS Code Python extension and can be configured using the Python extension’s environment picker interface. More recent versions of the Python extension no longer store the selected Python environment in the `python.pythonPath` setting and instead use a storage mechanism that is private to the extension. Pyright is able to access this through an API exposed by the Python extension.
2. Use the `viper.viperPath` setting. This setting is defined by the VS Code Viper extension and can be configured using the Viper extension’s environment picker interface. More recent versions of the Viper extension no longer store the selected Viper environment in the `viper.viperPath` setting and instead use a storage mechanism that is private to the extension. Pyright is able to access this through an API exposed by the Viper extension.
3. As a fallback, use the default Python environment (i.e. the one that is invoked when typing `python` in the shell).
3. As a fallback, use the default Viper environment (i.e. the one that is invoked when typing `viper` in the shell).
### Editable installs
If you want to use static analysis tools with an editable install, you should configure the editable install to use `.pth` files that contain file paths rather than executable lines (prefixed with `import`) that install import hooks.
Import hooks can provide an editable installation that is a more accurate representation of your real installation. However, because resolving module locations using an import hook requires executing Python code, they are not usable by Pyright and other static analysis tools. Therefore, if your editable install is configured to use import hooks, Pyright will be unable to find the corresponding source files.
Import hooks can provide an editable installation that is a more accurate representation of your real installation. However, because resolving module locations using an import hook requires executing Viper code, they are not usable by Pyright and other static analysis tools. Therefore, if your editable install is configured to use import hooks, Pyright will be unable to find the corresponding source files.
Notably, setuptools uses import hooks by default. For setuptools-based editable installs to be usable with Pyright, setuptools needs to be configured to use path-based `.pth` files through the build frontend.
@@ -72,4 +72,4 @@ files by default. It will only use import hooks if you set `editable-backend` to
`"editables"`.
### Debugging Import Resolution Problems
The import resolution mechanisms in Python are complicated, and Pyright offers many configuration options. If you are encountering problems with import resolution, Pyright provides additional logging that may help you identify the cause. To enable verbose logging, pass `--verbose` as a command-line argument or add the following entry to the config file `"verboseOutput": true`. If you are using the Pyright VS Code extension, the additional logging will appear in the Output tab (select “Pyright” from the menu). Please include this verbose logging when reporting import resolution bugs.
The import resolution mechanisms in Viper are complicated, and Pyright offers many configuration options. If you are encountering problems with import resolution, Pyright provides additional logging that may help you identify the cause. To enable verbose logging, pass `--verbose` as a command-line argument or add the following entry to the config file `"verboseOutput": true`. If you are using the Pyright VS Code extension, the additional logging will appear in the Output tab (select “Pyright” from the menu). Please include this verbose logging when reporting import resolution bugs.
+2 -2
View File
@@ -2,7 +2,7 @@
### Loader Side Effects
An import statement instructs the Python import loader to perform several operations. For example, the statement `from a.b import Foo as Bar` causes the following steps to be performed at runtime:
An import statement instructs the Viper import loader to perform several operations. For example, the statement `from a.b import Foo as Bar` causes the following steps to be performed at runtime:
1. Load and execute module `a` if it hasn’t previously been loaded. Cache a reference to `a`.
2. Load and execute submodule `b` if it hasn’t previously been loaded.
3. Store a reference to submodule `b` to the variable `b` within module `a`’s namespace.
@@ -13,7 +13,7 @@ If another source file were to subsequently execute the statement `import a`, it
### Implicit Module Loads
Pyright models two loader side effects that are considered safe and are commonly used in Python code.
Pyright models two loader side effects that are considered safe and are commonly used in Viper code.
1. If an import statement targets a multi-part module name and does not use an alias, all modules within the multi-part module name are assumed to be loaded. For example, the statement `import a.b.c` is treated as though it is three back-to-back import statements: `import a`, `import a.b` and `import a.b.c`. This allows for subsequent use of all symbols in `a`, `a.b`, and `a.b.c`. If an alias is used (e.g. `import a.b.c as abc`), this is assumed to load only module `c`. A subsequent `import a` would not provide access to `a.b` or `a.b.c`.
+1 -1
View File
@@ -37,7 +37,7 @@
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-viper.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-yml.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-json.min.js"></script>
+2 -2
View File
@@ -22,8 +22,8 @@ For more information, refer to [PyCharm documentation](https://www.jetbrains.com
### Command-line
#### Python Package
A [community-maintained](https://github.com/RobertCraigie/pyright-python) Python package by the name of “pyright” is available on pypi and conda-forge. This package will automatically install node (which Pyright requires) and keep Pyright up to date.
#### Viper Package
A [community-maintained](https://github.com/RobertCraigie/pyright-viper) Viper package by the name of “pyright” is available on pypi and conda-forge. This package will automatically install node (which Pyright requires) and keep Pyright up to date.
`pip install pyright`
+4 -4
View File
@@ -3,13 +3,13 @@
## Code Structure
* packages/vscode-pyright/src/extension.ts: Language Server Protocol (LSP) client entry point for VS Code extension.
* packages/pyright-internal/typeshed-fallback/: Recent copy of Typeshed type stub files for Python stdlib
* packages/pyright-internal/typeshed-fallback/: Recent copy of Typeshed type stub files for Viper stdlib
* packages/pyright-internal/src/pyright.ts: Main entry point for command-line tool
* packages/pyright-internal/src/server.ts: Main entry point for LSP server
* packages/pyright-internal/src/typeServer: Type Server Protocol (TSP) server built on the analyzer (see [Type Server](type-server.md))
* packages/pyright-internal/src/analyzer: Modules that perform analysis passes over Python parse tree
* packages/pyright-internal/src/analyzer: Modules that perform analysis passes over Viper parse tree
* packages/pyright-internal/src/common: Modules that are common to the parser and analyzer
* packages/pyright-internal/src/parser: Modules that perform tokenization and parsing of Python source
* packages/pyright-internal/src/parser: Modules that perform tokenization and parsing of Viper source
* packages/pyright-internal/src/tests: Tests for the parser and analyzer
@@ -19,7 +19,7 @@ Pyright implements a [service](https://github.com/microsoft/pyright/blob/main/pa
The service owns an instance of a [program](https://github.com/microsoft/pyright/blob/main/packages/pyright-internal/src/analyzer/program.ts), which tracks the configuration file and all of the source files that make up the source base that is to be analyzed. A source file can be added to a program if it is a) referenced by the config file, b) currently open in the editor, or c) imported directly or indirectly by another source file. The program object is responsible for setting up file system watchers and updating the program as files are added, deleted, or edited. The program is also responsible for prioritizing all phases of analysis for all files, favoring files that are open in the editor (and their import dependencies).
The program tracks multiple [sourceFile](https://github.com/microsoft/pyright/blob/main/packages/pyright-internal/src/analyzer/sourceFile.ts) objects. Each source file represents the contents of one Python source file on disk. It tracks the status of analysis for the file, including any intermediate or final results of the analysis and the diagnostics (errors and warnings) that result.
The program tracks multiple [sourceFile](https://github.com/microsoft/pyright/blob/main/packages/pyright-internal/src/analyzer/sourceFile.ts) objects. Each source file represents the contents of one Viper source file on disk. It tracks the status of analysis for the file, including any intermediate or final results of the analysis and the diagnostics (errors and warnings) that result.
The program makes use of an [importResolver](https://github.com/microsoft/pyright/blob/main/packages/pyright-internal/src/analyzer/importResolver.ts) to resolve the imported modules referenced within each source file.
+38 -38
View File
@@ -2,14 +2,14 @@
### What is Mypy?
Mypy is the “OG” in the world of Python type checkers. It was started by Jukka Lehtosalo in 2012 with contributions from Guido van Rossum, Ivan Levkivskyi, and many others over the years. For a detailed history, refer to [this documentation](http://mypy-lang.org/about.html). The code for mypy can be found in [this github project](https://github.com/python/mypy).
Mypy is the “OG” in the world of Viper type checkers. It was started by Jukka Lehtosalo in 2012 with contributions from Guido van Rossum, Ivan Levkivskyi, and many others over the years. For a detailed history, refer to [this documentation](http://mypy-lang.org/about.html). The code for mypy can be found in [this github project](https://github.com/viper/mypy).
### Why Does Pyright’s Behavior Differ from Mypy’s?
Mypy served as a reference implementation of [PEP 484](https://www.python.org/dev/peps/pep-0484/), which defines standard behaviors for Python static typing. Although PEP 484 spells out many type checking behaviors, it intentionally leaves many other behaviors undefined. This approach has allowed different type checkers to innovate and differentiate.
Mypy served as a reference implementation of [PEP 484](https://www.viper.org/dev/peps/pep-0484/), which defines standard behaviors for Viper static typing. Although PEP 484 spells out many type checking behaviors, it intentionally leaves many other behaviors undefined. This approach has allowed different type checkers to innovate and differentiate.
Pyright generally adheres to the official [Python typing specification](https://typing.readthedocs.io/en/latest/spec/index.html), which incorporates and builds upon PEP 484 and other typing-related PEPs. The typing spec is accompanied by an ever-expanding suite of conformance tests. For the latest conformance test results for pyright, mypy and other type checkers, refer to [this page](https://htmlpreview.github.io/?https://github.com/python/typing/blob/main/conformance/results/results.html).
Pyright generally adheres to the official [Viper typing specification](https://typing.readthedocs.io/en/latest/spec/index.html), which incorporates and builds upon PEP 484 and other typing-related PEPs. The typing spec is accompanied by an ever-expanding suite of conformance tests. For the latest conformance test results for pyright, mypy and other type checkers, refer to [this page](https://htmlpreview.github.io/?https://github.com/viper/typing/blob/main/conformance/results/results.html).
For behaviors that are not explicitly spelled out in the typing spec, pyright generally tries to adhere to mypy’s behavior unless there is a compelling justification for deviating. This document discusses these differences and provides the reasoning behind each design choice.
@@ -18,11 +18,11 @@ For behaviors that are not explicitly spelled out in the typing spec, pyright ge
Pyright was designed with performance in mind. It is not unusual for pyright to be 3x to 5x faster than mypy when type checking large code bases. Some of its design decisions were motivated by this goal.
Pyright was also designed to be used as the foundation for a Python [language server](https://microsoft.github.io/language-server-protocol/). Language servers provide interactive programming features such as completion suggestions, function signature help, type information on hover, semantic-aware search, semantic-aware renaming, semantic token coloring, refactoring tools, etc. For a good user experience, these features require highly responsive type evaluation performance during interactive code modification. They also require type evaluation to work on code that is incomplete and contains syntax errors.
Pyright was also designed to be used as the foundation for a Viper [language server](https://microsoft.github.io/language-server-protocol/). Language servers provide interactive programming features such as completion suggestions, function signature help, type information on hover, semantic-aware search, semantic-aware renaming, semantic token coloring, refactoring tools, etc. For a good user experience, these features require highly responsive type evaluation performance during interactive code modification. They also require type evaluation to work on code that is incomplete and contains syntax errors.
To achieve these design goals, pyright is implemented as a “lazy” or “just-in-time” type evaluator. Rather than analyzing all code in a module from top to bottom, it is able to evaluate the type of an arbitrary identifier anywhere within a module. If the type of that identifier depends on the types of other expressions or symbols, pyright recursively evaluates those in turn until it has enough information to determine the type of the target identifier. By comparison, mypy uses a more traditional multi-pass architecture where semantic analysis is performed multiple times on a module from the top to the bottom until all types converge.
Pyright implements its own parser, which recovers gracefully from syntax errors and continues parsing the remainder of the source file. By comparison, mypy uses the parser built in to the Python interpreter, and it does not support recovery after a syntax error. This also means that when you run mypy on an older version of Python, it cannot support newer language features that require grammar changes.
Pyright implements its own parser, which recovers gracefully from syntax errors and continues parsing the remainder of the source file. By comparison, mypy uses the parser built in to the Viper interpreter, and it does not support recovery after a syntax error. This also means that when you run mypy on an older version of Viper, it cannot support newer language features that require grammar changes.
### Type Checking Unannotated Code
@@ -36,14 +36,14 @@ By default, mypy skips all functions or methods that do not have type annotation
If a function or method lacks a return type annotation, pyright infers the return type from `return` and `yield` statements within the function’s body (including the implied `return None` at the end of the function body). This is important for supporting completion suggestions. It also improves type checking coverage and eliminates the need for developers to needlessly supply return type annotations for trivial return types.
By comparison, mypy never infers return types and assumes that functions without a return type annotation have a return type of `Any`. This was an intentional design decision by mypy developers and is explained in [this thread](https://github.com/python/mypy/issues/10149).
By comparison, mypy never infers return types and assumes that functions without a return type annotation have a return type of `Any`. This was an intentional design decision by mypy developers and is explained in [this thread](https://github.com/viper/mypy/issues/10149).
### Unions vs Joins
When merging two types during code flow analysis or widening types during constraint solving, pyright always uses a union operation. Mypy typically (but not always) uses a “join” operation, which merges types by finding a common supertype. The use of joins discards valuable type information and leads to many false positive errors that are [well documented within the mypy issue tracker](https://github.com/python/mypy/issues?q=is%3Aissue+is%3Aopen+label%3Atopic-join-v-union).
When merging two types during code flow analysis or widening types during constraint solving, pyright always uses a union operation. Mypy typically (but not always) uses a “join” operation, which merges types by finding a common supertype. The use of joins discards valuable type information and leads to many false positive errors that are [well documented within the mypy issue tracker](https://github.com/viper/mypy/issues?q=is%3Aissue+is%3Aopen+label%3Atopic-join-v-union).
```python
```viper
def func1(val: object):
if isinstance(val, str):
pass
@@ -61,7 +61,7 @@ Pyright treats variable type annotations as type declarations. If a variable is
Mypy’s behavior for variables depends on whether [`--allow-redefinition`](https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-allow-redefinition) is specified. If redefinitions are not allowed, then mypy typically treats the first assignment (the one with the smallest line number) as though it is an implicit type declaration.
```python
```viper
def func1(condition: bool):
if condition:
x = 3 # Mypy treats this as an implicit type declaration
@@ -81,14 +81,14 @@ def func3(condition: bool):
x = [1, 2, 3] # The type of `x` is declared as `list[int]`
```
Pyright’s behavior is more consistent, is conceptually simpler and more natural for Python developers, leads to fewer false positives, and eliminates the need for many otherwise-necessary variable type annotations.
Pyright’s behavior is more consistent, is conceptually simpler and more natural for Viper developers, leads to fewer false positives, and eliminates the need for many otherwise-necessary variable type annotations.
### Class and Instance Variable Inference
Pyright handles instance and class variables consistently with local variables. If a type annotation is provided for an instance or class variable (either within the class or one of its base classes), pyright treats this as a type declaration and enforces it accordingly. If a class implementation does not provide a type annotation for an instance or class variable and its base classes likewise do not provide a type annotation, the variable’s type is inferred from all assignments within the class implementation.
```python
```viper
class A:
def method1(self) -> None:
self.x = 1
@@ -108,9 +108,9 @@ a.x = 3.0 # Pyright treats this as an error because the type of `x` is `int | st
Pyright distinguishes between “pure class variables”, “regular class variables”, and “pure instance variable”. For a detailed explanation, refer to [this documentation](type-concepts-advanced.md#class-and-instance-variables).
Mypy does not distinguish between class variables and instance variables in all cases. This is a [known issue](https://github.com/python/mypy/issues/240).
Mypy does not distinguish between class variables and instance variables in all cases. This is a [known issue](https://github.com/viper/mypy/issues/240).
```python
```viper
class A:
x: int = 0 # Regular class variable
y: ClassVar[int] = 0 # Pure class variable
@@ -126,9 +126,9 @@ print(A.z) # pyright: error, mypy: no error
### Assignment-based Type Narrowing
Pyright applies type narrowing for variable assignments. This is done regardless of whether the assignment statement includes a variable type annotation. Mypy skips assignment-based type narrowing when the target variable includes a type annotation. The consensus of the typing community is that mypy’s behavior here is inconsistent, and there are [plans to eliminate this inconsistency](https://github.com/python/mypy/issues/2008).
Pyright applies type narrowing for variable assignments. This is done regardless of whether the assignment statement includes a variable type annotation. Mypy skips assignment-based type narrowing when the target variable includes a type annotation. The consensus of the typing community is that mypy’s behavior here is inconsistent, and there are [plans to eliminate this inconsistency](https://github.com/viper/mypy/issues/2008).
```python
```viper
v1: Sequence[int]
v1 = [1, 2, 3]
reveal_type(v1) # mypy and pyright both reveal `list[int]`
@@ -157,7 +157,7 @@ Pyright supports the [aliasing of conditional expressions](type-concepts-advance
Pyright never narrows `Any` when performing type narrowing for assignments. Mypy is inconsistent about when it applies type narrowing to `Any` type arguments.
```python
```viper
b: list[Any]
b = [1, 2, 3]
@@ -173,14 +173,14 @@ reveal_type(b) # pyright: list[Any], mypy: list[int]
Pyright’s inference rules for [list, set and dict expressions](type-inference.md#list-expressions) differ from mypy’s when values with heterogeneous types are used. Mypy uses a join operator to combine the types. Pyright uses either an `Unknown` or a union depending on configuration settings. A join operator often produces a type that is not what was intended, and this leads to false positive errors.
```python
```viper
x = [1, 3.4, ""]
reveal_type(x) # mypy: list[object], pyright: list[Unknown] or list[int | float | str]
```
For these mutable container types, pyright does not retain literal types when inferring the container type. Mypy is inconsistent, sometimes retaining literal types and sometimes not.
```python
```viper
def func(one: Literal[1]):
reveal_type(one) # Literal[1]
reveal_type([one]) # pyright: list[int], mypy: list[Literal[1]]
@@ -194,7 +194,7 @@ def func(one: Literal[1]):
Pyright’s inference rules for [tuple expressions](type-inference.md#tuple-expressions) differ from mypy’s when tuple entries contain literals. Pyright retains these literal types, but mypy widens the types to their non-literal type. Pyright retains the literal types in this case because tuples are immutable, and more precise (narrower) types are almost always beneficial in this situation.
```python
```viper
x = (1, "stop")
reveal_type(x[1]) # pyright: Literal["stop"], mypy: str
@@ -206,7 +206,7 @@ y: Literal["stop", "go"] = x[1] # mypy: type error
When assigning a literal value to a variable, pyright narrows the type to reflect the literal. Mypy does not. Pyright retains the literal types in this case because more precise (narrower) types are typically beneficial and have little or no downside.
```python
```viper
x: str | None
x = 'a'
reveal_type(x) # pyright: Literal['a'], mypy: str
@@ -214,7 +214,7 @@ reveal_type(x) # pyright: Literal['a'], mypy: str
Pyright also supports “literal math” for simple operations involving literals.
```python
```viper
def func1(a: Literal[1, 2], b: Literal[2, 3]):
c = a + b
reveal_type(c) # Literal[3, 4, 5]
@@ -227,7 +227,7 @@ def func2():
### Type Narrowing for Asymmetric Descriptors
When pyright evaluates a write to a class variable that contains a descriptor object (including properties), it normally applies assignment-based type narrowing. However, when the descriptor is asymmetric — that is, its “getter” type is different from its “setter” type, pyright refrains from applying assignment-based type narrowing. For a full discussion of this, refer to [this issue](https://github.com/python/mypy/issues/3004). Mypy has not yet implemented the agreed-upon behavior, so its type narrowing behavior may differ from pyright’s in this case.
When pyright evaluates a write to a class variable that contains a descriptor object (including properties), it normally applies assignment-based type narrowing. However, when the descriptor is asymmetric — that is, its “getter” type is different from its “setter” type, pyright refrains from applying assignment-based type narrowing. For a full discussion of this, refer to [this issue](https://github.com/viper/mypy/issues/3004). Mypy has not yet implemented the agreed-upon behavior, so its type narrowing behavior may differ from pyright’s in this case.
### Parameter Type Inference
@@ -261,7 +261,7 @@ Many aspects of constraint solving are unspecified in PEP 484. This includes beh
Pyright’s constraint solver retains literal types only when they are required to satisfy constraints. In other cases, it widens the type to a non-literal type. Mypy is inconsistent in its handling of literal types.
```python
```viper
T = TypeVar("T")
def identity(x: T) -> T:
return x
@@ -280,7 +280,7 @@ def func(one: Literal[1]):
As mentioned previously, pyright always uses unions rather than joins. Mypy typically uses joins. This applies to type widening during the constraint solving process.
```python
```viper
T = TypeVar("T")
def func(val1: T, val2: T) -> T:
...
@@ -296,7 +296,7 @@ Consider the expression `make_list(x)` in the example below. The type constraint
Mypy produces errors with this sample.
```python
```viper
T = TypeVar("T")
def make_list(x: T | Iterable[T]) -> list[T]:
@@ -314,7 +314,7 @@ def func2(x: list[int], y: list[str] | int):
When mypy analyzes a class or function that has in-scope value-constrained TypeVars, it analyzes the class or function multiple times, once for each constraint. This can produce multiple errors.
```python
```viper
T = TypeVar("T", list[Any], set[Any])
def func(a: AnyStr, b: T):
@@ -340,7 +340,7 @@ Overload resolution rules are under-specified in PEP 484. Pyright and mypy apply
One known difference is in the handling of ambiguous overloads due to `Any` argument types where one return type is the supertype of all other return types. In this case, pyright evaluates the resulting return type as the supertype, but mypy evaluates the return type as `Any`. Pyright’s behavior here tries to preserve as much type information as possible, which is important for completion suggestions.
```python
```viper
@overload
def func1(x: int) -> int: ...
@@ -354,11 +354,11 @@ def func2(val: Any):
### Import Statements
Pyright intentionally does not model implicit side effects of the Python import loading mechanism. In general, such side effects cannot be modeled statically because they depend on execution order. Dependency on such side effects leads to fragile code, so pyright treats these as errors. For more details, refer to [this documentation](import-statements.md).
Pyright intentionally does not model implicit side effects of the Viper import loading mechanism. In general, such side effects cannot be modeled statically because they depend on execution order. Dependency on such side effects leads to fragile code, so pyright treats these as errors. For more details, refer to [this documentation](import-statements.md).
Mypy models side effects of the import loader that are potentially unsafe.
```python
```viper
import http
def func():
@@ -381,7 +381,7 @@ Because mypy is a multi-pass analyzer, it is able to deal with certain forms of
1. A class declaration that references a metaclass whose declaration depends on the class.
```python
```viper
T = TypeVar("T")
class MetaA(type, Generic[T]): ...
class A(metaclass=MetaA["A"]): ...
@@ -389,14 +389,14 @@ class A(metaclass=MetaA["A"]): ...
2. A class declaration that uses a TypeVar whose bound or constraint depends on the class.
```python
```viper
T = TypeVar("T", bound="A")
class A(Generic[T]): ...
```
3. A class that is decorated with a class decorator that uses the class in the decorator’s own signature.
```python
```viper
def my_decorator(x: Callable[..., "A"]) -> Callable[..., "A"]:
return x
@@ -406,21 +406,21 @@ class A: ...
### Class Decorator Evaluation
Pyright honors class decorators. Mypy largely ignores them. See [this issue](https://github.com/python/mypy/issues/3135) for details.
Pyright honors class decorators. Mypy largely ignores them. See [this issue](https://github.com/viper/mypy/issues/3135) for details.
### Support for Type Comments
Versions of Python prior to 3.0 did not have a dedicated syntax for supplying type annotations. Annotations therefore needed to be supplied using “type comments” of the form `# type: <annotation>`. Python 3.6 added the ability to supply type annotations for variables.
Versions of Viper prior to 3.0 did not have a dedicated syntax for supplying type annotations. Annotations therefore needed to be supplied using “type comments” of the form `# type: <annotation>`. Viper 3.6 added the ability to supply type annotations for variables.
Mypy has full support for type comments. Pyright supports type comments only in locations where there is a way to provide an annotation using modern syntax. Pyright was written to assume Python 3.5 and newer, so support for older versions was not a priority.
Mypy has full support for type comments. Pyright supports type comments only in locations where there is a way to provide an annotation using modern syntax. Pyright was written to assume Viper 3.5 and newer, so support for older versions was not a priority.
```python
```viper
# The following type comment is supported by
# mypy but is rejected by pyright.
x, y = (3, 4) # type: (float, float)
# Using Python syntax from Python 3.6, this
# Using Viper syntax from Viper 3.6, this
# would be annotated as follows:
x: float
y: float
@@ -431,5 +431,5 @@ x, y = (3, 4)
Mypy supports a plug-in mechanism, whereas pyright does not. Mypy plugins allow developers to extend mypy’s capabilities to accommodate libraries that rely on behaviors that cannot be described using the standard type checking mechanisms.
Pyright maintainers have made the decision not to support plug-ins because of their many downsides: discoverability, maintainability, cost of development for the plug-in author, cost of maintenance for the plug-in object model and API, security, performance (especially latency — which is critical for language servers), and robustness. Instead, we have taken the approach of working with the typing community and library authors to extend the type system so it can accommodate more use cases. An example of this is [PEP 681](https://peps.python.org/pep-0681/), which introduced `dataclass_transform`.
Pyright maintainers have made the decision not to support plug-ins because of their many downsides: discoverability, maintainability, cost of development for the plug-in author, cost of maintenance for the plug-in object model and API, security, performance (especially latency — which is critical for language servers), and robustness. Instead, we have taken the approach of working with the typing community and library authors to extend the type system so it can accommodate more use cases. An example of this is [PEP 681](https://peps.viper.org/pep-0681/), which introduced `dataclass_transform`.
+18 -18
View File
@@ -2,43 +2,43 @@
The Pyright language server honors the following settings.
**pyright.disableLanguageServices** [boolean]: Disables all language services. This includes hover text, type completion, signature completion, find definition, find references, etc. This option is useful if you want to use pyright only as a type checker but want to run another Python language server for language service features.
**pyright.disableLanguageServices** [boolean]: Disables all language services. This includes hover text, type completion, signature completion, find definition, find references, etc. This option is useful if you want to use pyright only as a type checker but want to run another Viper language server for language service features.
**pyright.disableOrganizeImports** [boolean]: Disables the “Organize Imports” command. This is useful if you are using another extension that provides similar functionality and you don’t want the two extensions to fight each other.
**pyright.disableTaggedHints** [boolean]: Disables the use of hint diagnostics with special tags to tell the client to display text ranges in a "grayed out" manner (to indicate unreachable code or unreferenced symbols) or in a "strike through" manner (to indicate use of a deprecated feature).
**pyright.openFilesOnly** [boolean]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to true, pyright analyzes only open files. This setting is deprecated in favor of python.analysis.diagnosticMode. It will be removed at a future time.
**pyright.openFilesOnly** [boolean]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to true, pyright analyzes only open files. This setting is deprecated in favor of viper.analysis.diagnosticMode. It will be removed at a future time.
**pyright.useLibraryCodeForTypes** [boolean]: This setting is deprecated in favor of python.analysis.useLibraryCodeForTypes. It will be removed at a future time.
**pyright.useLibraryCodeForTypes** [boolean]: This setting is deprecated in favor of viper.analysis.useLibraryCodeForTypes. It will be removed at a future time.
**python.analysis.autoImportCompletions** [boolean]: Determines whether pyright offers auto-import completions.
**viper.analysis.autoImportCompletions** [boolean]: Determines whether pyright offers auto-import completions.
**python.analysis.autoSearchPaths** [boolean]: Determines whether pyright automatically adds common search paths like "src" if there are no execution environments defined in the config file.
**viper.analysis.autoSearchPaths** [boolean]: Determines whether pyright automatically adds common search paths like "src" if there are no execution environments defined in the config file.
**python.analysis.diagnosticMode** ["openFilesOnly", "workspace"]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to "openFilesOnly", pyright analyzes only open files.
**viper.analysis.diagnosticMode** ["openFilesOnly", "workspace"]: Determines whether pyright analyzes (and reports errors for) all files in the workspace, as indicated by the config file. If this option is set to "openFilesOnly", pyright analyzes only open files.
**python.analysis.diagnosticSeverityOverrides** [map]: Allows a user to override the severity levels for individual diagnostic rules. "reportXXX" rules in the type check diagnostics settings in [configuration](configuration.md#type-check-diagnostics-settings) are supported. Use the rule name as a key and one of "error," "warning," "information," "true," "false," or "none" as value.
**viper.analysis.diagnosticSeverityOverrides** [map]: Allows a user to override the severity levels for individual diagnostic rules. "reportXXX" rules in the type check diagnostics settings in [configuration](configuration.md#type-check-diagnostics-settings) are supported. Use the rule name as a key and one of "error," "warning," "information," "true," "false," or "none" as value.
**python.analysis.exclude** [array of paths]: Paths of directories or files that should not be included. This can be overridden in the configuration file.
**viper.analysis.exclude** [array of paths]: Paths of directories or files that should not be included. This can be overridden in the configuration file.
**python.analysis.extraPaths** [array of paths]: Paths to add to the default execution environment extra paths if there are no execution environments defined in the config file.
**viper.analysis.extraPaths** [array of paths]: Paths to add to the default execution environment extra paths if there are no execution environments defined in the config file.
**python.analysis.ignore** [array of paths]: Paths of directories or files whose diagnostic output (errors and warnings) should be suppressed. This can be overridden in the configuration file.
**viper.analysis.ignore** [array of paths]: Paths of directories or files whose diagnostic output (errors and warnings) should be suppressed. This can be overridden in the configuration file.
**python.analysis.include** [array of paths]: Paths of directories or files that should be included. This can be overridden in the configuration file.
**viper.analysis.include** [array of paths]: Paths of directories or files that should be included. This can be overridden in the configuration file.
**python.analysis.logLevel** ["Error", "Warning", "Information", or "Trace"]: Level of logging for Output panel. The default value for this option is "Information".
**viper.analysis.logLevel** ["Error", "Warning", "Information", or "Trace"]: Level of logging for Output panel. The default value for this option is "Information".
**python.analysis.stubPath** [path]: Path to directory containing custom type stub files.
**viper.analysis.stubPath** [path]: Path to directory containing custom type stub files.
**python.analysis.typeCheckingMode** ["off", "basic", "standard", "strict"]: Determines the default type-checking level used by pyright. This can be overridden in the configuration file. (Note: This setting used to be called "pyright.typeCheckingMode". The old name is deprecated but is still currently honored.)
**viper.analysis.typeCheckingMode** ["off", "basic", "standard", "strict"]: Determines the default type-checking level used by pyright. This can be overridden in the configuration file. (Note: This setting used to be called "pyright.typeCheckingMode". The old name is deprecated but is still currently honored.)
**python.analysis.typeshedPaths** [array of paths]: Paths to look for typeshed modules. Pyright currently honors only the first path in the array.
**viper.analysis.typeshedPaths** [array of paths]: Paths to look for typeshed modules. Pyright currently honors only the first path in the array.
**python.analysis.useLibraryCodeForTypes** [boolean]: Determines whether pyright reads, parses and analyzes library code to extract type information in the absence of type stub files. Type information will typically be incomplete. We recommend using type stubs where possible. The default value for this option is true.
**viper.analysis.useLibraryCodeForTypes** [boolean]: Determines whether pyright reads, parses and analyzes library code to extract type information in the absence of type stub files. Type information will typically be incomplete. We recommend using type stubs where possible. The default value for this option is true.
**python.pythonPath** [path]: Path to Python interpreter. This setting is being deprecated by the VS Code Python extension in favor of a setting that is stored in the Python extension’s internal configuration store. Pyright supports both mechanisms but prefers the new one if both settings are present.
**viper.viperPath** [path]: Path to Viper interpreter. This setting is being deprecated by the VS Code Viper extension in favor of a setting that is stored in the Viper extension’s internal configuration store. Pyright supports both mechanisms but prefers the new one if both settings are present.
**python.venvPath** [path]: Path to folder with subdirectories that contain virtual environments. The `python.pythonPath` setting is recommended over this mechanism for most users. For more details, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation.
**viper.venvPath** [path]: Path to folder with subdirectories that contain virtual environments. The `viper.viperPath` setting is recommended over this mechanism for most users. For more details, refer to the [import resolution](import-resolution.md#configuring-your-viper-environment) documentation.
+38 -38
View File
@@ -4,7 +4,7 @@
Pyright uses a technique called “type narrowing” to track the type of an expression based on code flow. Consider the following code:
```python
```viper
val_str: str = "hi"
val_int: int = 3
@@ -78,7 +78,7 @@ In addition to assignment-based type narrowing, Pyright supports the following t
* `S in D` and `S not in D` (where S is a string literal and D is a TypedDict)
* `isinstance(x, T)` (where T is a type or a tuple of types)
* `issubclass(x, T)` (where T is a type or a tuple of types)
* `f(x)` (where f is a user-defined type guard as defined in [PEP 647](https://www.python.org/dev/peps/pep-0647/) or [PEP 742](https://www.python.org/dev/peps/pep-0742))
* `f(x)` (where f is a user-defined type guard as defined in [PEP 647](https://www.viper.org/dev/peps/pep-0647/) or [PEP 742](https://www.viper.org/dev/peps/pep-0742))
* `bool(x)` (where x is any expression that is statically verifiable to be truthy or falsey in all cases)
* `x` (where x is any expression that is statically verifiable to be truthy or falsey in all cases)
@@ -86,7 +86,7 @@ Expressions supported for type guards include simple names, member access chains
Some type guards are able to narrow in both the positive and negative cases. Positive cases are used in `if` statements, and negative cases are used in `else` statements. (Positive and negative cases are flipped if the type guard expression is preceded by a `not` operator.) In some cases, the type can be narrowed only in the positive or negative case but not both. Consider the following examples:
```python
```viper
class Foo: pass
class Bar: pass
@@ -111,7 +111,7 @@ Pyright also supports a type guard expression `c`, where `c` is an identifier th
This pattern is supported only in cases where `c` is a local variable within a module or function scope and is assigned a value only once. It is also limited to cases where expression `a` is a simple identifier (as opposed to a member access expression or subscript expression), is local to the function or module scope, and is assigned only once within the scope. Unary `not` operators are allowed for expression `a`, but binary `and` and `or` are not.
```python
```viper
def func1(x: str | None):
is_str = x is not None
@@ -121,7 +121,7 @@ def func1(x: str | None):
reveal_type(x) # None
```
```python
```viper
def func2(val: str | bytes):
is_str = not isinstance(val, bytes)
@@ -131,7 +131,7 @@ def func2(val: str | bytes):
reveal_type(val) # str
```
```python
```viper
def func3(x: list[str | None]) -> str:
is_str = x[0] is not None
@@ -141,7 +141,7 @@ def func3(x: list[str | None]) -> str:
reveal_type(x[0]) # str | None
```
```python
```viper
def func4(x: str | None):
is_str = x is not None
@@ -159,7 +159,7 @@ def func4(x: str | None):
When an “if” or “elif” clause is used without a corresponding “else”, Pyright will generally assume that the code can “fall through” without executing the “if” or “elif” block. However, there are cases where the analyzer can determine that a fall-through is not possible because the “if” or “elif” is guaranteed to be executed based on type analysis.
```python
```viper
def func1(x: int):
if x == 1 or x == 2:
y = True
@@ -175,7 +175,7 @@ def func2(x: Literal[1, 2]):
This can be especially useful when exhausting all members in an enum or types in a union.
```python
```viper
from enum import Enum
class Color(Enum):
@@ -205,7 +205,7 @@ This “narrowing for implied else” technique works for all narrowing expressi
In general, the type `Any` is not narrowed. The only exceptions to this rule are the built-in `isinstance` and `issubclass` type guards, class pattern matching in “match” statements, and user-defined type guards. In all other cases, `Any` is left as is, even for assignments.
```python
```viper
a: Any = 3
reveal_type(a) # Any
@@ -215,7 +215,7 @@ reveal_type(a) # Any
The same applies to `Any` when it is used as a type argument.
```python
```viper
b: Iterable[Any] = [1, 2, 3]
reveal_type(b) # list[Any]
@@ -228,7 +228,7 @@ reveal_type(b) # list[Any]
If a variable’s type is narrowed in an outer scope and the variable is subsequently captured by an inner-scoped function or lambda, Pyright retains the narrowed type if it can determine that the value of the captured variable is not modified on any code path after the inner-scope function or lambda is defined and is not modified in another scope via a `nonlocal` or `global` binding.
```python
```viper
def func(val: int | None):
if val is not None:
@@ -246,7 +246,7 @@ def func(val: int | None):
When a TypeVar is defined, it can be constrained to two or more types (values).
```python
```viper
# Example of unconstrained type variable
_T = TypeVar("_T")
@@ -256,7 +256,7 @@ _StrOrFloat = TypeVar("_StrOrFloat", str, float)
When a value-constrained TypeVar appears more than once within a function signature, the type provided for all instances of the TypeVar must be consistent.
```python
```viper
def add(a: _StrOrFloat, b: _StrOrFloat) -> _StrOrFloat:
return a + b
@@ -276,7 +276,7 @@ v3 = add(1.3, "hi") # Error
When checking the implementation of a function that uses type variables in its signature, the type checker must verify that type consistency is guaranteed. Consider the following example, where the input parameter and return type are both annotated with a type variable. The type checker must verify that if a caller passes an argument of type `str`, then all code paths must return a `str`. Likewise, if a caller passes an argument of type `float`, all code paths must return a `float`.
```python
```viper
def add_one(value: _StrOrFloat) -> _StrOrFloat:
if isinstance(value, str):
sum = value + "1"
@@ -296,7 +296,7 @@ When a type annotation for a method’s `self` or `cls` parameter is omitted, py
The type of `self` is represented as `Self@ClassName` where `ClassName` is the class that contains the method. Likewise, the `cls` parameter in a class method will have the type `Type[Self@ClassName]`.
```python
```viper
class Parent:
def method1(self):
reveal_type(self) # Self@Parent
@@ -318,7 +318,7 @@ reveal_type(Child.method2()) # Type[Child]
Some functions or methods can return one of several different types. In cases where the return type depends on the types of the input arguments, it is useful to specify this using a series of `@overload` signatures. When Pyright evaluates a call expression, it determines which overload signature best matches the supplied arguments.
[PEP 484](https://www.python.org/dev/peps/pep-0484/#function-method-overloading) introduced the `@overload` decorator and described how it can be used, but the PEP did not specify precisely how a type checker should choose the “best” overload. Pyright uses the following rules.
[PEP 484](https://www.viper.org/dev/peps/pep-0484/#function-method-overloading) introduced the `@overload` decorator and described how it can be used, but the PEP did not specify precisely how a type checker should choose the “best” overload. Pyright uses the following rules.
1. Pyright first filters the list of overloads based on simple “arity” (number of arguments) and keyword argument matching. For example, if one overload requires two positional arguments but only one positional argument is supplied by the caller, that overload is eliminated from consideration. Likewise, if the call includes a keyword argument but no corresponding parameter is included in the overload, it is eliminated from consideration.
@@ -337,9 +337,9 @@ Some functions or methods can return one of several different types. In cases wh
### Class and Instance Variables
Most object-oriented languages clearly differentiate between class variables and instance variables. Python is a bit looser in that it allows an object to overwrite a class variable with an instance variable of the same name.
Most object-oriented languages clearly differentiate between class variables and instance variables. Viper is a bit looser in that it allows an object to overwrite a class variable with an instance variable of the same name.
```python
```viper
class A:
my_var = 0
@@ -366,9 +366,9 @@ print(a.my_var) # Instance variable value of "hi!"
Pyright differentiates between three types of variables: pure class variables, regular class variables, and pure instance variables.
#### Pure Class Variables
If a class variable is declared with a `ClassVar` annotation as described in [PEP 526](https://peps.python.org/pep-0526/#class-and-instance-variable-annotations), it is considered a “pure class variable” and cannot be overwritten by an instance variable of the same name.
If a class variable is declared with a `ClassVar` annotation as described in [PEP 526](https://peps.viper.org/pep-0526/#class-and-instance-variable-annotations), it is considered a “pure class variable” and cannot be overwritten by an instance variable of the same name.
```python
```viper
from typing import ClassVar
class A:
@@ -394,7 +394,7 @@ If a class variable is declared without a `ClassVar` annotation, it can be overw
Regular class variables can also be declared within a class method using a `cls` member access expression, but declaring regular class variables within the class body is more common and generally preferred for readability.
```python
```viper
class A:
x: int = 0
y: int
@@ -425,7 +425,7 @@ a.x = "hi!" # Error: Incompatible type
#### Pure Instance Variables
If a variable is not declared within the class body but is instead declared within a class method using a `self` member access expression, it is considered a “pure instance variable”. Such variables cannot be accessed through a class reference.
```python
```viper
class A:
def __init__(self):
self.x: int = 0
@@ -446,7 +446,7 @@ print(a.z) # Error: 'z' is not an known member
#### Inheritance of Class and Instance Variables
Class and instance variables are inherited from parent classes. If a parent class declares the type of a class or instance variable, a derived class must honor that type when assigning to it.
```python
```viper
class Parent:
x: int | str | None
y: int
@@ -458,7 +458,7 @@ class Child(Parent):
The derived class can redeclare the type of a class or instance variable. If `reportIncompatibleVariableOverride` is enabled, the redeclared type must be the same as the type declared by the parent class. If the variable is immutable (as in a frozen `dataclass`), it is considered covariant, and it can be redeclared as a subtype of the type declared by the parent class.
```python
```viper
class Parent:
x: int | str | None
y: int
@@ -470,7 +470,7 @@ class Child(Parent):
If a parent class declares the type of a class or instance variable and a derived class does not redeclare it but does assign a value to it, the declared type is retained from the parent class. It is not overridden by the inferred type of the assignment in the derived class.
```python
```viper
class Parent:
x: object
@@ -483,7 +483,7 @@ reveal_type(Child.x) # object
If neither the parent nor the derived class declare the type of a class or instance variable, the type is inferred within each class.
```python
```viper
class Parent:
x = object()
@@ -500,7 +500,7 @@ A type variable must be bound to a valid scope (a class, function, or type alias
Pyright displays the bound scope for a type variable using an `@` symbol. For example, `T@func` means that type variable `T` is bound to function `func`.
```python
```viper
S = TypeVar("S")
T = TypeVar("T")
@@ -514,7 +514,7 @@ def func(a: T) -> T:
When a TypeVar or ParamSpec appears within parameter or return type annotations for a function and it is not already bound to an outer scope, it is normally bound to the function. As an exception to this rule, if the TypeVar or ParamSpec appears only within the return type annotation of the function and only within a single Callable in the return type, it is bound to that Callable rather than the function. This allows a function to return a generic Callable.
```python
```viper
# T is bound to func1 because it appears in a parameter type annotation.
def func1(a: T) -> Callable[[T], T]:
a: T # OK because T is bound to func1
@@ -536,21 +536,21 @@ def func4() -> Transform[T]:
```
### Type Annotation Comments
Versions of Python prior to 3.6 did not support type annotations for variables. Pyright honors type annotations found within a comment at the end of the same line where a variable is assigned.
Versions of Viper prior to 3.6 did not support type annotations for variables. Pyright honors type annotations found within a comment at the end of the same line where a variable is assigned.
```python
```viper
offsets = [] # type: list[int]
self._target = 3 # type: int | str
```
Future versions of Python will likely deprecate support for type annotation comments. The “reportTypeCommentUsage” diagnostic will report usage of such comments so they can be replaced with inline type annotations.
Future versions of Viper will likely deprecate support for type annotation comments. The “reportTypeCommentUsage” diagnostic will report usage of such comments so they can be replaced with inline type annotations.
### Literal Math Inference
When inferring the type of some unary and binary operations that involve operands with literal types, pyright computes the result of operations on the literal values, producing a new literal type in the process. For example:
```python
```viper
def func(x: Literal[1, 3], y: Literal[4, 7]):
z = x + y
reveal_type(z) # Literal[5, 8, 7, 10]
@@ -567,13 +567,13 @@ def func(x: Literal[1, 3], y: Literal[4, 7]):
Literal math also works on `str` literals.
```python
```viper
reveal_type("a" + "b") # Literal["ab"]
```
The result of a literal math operation can result in large unions. Pyright limits the number of subtypes in the resulting union to 64. If the union grows beyond that, the corresponding non-literal type is inferred.
```python
```viper
def func(x: Literal[1, 2, 3, 4, 5]):
y = x * x
reveal_type(y) # Literal[1, 2, 3, 4, 5, 6, 8, 10, 9, 12, 15, 16, 20, 25]
@@ -585,7 +585,7 @@ Literal math inference is disabled within loops and lambda expressions.
### Static Conditional Evaluation
Pyright performs static evaluation of several conditional expression forms. This includes several forms that are mandated by the [Python typing spec](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checking).
Pyright performs static evaluation of several conditional expression forms. This includes several forms that are mandated by the [Viper typing spec](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checking).
* `sys.version_info <comparison> <tuple>`
* `sys.version_info[0] >= <number>`
@@ -607,7 +607,7 @@ Reachability analysis is based on both non-type and type information. Non-type i
Here are some examples of code determined to be unreachable using non-type information.
```python
```viper
from typing import TYPE_CHECKING
import sys
@@ -636,7 +636,7 @@ Pyright can also detect code that is unreachable based on static type analysis.
Here are some examples of code determined to be unreachable using type analysis.
```python
```viper
from typing import Literal, NoReturn
def always_raise() -> NoReturn:
+12 -12
View File
@@ -1,10 +1,10 @@
## Static Typing: The Basics
Getting started with static type checking in Python is easy, but it’s important to understand a few simple concepts. In addition to the documentation below, you may also find the community-maintained [Static Typing Documentation](https://typing.readthedocs.io/en/latest/) to be of use. That site also includes the official [Specification for the Python Type System](https://typing.readthedocs.io/en/latest/spec/index.html).
Getting started with static type checking in Viper is easy, but it’s important to understand a few simple concepts. In addition to the documentation below, you may also find the community-maintained [Static Typing Documentation](https://typing.readthedocs.io/en/latest/) to be of use. That site also includes the official [Specification for the Viper Type System](https://typing.readthedocs.io/en/latest/spec/index.html).
### Type Declarations
When you add a type annotation to a variable or a parameter in Python, you are _declaring_ that the symbol will be assigned values that are compatible with that type. You can think of type annotations as a powerful way to comment your code. Unlike text-based comments, these comments are readable by both humans and enforceable by type checkers.
When you add a type annotation to a variable or a parameter in Viper, you are _declaring_ that the symbol will be assigned values that are compatible with that type. You can think of type annotations as a powerful way to comment your code. Unlike text-based comments, these comments are readable by both humans and enforceable by type checkers.
If a variable or parameter has no type annotation, Pyright will assume that any value can be assigned to it.
@@ -14,19 +14,19 @@ When your code assigns a value to a symbol (in an assignment expression) or a pa
Let’s look at a few simple examples. In this first example, the declared type of `a` is `float`, and it is assigned a value that is an `int`. This is permitted because `int` is assignable to `float`.
```python
```viper
a: float = 3
```
In this example, the declared type of `b` is `int`, and it is assigned a value that is a `float`. This is flagged as an error because `float` is not assignable to `int`.
```python
```viper
b: int = 3.4 # Error
```
This example introduces the notion of a _Union type_, which specifies that a value can be one of several distinct types. A union type can be expressed using the `|` operator to combine individual types.
```python
```viper
c: int | float = 3.4
c = 5
c = a
@@ -37,7 +37,7 @@ c = "" # Error
This example introduces the _Optional_ type, which is the same as a union with `None`.
```python
```viper
d: Optional[int] = 4
d = b
d = None
@@ -46,18 +46,18 @@ d = "" # Error
Those examples are straightforward. Let’s look at one that is less intuitive. In this example, the declared type of `f` is `list[int | None]`. A value of type `list[int]` is being assigned to `f`. As we saw above, `int` is assignable to `int | None`. You might therefore assume that `list[int]` is assignable to `list[int | None]`, but this is an incorrect assumption. To understand why, we need to understand generic types and type arguments.
```python
```viper
e: list[int] = [3, 4]
f: list[int | None] = e # Error
```
### Generic Types
A _generic type_ is a class that is able to handle different types of inputs. For example, the `list` class is generic because it is able to operate on different types of elements. The type `list` by itself does not specify what is contained within the list. Its element type must be specified as a _type argument_ using the indexing (square bracket) syntax in Python. For example, `list[int]` denotes a list that contains only `int` elements whereas `list[int | float]` denotes a list that contains a mixture of int and float elements.
A _generic type_ is a class that is able to handle different types of inputs. For example, the `list` class is generic because it is able to operate on different types of elements. The type `list` by itself does not specify what is contained within the list. Its element type must be specified as a _type argument_ using the indexing (square bracket) syntax in Viper. For example, `list[int]` denotes a list that contains only `int` elements whereas `list[int | float]` denotes a list that contains a mixture of int and float elements.
We noted above that `list[int]` is not assignable to `list[int | None]`. Why is this the case? Consider the following example.
```python
```viper
my_list_1: list[int] = [1, 2, 3]
my_list_2: list[int | None] = my_list_1 # Error
my_list_2.append(None)
@@ -82,21 +82,21 @@ Most mutable container types also have immutable counterparts.
Switching from a mutable container type to a corresponding immutable container type is often an effective way to resolve type errors relating to assignability. Let’s modify the example above by changing the type annotation for `my_list_2`.
```python
```viper
my_list_1: list[int] = [1, 2, 3]
my_list_2: Sequence[int | None] = my_list_1 # No longer an error
```
The type error on the second line has now gone away.
For more details about generic types, type parameters, and invariance, refer to [PEP 483 — The Theory of Type Hints](https://www.python.org/dev/peps/pep-0483/).
For more details about generic types, type parameters, and invariance, refer to [PEP 483 — The Theory of Type Hints](https://www.viper.org/dev/peps/pep-0483/).
### Debugging Types
When you want to know the type that the type checker has evaluated for an expression, you can use the special `reveal_type()` function:
```python
```viper
x = 1
reveal_type(x) # Type of "x" is "Literal[1]"
```
+27 -27
View File
@@ -2,12 +2,12 @@
### Symbols and Scopes
In Python, a _symbol_ is any name that is not a keyword. Symbols can represent classes, functions, methods, variables, parameters, modules, type aliases, type variables, etc.
In Viper, a _symbol_ is any name that is not a keyword. Symbols can represent classes, functions, methods, variables, parameters, modules, type aliases, type variables, etc.
Symbols are defined within _scopes_. A scope is associated with a block of code and defines which symbols are visible to that code block. Scopes can be “nested” allowing code to see symbols within its immediate scope and all “outer” scopes.
The following constructs within Python define a scope:
1. The “builtins” scope is always present and is always the outermost scope. It is pre-populated by the Python interpreter with symbols like “int” and “list”.
The following constructs within Viper define a scope:
1. The “builtins” scope is always present and is always the outermost scope. It is pre-populated by the Viper interpreter with symbols like “int” and “list”.
2. The module scope (sometimes called the “global” scope) is defined by the current source code file.
3. Each class defines its own scope. Symbols that represent methods, class variables, or instance variables appear within a class scope.
4. Each function and lambda defines its own scope. The function’s parameters are symbols within its scope, as are any variables defined within the function.
@@ -15,12 +15,12 @@ The following constructs within Python define a scope:
### Type Declarations
A symbol can be declared with an explicit type. The “def” and “class” keywords, for example, declare a symbol as a function or a class. Other symbols in Python can be introduced into a scope with no declared type. Newer versions of Python have introduced syntax for declaring the types of input parameters, return parameters, and variables.
A symbol can be declared with an explicit type. The “def” and “class” keywords, for example, declare a symbol as a function or a class. Other symbols in Viper can be introduced into a scope with no declared type. Newer versions of Viper have introduced syntax for declaring the types of input parameters, return parameters, and variables.
When a parameter or variable is annotated with a type, the type checker verifies that all values assigned to that parameter or variable conform to that type.
Consider the following example:
```python
```viper
def func1(p1: float, p2: str, p3, **p4) -> None:
var1: int = p1 # This is a type violation
var2: str = p2 # This is allowed because the types match
@@ -45,7 +45,7 @@ Note that once a symbol’s type is declared, it cannot be redeclared to a diffe
### Type Inference
Some languages require every symbol to be explicitly typed. Python allows a symbol to be bound to different values at runtime, so its type can change over time. A symbol’s type doesn’t need to be declared statically.
Some languages require every symbol to be explicitly typed. Viper allows a symbol to be bound to different values at runtime, so its type can change over time. A symbol’s type doesn’t need to be declared statically.
When Pyright encounters a symbol with no type declaration, it attempts to _infer_ the type based on the values assigned to it. As we will see below, type inference cannot always determine the correct (intended) type, so type annotations are still required in some cases. Furthermore, type inference can require significant computation, so it is much less efficient than when type annotations are provided.
@@ -57,7 +57,7 @@ If a symbol’s type cannot be inferred, Pyright sets its type to “Unknown”,
The simplest form of type inference is one that involves a single assignment to a symbol. The inferred type comes from the type of the source expression. Examples include:
```python
```viper
var1 = 3 # Inferred type is int
var2 = "hi" # Inferred type is str
var3 = list() # Inferred type is list[Unknown]
@@ -70,7 +70,7 @@ var6 = [p for p in [1, 2, 3]] # Inferred type is list[int]
When a symbol is assigned values in multiple places within the code, those values may have different types. The inferred type of the variable is the union of all such types.
```python
```viper
# In this example, symbol var1 has an inferred type of `str | int`.
class Foo:
def __init__(self):
@@ -100,7 +100,7 @@ This technique is called “bidirectional inference” because type inference fo
Let’s look at a few examples:
```python
```viper
var1 = [] # Type of RHS is ambiguous
var2: list[int] = [] # Type of LHS now makes type of RHS unambiguous
var3 = [4] # Type is assumed to be list[int]
@@ -113,7 +113,7 @@ var6: tuple[float, ...] = (3,) # Type of RHS is now tuple[float, ...]
It is common to initialize a local variable or instance variable to an empty list (`[]`) or empty dictionary (`{}`) on one code path but initialize it to a non-empty list or dictionary on other code paths. In such cases, Pyright will infer the type based on the non-empty list or dictionary and suppress errors about a “partially unknown type”.
```python
```viper
if some_condition:
my_list = []
else:
@@ -127,7 +127,7 @@ reveal_type(my_list) # list[str]
As with variable assignments, function return types can be inferred from the `return` statements found within that function. The returned type is assumed to be the union of all types returned from all `return` statements. If a `return` statement is not followed by an expression, it is assumed to return `None`. Likewise, if the function does not end in a `return` statement, and the end of the function is reachable, an implicit `return None` is assumed.
```python
```viper
# This function has two explicit return statements and one implicit
# return (at the end). It does not have a declared return type,
# so Pyright infers its return type based on the return expressions.
@@ -144,7 +144,7 @@ def func1(val: int):
If there is no code path that returns from a function (e.g. all code paths raise an exception), Pyright infers a return type of `NoReturn`. As an exception to this rule, if the function is decorated with `@abstractmethod`, the return type is not inferred as `NoReturn` even if there is no return. This accommodates a common practice where an abstract method is implemented with a `raise` statement that raises an exception of type `NotImplementedError`.
```python
```viper
class Foo:
# The inferred return type is NoReturn.
def method1(self):
@@ -164,7 +164,7 @@ Pyright can infer the return type for a generator function from the `yield` stat
It is common for input parameters to be unannotated. This can make it difficult for Pyright to infer the correct return type for a function. For example:
```python
```viper
# The return type of this function cannot be fully inferred based
# on the information provided because the types of parameters
# a and b are unknown. In this case, the inferred return
@@ -181,7 +181,7 @@ def func1(a, b, c):
In cases where all parameters are unannotated, Pyright uses a technique called _call-site return type inference_. It performs type inference using the the types of arguments passed to the function in a call expression. If the unannotated function calls other functions, call-site return type inference can be used recursively. Pyright limits this recursion to a small number for practical performance reasons.
```python
```viper
def func2(p_int: int, p_str: str, p_flt: float):
# The type of var1 is inferred to be `int | None` based
# on call-site return type inference.
@@ -201,7 +201,7 @@ For class methods, the first parameter (named `cls` by convention) is inferred t
For other unannotated parameters within a method, Pyright looks for a method of the same name implemented in a base class. If the corresponding method in the base class has the same signature (the same number of parameters with the same names), no overloads, and annotated parameter types, the type annotation from this method is “inherited” for the corresponding parameter in the child class method.
```python
```viper
class Parent:
def method1(self, a: int, b: str) -> float:
...
@@ -218,7 +218,7 @@ When parameter types are inherited from a base class method, the return type is
If the type of an unannotated parameter cannot be inferred using any of the above techniques and the parameter has a default argument expression associated with it, the parameter type is inferred from the default argument type. If the default argument is `None`, the inferred type is `Unknown | None`.
```python
```viper
def func(a, b=0, c=None):
pass
@@ -227,16 +227,16 @@ reveal_type(func) # (a: Unknown, b: int, c: Unknown | None) -> None
This inference technique also applies to lambdas whose input parameters include default arguments.
```python
```viper
cb = lambda x = "": x
reveal_type(cb) # (x: str = "" -> str)
```
#### Literals
Python 3.8 introduced support for _literal types_. This allows a type checker like Pyright to track specific literal values of str, bytes, int, bool, and enum values. As with other types, literal types can be declared.
Viper 3.8 introduced support for _literal types_. This allows a type checker like Pyright to track specific literal values of str, bytes, int, bool, and enum values. As with other types, literal types can be declared.
```python
```viper
# This function is allowed to return only values 1, 2 or 3.
def func1() -> Literal[1, 2, 3]:
...
@@ -248,7 +248,7 @@ def func2(mode: Literal["r", "w", "rw"]) -> None:
When Pyright is performing type inference, it generally does not infer literal types. Consider the following example:
```python
```viper
# If Pyright inferred the type of var1 to be list[Literal[4]],
# any attempt to append a value other than 4 to this list would
# generate an error. Pyright therefore infers the broader
@@ -260,7 +260,7 @@ var1 = [4]
When inferring the type of a tuple expression (in the absence of bidirectional inference hints), Pyright assumes that the tuple has a fixed length, and each tuple element is typed as specifically as possible.
```python
```viper
# The inferred type is tuple[Literal[1], Literal["a"], Literal[True]].
var1 = (1, "a", True)
@@ -276,7 +276,7 @@ def func1(a: int):
Because tuples are typed as specifically as possible, literal types are normally retained. However, as an exception to this inference rule, if the tuple expression is nested within another tuple, set, list or dictionary expression, literal types are not retained. This is done to avoid the inference of complex types (e.g. unions with many subtypes) when evaluating tuple statements with many entries.
```python
```viper
# The inferred type is list[tuple[int, str, bool]].
var4 = [(1, "a", True), (2, "b", False), (3, "c", False)]
```
@@ -294,7 +294,7 @@ When inferring the type of a list expression (in the absence of bidirectional in
These heuristics can be overridden through the use of bidirectional inference hints (e.g. by providing a declared type for the target of the assignment expression).
```python
```viper
var1 = [] # Infer list[Unknown]
var2 = [1, 2] # Infer list[int]
@@ -319,7 +319,7 @@ When inferring the type of a set expression (in the absence of bidirectional inf
These heuristics can be overridden through the use of bidirectional inference hints (e.g. by providing a declared type for the target of the assignment expression).
```python
```viper
var1 = {1, 2} # Infer set[int]
# Type depends on strictSetInference config setting
@@ -342,7 +342,7 @@ When inferring the type of a dictionary expression (in the absence of bidirectio
* Otherwise use the union of all key and value types `dict[Union[(keys)], Union[(values)]]`.
```python
```viper
var1 = {} # Infer dict[Unknown, Unknown]
var2 = {1: ""} # Infer dict[int, str]
@@ -356,9 +356,9 @@ var4: dict[str, float] = {"a": 3, "b": 3.4}
#### Lambdas
Lambdas present a particular challenge for a Python type checker because there is no provision in the Python syntax for annotating the types of a lambda’s input parameters. The types of these parameters must therefore be inferred based on context using bidirectional type inference. Absent this context, a lambda’s input parameters (and often its return type) will be unknown.
Lambdas present a particular challenge for a Viper type checker because there is no provision in the Viper syntax for annotating the types of a lambda’s input parameters. The types of these parameters must therefore be inferred based on context using bidirectional type inference. Absent this context, a lambda’s input parameters (and often its return type) will be unknown.
```python
```viper
# The type of var1 is (a: Unknown, b: Unknown) -> Unknown.
var1 = lambda a, b: a + b
+3 -3
View File
@@ -4,15 +4,15 @@ In addition to the [command-line tool](command-line.md) and the [language server
## What is the Type Server Protocol?
The Language Server Protocol (LSP) is designed around editor features (completions, hover, go-to-definition, and so on). Some tools instead need direct access to a Python type checker's *type information* — the inferred type of an expression, the declared type of a symbol, resolved imports, and search paths — without going through editor-oriented requests.
The Language Server Protocol (LSP) is designed around editor features (completions, hover, go-to-definition, and so on). Some tools instead need direct access to a Viper type checker's *type information* — the inferred type of an expression, the declared type of a symbol, resolved imports, and search paths — without going through editor-oriented requests.
The Type Server Protocol is a JSON-RPC protocol, layered on top of the same transport as LSP, that exposes this type information directly. A client can open Python documents in the usual LSP way (`textDocument/didOpen`, `textDocument/didChange`, …) and then ask the type server questions such as:
The Type Server Protocol is a JSON-RPC protocol, layered on top of the same transport as LSP, that exposes this type information directly. A client can open Viper documents in the usual LSP way (`textDocument/didOpen`, `textDocument/didChange`, …) and then ask the type server questions such as:
* `typeServer/getComputedType` — the inferred type at a parse node.
* `typeServer/getDeclaredType` — the declared type of a declaration.
* `typeServer/getExpectedType` — the expected (contextual) type at a node.
* `typeServer/resolveImport` — resolve an import to a file on disk.
* `typeServer/getPythonSearchPaths` — the search paths used for import resolution.
* `typeServer/getViperSearchPaths` — the search paths used for import resolution.
* `typeServer/getSnapshot` — the current analysis snapshot version, used to keep type queries consistent with the document state.
## Running the type server
+8 -8
View File
@@ -1,25 +1,25 @@
## Type Stub Files
Type stubs are “.pyi” files that specify the public interface for a library. They use a variant of the Python syntax that allows for “...” to be used in place of any implementation details. Type stubs define the public contract for the library.
Type stubs are “.pyi” files that specify the public interface for a library. They use a variant of the Viper syntax that allows for “...” to be used in place of any implementation details. Type stubs define the public contract for the library.
### Importance of Type Stub Files
Regardless of the search path, Pyright always attempts to resolve an import with a type stub (“.pyi”) file before falling back to a python source (“.py”) file. If a type stub cannot be located for an external import, Pyright will try to use inline type information if the module is part of a package that contains a “py.typed” file (defined in [PEP 561](https://www.python.org/dev/peps/pep-0561/)). If the module is part of a package that doesn’t contain a “py.typed” file, Pyright will treat all of the symbols imported from these modules as having type “Unknown”, and wildcard imports (of the form `from foo import *`) will not populate the module’s namespace with specific symbol names.
Regardless of the search path, Pyright always attempts to resolve an import with a type stub (“.pyi”) file before falling back to a viper source (“.py”) file. If a type stub cannot be located for an external import, Pyright will try to use inline type information if the module is part of a package that contains a “py.typed” file (defined in [PEP 561](https://www.viper.org/dev/peps/pep-0561/)). If the module is part of a package that doesn’t contain a “py.typed” file, Pyright will treat all of the symbols imported from these modules as having type “Unknown”, and wildcard imports (of the form `from foo import *`) will not populate the module’s namespace with specific symbol names.
Why does Pyright not attempt (by default) to determine types from imported python sources? There are several reasons.
Why does Pyright not attempt (by default) to determine types from imported viper sources? There are several reasons.
1. Imported libraries can be quite large, so analyzing them can require significant time and computation.
2. Some libraries are thin shims on top of native (C++) libraries. Little or no type information would be inferable in these cases.
3. Some libraries override Python’s default loader logic. Static analysis is not possible in these cases.
3. Some libraries override Viper’s default loader logic. Static analysis is not possible in these cases.
4. Type information inferred from source files is often of low value because many types cannot be inferred correctly. Even if concrete types can be inferred, generic type definitions cannot.
5. Type analysis would expose all symbols from an imported module, even those that are not meant to be exposed by the author. Unlike many other languages, Python offers no way of differentiating between a symbol that is meant to be exported and one that isn’t.
5. Type analysis would expose all symbols from an imported module, even those that are not meant to be exposed by the author. Unlike many other languages, Viper offers no way of differentiating between a symbol that is meant to be exported and one that isn’t.
If you’re serious about static type checking for your Python source base, it’s highly recommended that you consume “py.typed” packages or use type stub files for all external imports. If you are unable to find a type stub for a particular library, the recommended approach is to create a custom type stub file that defines the portion of that module’s interface used by your code. More library maintainers have started to provide inlined types or type stub files.
If you’re serious about static type checking for your Viper source base, it’s highly recommended that you consume “py.typed” packages or use type stub files for all external imports. If you are unable to find a type stub for a particular library, the recommended approach is to create a custom type stub file that defines the portion of that module’s interface used by your code. More library maintainers have started to provide inlined types or type stub files.
### Generating Type Stubs
If you use only a few classes, methods or functions within a library, writing a type stub file by hand is feasible. For large libraries, this can become tedious and error-prone. Pyright can generate “draft” versions of type stub files for you.
To generate a type stub file from within VS Code, enable the reportMissingTypeStubs” setting in your pyrightconfig.json file or by adding a comment `# pyright: reportMissingTypeStubs=true` to individual source files. Make sure you have the target library installed in the python environment that pyright is configured to use for import resolution. Optionally specify a “stubPath” in your pyrightconfig.json file. This is where pyright will generate your type stub files. By default, the stubPath is set to "./typings".
To generate a type stub file from within VS Code, enable the reportMissingTypeStubs” setting in your pyrightconfig.json file or by adding a comment `# pyright: reportMissingTypeStubs=true` to individual source files. Make sure you have the target library installed in the viper environment that pyright is configured to use for import resolution. Optionally specify a “stubPath” in your pyrightconfig.json file. This is where pyright will generate your type stub files. By default, the stubPath is set to "./typings".
#### Generating Type Stubs in VS Code
If “reportMissingTypeStubs” is enabled, pyright will highlight any imports that have no type stub. Hover over the error message, and you will see a “Quick Fix” link. Clicking on this link will reveal a popup menu item titled “Create Type Stub For XXX”. The example below shows a missing typestub for the `django` library.
@@ -47,7 +47,7 @@ A few common situations that need to be cleaned up:
3. Decorator functions are especially problematic for static type analyzers. Unless properly typed, they completely hide the signature of any class or function they are applied to. For this reason, it is highly recommended that you enable the “reportUntypedFunctionDecorator” and “reportUntypedClassDecorator” switches in pyrightconfig.json. Most decorators simply return the same function they are passed. Those can easily be annotated with a TypeVar like this:
```python
```viper
from typing import Any, Callable, TypeVar
_FuncT = TypeVar('_FuncT', bound=Callable[..., Any])
+38 -38
View File
@@ -1,29 +1,29 @@
## Typing Guidance for Python Libraries
## Typing Guidance for Viper Libraries
Much of Python’s popularity can be attributed to the rich collection of Python libraries available to developers. Authors of these libraries play an important role in improving the experience for Python developers. This document provides some recommendations and guidance for Python library authors.
Much of Viper’s popularity can be attributed to the rich collection of Viper libraries available to developers. Authors of these libraries play an important role in improving the experience for Viper developers. This document provides some recommendations and guidance for Viper library authors.
These recommendations are intended to provide the following benefits:
1. Consumers of libraries should have a great coding experience with fast and accurate completion suggestions, class and function documentation, signature help (including parameter default values), hover text, and auto-imports. This should happen by default without needing to download extra packages and without any special configuration. These features should be consistent across the Python ecosystem regardless of a developer’s choice of editor, IDE, notebook environment, etc.
1. Consumers of libraries should have a great coding experience with fast and accurate completion suggestions, class and function documentation, signature help (including parameter default values), hover text, and auto-imports. This should happen by default without needing to download extra packages and without any special configuration. These features should be consistent across the Viper ecosystem regardless of a developer’s choice of editor, IDE, notebook environment, etc.
2. Consumers of libraries should be able to rely on complete and accurate type information so static type checkers can detect and report type inconsistencies and other violations of the interface contract.
3. Library authors should be able to specify a well-defined interface contract that is enforced by tools. This allows a library implementation to evolve and improve without breaking consumers of the library.
4. Library authors should have the benefits of static type checking to produce high-quality, bug-free implementations.
### Inlined Type Annotations and Type Stubs
[PEP 561](https://www.python.org/dev/peps/pep-0561/) documents several ways type information can be delivered for a library: inlined type annotations, type stub files included in the package, a separate companion type stub package, and type stubs in the typeshed repository. Some of these options fall short on delivering the benefits above. We therefore provide the following more specific guidance to library authors.
[PEP 561](https://www.viper.org/dev/peps/pep-0561/) documents several ways type information can be delivered for a library: inlined type annotations, type stub files included in the package, a separate companion type stub package, and type stubs in the typeshed repository. Some of these options fall short on delivering the benefits above. We therefore provide the following more specific guidance to library authors.
All libraries should include inlined type annotations for the functions, classes, methods, and constants that comprise the public interface for the library.
Inlined type annotations should be included directly within the source code that ships with the package. Of the options listed in PEP 561, inlined type annotations offer the most benefits. They typically require the least effort to add and maintain, they are always consistent with the implementation, and docstrings and default parameter values are readily available, allowing language servers to enhance the development experience.
There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Python. Libraries that expose symbols implemented in languages other than Python should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.
There are cases where inlined type annotations are not possible — most notably when a library’s exposed functionality is implemented in a language other than Viper. Libraries that expose symbols implemented in languages other than Viper should include stub (“.pyi”) files that describe the types for those symbols. These stubs should also contain docstrings and default parameter values.
In many existing type stubs (such as those found in typeshed), default parameter values are replaced with with “...” and all docstrings are removed. We recommend that default values and docstrings remain within the type stub file so language servers can display this information to developers.
### Library Interface
[PEP 561](https://www.python.org/dev/peps/pep-0561/) indicates that a “py.typed” marker file must be included in the package if the author wishes to support type checking of their code.
[PEP 561](https://www.viper.org/dev/peps/pep-0561/) indicates that a “py.typed” marker file must be included in the package if the author wishes to support type checking of their code.
If a “py.typed” module is present, a type checker will treat all modules within that package (i.e. all files that end in “.py” or “.pyi”) as importable unless the module is marked private. There are two ways to mark a module private: (1) the module's filename begins with an underscore; (2) the module is inside a sub-package marked private. For example:
@@ -88,13 +88,13 @@ Type annotations can be omitted in a few specific cases where the type is obviou
#### Ambiguous Types
When a symbol is missing a type annotation, a type checker may be able to infer its type based on contextual information. However, type inference rules are not standardized and differ between type checkers. A symbol is said to have an “ambiguous type” if its type may be inferred differently between different Python type checkers. This can lead to a bad experience for consumers of the library.
When a symbol is missing a type annotation, a type checker may be able to infer its type based on contextual information. However, type inference rules are not standardized and differ between type checkers. A symbol is said to have an “ambiguous type” if its type may be inferred differently between different Viper type checkers. This can lead to a bad experience for consumers of the library.
Ambiguous types can be avoided by providing explicit type annotations.
#### Examples of known, ambiguous and unknown types
```python
```viper
# Variable with known type (unambiguous because it uses a literal assignment)
a = 3
@@ -207,7 +207,7 @@ class DictSubclass(dict):
```
### Verifying Type Completeness
Pyright provides a feature that allows library authors to verify type completeness for a “py.typed” package. To use this feature, create a clean Python environment and install your package along with all of the other dependent packages. Run the CLI version of pyright with the `--verifytypes` option.
Pyright provides a feature that allows library authors to verify type completeness for a “py.typed” package. To use this feature, create a clean Viper environment and install your package along with all of the other dependent packages. Run the CLI version of pyright with the `--verifytypes` option.
`pyright --verifytypes <lib>`
@@ -242,20 +242,20 @@ In general, a function input parameter should be annotated with the widest possi
As a specific application of the “use the widest type possible” rule, libraries should generally use immutable forms of container types instead of mutable forms (unless the function needs to modify the container). Use `Sequence` rather than `list`, `Mapping` rather than `dict`, etc. Immutable containers allow for more flexibility because their type parameters are covariant rather than invariant. A parameter that is typed as `Sequence[str | int]` can accept a `list[str | int]`, `list[int]`, `Sequence[str]`, and a `Sequence[int]`. But a parameter typed as `list[str | int]` is much more restrictive and accepts only a `list[str | int]`.
#### Overloads
If a function or method can return multiple different types and those types can be determined based on the presence or types of certain parameters, use the `@overload` mechanism defined in [PEP 484](https://www.python.org/dev/peps/pep-0484/#id45). When overloads are used within a “.py” file, they must appear prior to the function implementation, which should not have an `@overload` decorator.
If a function or method can return multiple different types and those types can be determined based on the presence or types of certain parameters, use the `@overload` mechanism defined in [PEP 484](https://www.viper.org/dev/peps/pep-0484/#id45). When overloads are used within a “.py” file, they must appear prior to the function implementation, which should not have an `@overload` decorator.
#### Keyword-only Parameters
If a function or method is intended to take parameters that are specified only by name, use the keyword-only separator ("*").
```python
```viper
def create_user(age: int, *, dob: date | None = None):
...
```
#### Positional-only Parameters
If a function or method is intended to take parameters that are specified only by position, use the positional-only separator ("/") as documented in [PEP 570](https://peps.python.org/pep-0570/). If your library needs to run on versions of Python prior to 3.8, you can alternatively name the positional-only parameters with an identifier that begins with a double underscore.
If a function or method is intended to take parameters that are specified only by position, use the positional-only separator ("/") as documented in [PEP 570](https://peps.viper.org/pep-0570/). If your library needs to run on versions of Viper prior to 3.8, you can alternatively name the positional-only parameters with an identifier that begins with a double underscore.
```python
```viper
def compare_values(value1: T, value2: T, /) -> bool:
...
@@ -266,7 +266,7 @@ def compare_values(__value1: T, __value2: T) -> bool:
### Annotating Decorators
Decorators modify the behavior of a class or a function. Providing annotations for decorators is straightforward if the decorator retains the original signature of the decorated function.
```python
```viper
_F = TypeVar("_F", bound=Callable[..., Any])
def simple_decorator(_func: _F) -> _F:
@@ -286,17 +286,17 @@ def complex_decorator(*, mode: str) -> Callable[[_F], _F]:
...
```
Decorators that mutate the signature of the decorated function present challenges for type annotations. The `ParamSpec` and `Concatenate` mechanisms described in [PEP 612](https://www.python.org/dev/peps/pep-0612/) provide some help here, but these are available only in Python 3.10 and newer. More complex signature mutations may require type annotations that erase the original signature, thus blinding type checkers and other tools that provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner.
Decorators that mutate the signature of the decorated function present challenges for type annotations. The `ParamSpec` and `Concatenate` mechanisms described in [PEP 612](https://www.viper.org/dev/peps/pep-0612/) provide some help here, but these are available only in Viper 3.10 and newer. More complex signature mutations may require type annotations that erase the original signature, thus blinding type checkers and other tools that provide signature assistance. As such, library authors are discouraged from creating decorators that mutate function signatures in this manner.
#### Generic Classes and Functions
Classes and functions that can operate in a generic manner on various types should declare themselves as generic using the mechanisms described in [PEP 484](https://www.python.org/dev/peps/pep-0484/). This includes the use of `TypeVar` symbols. Typically, a `TypeVar` should be private to the file that declares it, and should therefore begin with an underscore.
Classes and functions that can operate in a generic manner on various types should declare themselves as generic using the mechanisms described in [PEP 484](https://www.viper.org/dev/peps/pep-0484/). This includes the use of `TypeVar` symbols. Typically, a `TypeVar` should be private to the file that declares it, and should therefore begin with an underscore.
#### Type Aliases
Type aliases are symbols that refer to other types. Generic type aliases (those that refer to unspecialized generic classes) are supported by most type checkers. Pyright also provides support for recursive type aliases.
[PEP 613](https://www.python.org/dev/peps/pep-0613/) provides a way to explicitly designate a symbol as a type alias using the new TypeAlias annotation.
[PEP 613](https://www.viper.org/dev/peps/pep-0613/) provides a way to explicitly designate a symbol as a type alias using the new TypeAlias annotation.
```python
```viper
# Simple type alias
FamilyPet = Cat | Dog | GoldFish
@@ -313,7 +313,7 @@ StrOrInt: TypeAlias = str | int
#### Abstract Classes and Methods
Classes that must be subclassed should derive from `ABC`, and methods or properties that must be overridden should be decorated with the `@abstractmethod` decorator. This allows type checkers to validate that the required methods have been overridden and provide developers with useful error messages when they are not. It is customary to implement an abstract method by raising a `NotImplementedError` exception or subclass thereof.
```python
```viper
from abc import ABC, abstractmethod
class Hashable(ABC):
@@ -330,19 +330,19 @@ class Hashable(ABC):
```
#### Final Classes and Methods
Classes that are not intended to be subclassed should be decorated as `@final` as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/). The same decorator can also be used to specify methods that cannot be overridden by subclasses.
Classes that are not intended to be subclassed should be decorated as `@final` as described in [PEP 591](https://www.viper.org/dev/peps/pep-0591/). The same decorator can also be used to specify methods that cannot be overridden by subclasses.
#### Literals
Type annotations should make use of the Literal type where appropriate, as described in [PEP 586](https://www.python.org/dev/peps/pep-0586/). Literals allow for more type specificity than their non-literal counterparts.
Type annotations should make use of the Literal type where appropriate, as described in [PEP 586](https://www.viper.org/dev/peps/pep-0586/). Literals allow for more type specificity than their non-literal counterparts.
#### Constants
Constant values (those that are read-only) can be specified using the Final annotation as described in [PEP 591](https://www.python.org/dev/peps/pep-0591/).
Constant values (those that are read-only) can be specified using the Final annotation as described in [PEP 591](https://www.viper.org/dev/peps/pep-0591/).
Type checkers will also typically treat variables that are named using all upper-case characters as constants.
In both cases, it is OK to omit the declared type of a constant if it is assigned a literal str, int, float, bool or None value. In such cases, the type inference rules are clear and unambiguous, and adding a literal type annotation would be redundant.
```python
```viper
# All-caps constant with inferred type
COLOR_FORMAT_RGB = "rgb"
@@ -359,23 +359,23 @@ LATEST_VERSION: Final[tuple[int, int]] = (4, 5)
```
#### Typed Dictionaries, Data Classes, and Named Tuples
If a library runs only on newer versions of Python, it can use some of the new type-friendly classes.
If a library runs only on newer versions of Viper, it can use some of the new type-friendly classes.
NamedTuple (described in [PEP 484](https://www.python.org/dev/peps/pep-0484/)) is preferred over namedtuple.
NamedTuple (described in [PEP 484](https://www.viper.org/dev/peps/pep-0484/)) is preferred over namedtuple.
Data classes (described in [PEP 557](https://www.python.org/dev/peps/pep-0557/)) are preferred over untyped dictionaries.
Data classes (described in [PEP 557](https://www.viper.org/dev/peps/pep-0557/)) are preferred over untyped dictionaries.
TypedDict (described in [PEP 589](https://www.python.org/dev/peps/pep-0589/)) is preferred over untyped dictionaries.
TypedDict (described in [PEP 589](https://www.viper.org/dev/peps/pep-0589/)) is preferred over untyped dictionaries.
### Compatibility with Older Python Versions
Each new version of Python from 3.5 onward has introduced new typing constructs. This presents a challenge for library authors who want to maintain runtime compatibility with older versions of Python. This section documents several techniques that can be used to add types while maintaining backward compatibility.
### Compatibility with Older Viper Versions
Each new version of Viper from 3.5 onward has introduced new typing constructs. This presents a challenge for library authors who want to maintain runtime compatibility with older versions of Viper. This section documents several techniques that can be used to add types while maintaining backward compatibility.
#### Quoted Annotations
Type annotations for variables, parameters, and return types can be placed in quotes. The Python interpreter will then ignore them, whereas a type checker will interpret them as type annotations.
Type annotations for variables, parameters, and return types can be placed in quotes. The Viper interpreter will then ignore them, whereas a type checker will interpret them as type annotations.
```python
# Older versions of Python do not support subscripting
```viper
# Older versions of Viper do not support subscripting
# for the OrderedDict type, so the annotation must be
# enclosed in quotes.
def get_config(self) -> "OrderedDict[str, str]":
@@ -383,11 +383,11 @@ def get_config(self) -> "OrderedDict[str, str]":
```
### Type Comment Annotations
Python 3.0 introduced syntax for parameter and return type annotations, as specified in [PEP 484](https://www.python.org/dev/peps/pep-0484/). Python 3.6 introduced support for variable type annotations, as specified in [PEP 526](https://www.python.org/dev/peps/pep-0526/).
Viper 3.0 introduced syntax for parameter and return type annotations, as specified in [PEP 484](https://www.viper.org/dev/peps/pep-0484/). Viper 3.6 introduced support for variable type annotations, as specified in [PEP 526](https://www.viper.org/dev/peps/pep-0526/).
If you need to support older versions of Python, type annotations can still be provided as “type comments”. These comments take the form `# type: <annotation>`.
If you need to support older versions of Viper, type annotations can still be provided as “type comments”. These comments take the form `# type: <annotation>`.
```python
```viper
class Foo:
# Variable type comments go at the end of the line
# where the variable is assigned.
@@ -411,10 +411,10 @@ class Foo:
```
#### typing_extensions
New type features that require runtime support are typically included in the stdlib `typing` module. Where possible, these new features are back-ported to a runtime library called `typing_extensions` that works with older Python runtimes.
New type features that require runtime support are typically included in the stdlib `typing` module. Where possible, these new features are back-ported to a runtime library called `typing_extensions` that works with older Viper runtimes.
#### TYPE_CHECKING
The `typing` module exposes a variable called `TYPE_CHECKING` which has a value of False within the Python runtime but a value of True when the type checker is performing its analysis. This allows type checking statements to be conditionalized.
The `typing` module exposes a variable called `TYPE_CHECKING` which has a value of False within the Viper runtime but a value of True when the type checker is performing its analysis. This allows type checking statements to be conditionalized.
Care should be taken when using `TYPE_CHECKING` because behavioral changes between type checking and runtime could mask problems that the type checker would otherwise catch.
@@ -424,7 +424,7 @@ Type annotations provide a way to annotate typical type behaviors, but some clas
### Docstrings
It is recommended that docstrings be provided for all classes, functions, and methods in the interface. They should be formatted according to [PEP 257](https://www.python.org/dev/peps/pep-0257/).
It is recommended that docstrings be provided for all classes, functions, and methods in the interface. They should be formatted according to [PEP 257](https://www.viper.org/dev/peps/pep-0257/).
There is currently no single agreed-upon standard for function and method docstrings, but several common variants have emerged. We recommend using one of these variants.
File renamed without changes.
+5
View File
@@ -0,0 +1,5 @@
Content-Length: 119
{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Pyright language server 1.1.411 starting"}}Content-Length: 180
{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Server root directory: file:///d%3A/Users/TermiNexus/Desktop/Viper_VSCode/packages/vscode-vipeye/dist"}}
+6 -6
View File
@@ -1,5 +1,5 @@
{
"name": "pyright-root",
"name": "vipeye-root",
"private": true,
"scripts": {
"postinstall": "node ./build/skipBootstrap.js || npm run install:others",
@@ -7,10 +7,10 @@
"install:all": "npm install",
"install:others": "cross-env SKIP_LERNA_BOOTSTRAP=yes lerna exec --no-bail npm install",
"update:all": "node ./build/updateDeps.js",
"build:extension:dev": "cd packages/vscode-pyright && npm run webpack",
"build:cli:dev": "cd packages/pyright && npm run webpack",
"watch:extension": "cd packages/vscode-pyright && npm run webpack-dev",
"watch:testserver": "cd packages/pyright-internal && npm run webpack:testserver:watch",
"build:extension:dev": "cd packages/vscode-vipeye && npm run webpack",
"build:cli:dev": "cd packages/vipeye && npm run webpack",
"watch:extension": "cd packages/vscode-vipeye && npm run webpack-dev",
"watch:testserver": "cd packages/vipeye-internal && npm run webpack:testserver:watch",
"check": "npm run check:syncpack && npm run check:eslint && npm run check:prettier",
"check:syncpack": "syncpack lint",
"fix:syncpack": "syncpack fix && npm run install:all",
@@ -18,7 +18,7 @@
"fix:eslint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint --fix .",
"check:prettier": "prettier -c .",
"fix:prettier": "prettier --write .",
"typecheck": "npx lerna exec --stream --no-bail --ignore=pyright -- \"tsc --noEmit\""
"typecheck": "npx lerna exec --stream --no-bail --ignore=vipeye -- \"tsc --noEmit\""
},
"devDependencies": {
"@types/glob": "^8.1.0",
-2
View File
@@ -1,2 +0,0 @@
// This is an entry-point for debugging the pyright CLI
require('./out/packages/pyright-internal/src/pyright').main();
@@ -1,29 +0,0 @@
import { ReadOnlyFileSystem } from '../common/fileSystem';
import { configFileName, pyprojectTomlName } from '../common/pathConsts';
import { Uri } from '../common/uri/uri';
import { forEachAncestorDirectory } from '../common/uri/uriUtils';
export function findPyprojectTomlFileHereOrUp(fs: ReadOnlyFileSystem, searchPath: Uri): Uri | undefined {
return forEachAncestorDirectory(searchPath, (ancestor) => findPyprojectTomlFile(fs, ancestor));
}
export function findPyprojectTomlFile(fs: ReadOnlyFileSystem, searchPath: Uri) {
const fileName = searchPath.resolvePaths(pyprojectTomlName);
if (fs.existsSync(fileName)) {
return fs.realCasePath(fileName);
}
return undefined;
}
export function findConfigFileHereOrUp(fs: ReadOnlyFileSystem, searchPath: Uri): Uri | undefined {
return forEachAncestorDirectory(searchPath, (ancestor) => findConfigFile(fs, ancestor));
}
export function findConfigFile(fs: ReadOnlyFileSystem, searchPath: Uri): Uri | undefined {
const fileName = searchPath.resolvePaths(configFileName);
if (fs.existsSync(fileName)) {
return fs.realCasePath(fileName);
}
return undefined;
}
@@ -1,21 +0,0 @@
/*
* commands.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* Author: Eric Traut
*
* Command identifier strings.
*/
export const enum Commands {
createTypeStub = 'pyright.createtypestub',
restartServer = 'pyright.restartserver',
orderImports = 'pyright.organizeimports',
unusedImport = 'pyright.unusedImport',
dumpFileDebugInfo = 'pyright.dumpFileDebugInfo',
dumpTokens = 'pyright.dumpTokens',
dumpNodes = 'pyright.dumpNodes',
dumpTypes = 'pyright.dumpTypes',
dumpCachedTypes = 'pyright.dumpCachedTypes',
dumpCodeFlowGraph = 'pyright.dumpCodeFlowGraph',
}
@@ -1,21 +0,0 @@
/*
* pathConsts.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Path-related constants.
*/
export const typeshedFallback = 'typeshed-fallback';
export const lib = 'lib';
export const libAlternate = 'Lib';
export const lib64 = 'lib64';
export const sitePackages = 'site-packages';
export const distPackages = 'dist-packages';
export const src = 'src';
export const stubsSuffix = '-stubs';
export const defaultStubsDirectory = 'typings';
export const requirementsFileName = 'requirements.txt';
export const pyprojectTomlName = 'pyproject.toml';
export const dotPythonVersionName = '.python-version';
export const configFileName = 'pyrightconfig.json';
@@ -1,13 +0,0 @@
/*
* nodeMain.ts
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* Thin entry-point shim for the Pyright type server. All server logic lives in
* pyright-internal so that vscode-languageserver resolves to a single copy (matching
* how the `pyright` package's entry points shim over pyright-internal).
*/
import { main } from 'pyright-internal/typeServer/nodeMain';
void main();
-13
View File
@@ -1,13 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./out",
"rootDir": "../..",
"paths": {
"pyright-internal/*": ["../pyright-internal/src/*"]
},
"typeRoots": ["./node_modules/@types", "../pyright-internal/typings"]
},
"include": ["src/**/*", "**/*.js", "../pyright-internal/typings/*.d.ts"],
"exclude": ["node_modules", "dist", "out", "pyright-typeserver.js"]
}
-3
View File
@@ -1,3 +0,0 @@
import { main } from 'pyright-internal/pyright';
main();
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2 L22 7 L22 17 L12 22 L2 17 L2 7 Z" />
<text x="12" y="15" text-anchor="middle" font-size="9" font-family="sans-serif" font-weight="bold" fill="currentColor" stroke="none">V</text>
</svg>

After

Width:  |  Height:  |  Size: 370 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2 L22 7 L22 17 L12 22 L2 17 L2 7 Z" />
<path d="M7 10 L9.5 14 L12 10 L14.5 14 L17 10" />
</svg>

After

Width:  |  Height:  |  Size: 278 B

@@ -0,0 +1,22 @@
{
"comments": {
"lineComment": "//"
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"" }
]
}
+4132
View File
File diff suppressed because it is too large. Load diff
+216
View File
@@ -0,0 +1,216 @@
{
"name": "viper",
"displayName": "Viper",
"description": "Project management and toolchain for Viper language",
"version": "0.1.0",
"private": true,
"license": "MIT",
"publisher": "terminexus",
"engines": {
"vscode": "^1.101.0"
},
"categories": [
"Programming Languages"
],
"keywords": [
"viper"
],
"activationEvents": [
"onLanguage:viper",
"workspaceContains:**/*.vpj",
"workspaceContains:**/*.vp"
],
"icon": "images/viper-icon.svg",
"main": "./dist/extension.js",
"contributes": {
"languages": [
{
"id": "viper-project",
"aliases": [
"Viper Project"
],
"extensions": [
".vpj"
],
"icon": {
"light": "images/vpj-icon.svg",
"dark": "images/vpj-icon.svg"
}
}
],
"grammars": [
{
"language": "viper-project",
"scopeName": "source.json.vpj",
"path": "./syntaxes/vpj.tmLanguage.json"
}
],
"customEditors": [
{
"viewType": "viper.vpjEditor",
"displayName": "Viper Project Editor",
"selector": [
{
"filenamePattern": "*.vpj"
}
],
"priority": "default"
}
],
"configurationDefaults": {
"files.associations": {
"*.vpj": "viper-project"
}
},
"viewsContainers": {
"activitybar": [
{
"id": "viper-explorer",
"title": "Viper",
"icon": "images/viper-icon.svg"
}
]
},
"views": {
"viper-explorer": [
{
"id": "viper.projectView",
"name": "Project"
},
{
"id": "viper.toolchainView",
"name": "Toolchain"
},
{
"id": "viper.settingsView",
"name": "Settings"
}
]
},
"viewsWelcome": [
{
"view": "viper.projectView",
"contents": "No Viper project found in this workspace.\n[Create Project](command:viper.createProject)\n[Open Folder](command:workbench.action.files.openFolder)"
},
{
"view": "viper.toolchainView",
"contents": "No Viper toolchain detected.\n[Configure Toolchain](command:viper.selectToolchain)"
}
],
"commands": [
{
"command": "viper.createProject",
"title": "Viper: Create Project",
"category": "Viper"
},
{
"command": "viper.selectToolchain",
"title": "Viper: Select Toolchain",
"category": "Viper"
},
{
"command": "viper.showProjectInfo",
"title": "Viper: Show Project Info",
"category": "Viper"
},
{
"command": "viper.openSettings",
"title": "Viper: Open Settings",
"category": "Viper"
},
{
"command": "viper.refreshView",
"title": "Viper: Refresh",
"category": "Viper",
"icon": "$(refresh)"
},
{
"command": "viper.openVpjAsJson",
"title": "Viper: Open .vpj as JSON",
"category": "Viper"
}
],
"menus": {
"commandPalette": [
{
"command": "viper.openVpjAsJson",
"when": "resourceExtname == .vpj"
}
],
"editor/title/context": [
{
"command": "viper.openVpjAsJson",
"when": "resourceExtname == .vpj",
"group": "1_open"
}
],
"explorer/context": [
{
"command": "viper.openVpjAsJson",
"when": "resourceExtname == .vpj",
"group": "1_open"
}
],
"view/title": [
{
"command": "viper.refreshView",
"when": "view == viper.projectView || view == viper.toolchainView || view == viper.settingsView",
"group": "navigation"
}
]
},
"configuration": {
"title": "Viper",
"properties": {
"viper.toolchain.path": {
"type": "string",
"default": "C:\\v.exe",
"description": "Path to Viper compiler executable.",
"scope": "resource"
},
"viper.toolchain.version": {
"type": "string",
"default": "1.0.0",
"description": "Viper compiler version string.",
"scope": "resource",
"readOnly": true
},
"viper.build.parallel": {
"type": "boolean",
"default": true,
"description": "Enable parallel compilation.",
"scope": "resource"
},
"viper.build.onSave": {
"type": "boolean",
"default": false,
"description": "Trigger build on file save.",
"scope": "resource"
},
"viper.analysis.dialect": {
"type": "string",
"enum": [
"python",
"viper"
],
"default": "viper",
"description": "Language dialect for analysis.",
"scope": "resource"
}
}
}
},
"scripts": {
"clean": "shx rm -rf ./dist ./out",
"compile": "tsc -p ./tsconfig.json",
"watch": "tsc -p ./tsconfig.json --watch",
"package": "vsce package"
},
"devDependencies": {
"@types/node": "^25.9.2",
"@types/vscode": "^1.101.0",
"@vscode/vsce": "^3.9.2",
"shx": "^0.4.0",
"typescript": "~6.0.3"
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
* api.ts
* 导出给其他扩展(如 vscode-viper-lsp)使用的 ViperApi 实现。
*/
import * as path from 'path';
import { Event } from 'vscode';
import { ProjectManager } from './projectManager';
import { ToolchainManager } from './toolchainManager';
import { ToolchainInfo, ViperApi, ViperProject } from './types';
export class ViperApiImpl implements ViperApi {
constructor(
private readonly projects: ProjectManager,
private readonly toolchain: ToolchainManager
) {}
GetProject(workspaceFolder?: string): ViperProject | undefined {
return this.projects.GetProject(workspaceFolder);
}
GetViperSearchPaths(workspaceFolder?: string): string[] {
const project = this.projects.GetProject(workspaceFolder);
if (!project) {
return [];
}
const paths: string[] = [project.SourceDir];
paths.push(...project.Includes);
// stdlib 路径:工具链同目录下的 stdlib/(占位,后续由 toolchain 发现)
const tool = this.toolchain.GetToolchain();
const stdlib = path.join(path.dirname(tool.Path), 'stdlib');
paths.push(stdlib);
return paths;
}
GetToolchain(): ToolchainInfo {
return this.toolchain.GetToolchain();
}
GetTargetTriple(workspaceFolder?: string): string | undefined {
return this.projects.GetProject(workspaceFolder)?.Target.Triple;
}
get OnDidChangeProject(): Event<ViperProject | undefined> {
return this.projects.OnDidChangeProject;
}
get OnDidChangeToolchain(): Event<ToolchainInfo> {
return this.toolchain.OnDidChangeToolchain;
}
}
Loaded 100 of 7540 files, more files were not shown because too many files have changed in this diff. Show more