Files
TermiNexus 0f235d0d0b fix(keyboard): 移除 devfs 注册时的重复 keyboard.init()
键盘只收到一个残留字节(ch=50)后就没有数据,符合'数据上报被关掉'。keyboard.init() 被调了两次:devfs_register_keyboard() 与 services/keyboard.vp 的 start()。init() 会读-改-写 8042 CCB 并安装 IRQ1,二次读改写可能把键盘时钟/数据上报关掉。改为只在 service 层初始化一次。
2026-10-17 13:54:52 +08:00

613 lines
23 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import drivers.serial.uart.serial as serial
import string
import asm
define MAX_FILE_DESCRIPTORS = 64
define MAX_DEVICE_FILES = 32
define MAX_DIRECTORIES = 16
define PATH_BUFFER_SIZE = 256
directories: [MAX_DIRECTORIES]devfs_directory = [0]
directory_count: static int = 0
# 存**指针**而不是基类值:按值数组会把设备对象**切片**成 devfs_file(只拷 88 字节基类部分),
# 派生字段(framebuffer_device.fb_w/fb_h/fb_phys…、shm_device.shm_phys…)全部丢失 ——
# 现场表现为 [main] fb0 reg w=1360(读的是模块级对象)而 [fb0] ioctl w=0(读的是数组里的切片)。
# IR 证据:create_file 里生成的是 llvm.load/llvm.store 整个 devfs_file 结构体,不含派生字段。
files: [MAX_DEVICE_FILES]devfs_file* = [0]
file_count: static int = 0
class _fd_table:
file: devfs_file*
flags: int
fd_table: [MAX_FILE_DESCRIPTORS]_fd_table = [0]
class devfs_directory:
name: [64]char
parent: devfs_directory*
@staticmethod
def find(path: const str, root_directory: devfs_directory*) -> devfs_directory*:
if not path or string.strchr(path, '/') != path: return None
if not root_directory: return None
current_dir: devfs_directory* = root_directory
result: [PATH_BUFFER_SIZE]str
token = string.split(path, "/", result, PATH_BUFFER_SIZE)
# `for i in result` 按 NULL 终止语义遍历(i8* 元素,split 尾哨兵 NULL 停):
# 编译器 _handle_array_iter 已实现"长度上限 + 解引用为空提前终止"。
for i in result:
found: bool = False
for d in range(directory_count):
dirp: devfs_directory* = &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: const str, parent: devfs_directory*) -> devfs_directory*:
for d in range(directory_count):
dirp: devfs_directory* = &directories[d]
if dirp.parent == parent and string.samestr(dirp.name, name):
return dirp
return None
@staticmethod
def list_subdirs(dir_ptr: devfs_directory*, buffer: str, size: uint32, pos: int) -> int:
for d in range(directory_count):
dirp: devfs_directory* = &directories[d]
if dirp.parent == dir_ptr:
name_len: int = int(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
virtual class devfs_file:
name: [64]char
private_data: rawptr
parent_dir: devfs_directory*
@staticmethod
def find_in_dir(file_name: const str, dir_ptr: devfs_directory*) -> devfs_file*:
for f in range(file_count):
fp: devfs_file* = 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*, buffer: str, size: uint32, pos: int) -> int:
for f in range(file_count):
filep: devfs_file* = files[f]
if filep.parent_dir == dir_ptr:
name_len: int = int(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: const str, flags: int) -> int: return 0
def close(self, fd: int) -> int: return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int: return 0
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int: return 0
def ioctl(self, fd: int, request: int, arg: rawptr) -> int: return 0
# mmap 支持:默认不可映射(返回 -1)。可映射设备(/dev/fb0)覆写它,
# 把自己的物理区间交给内核 mmap 去映射进用户地址空间。
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int: return -1
class _DevFSObject:
root_directory: devfs_directory*
def __init__(self):
global directory_count, file_count
self.root_directory = None
directory_count = 0
file_count = 0
string.memset(&directories, 0, devfs_directory.__sizeof__() * MAX_DIRECTORIES)
# files 是指针数组(8 字节/项),不能再用 devfs_file.__sizeof__() —— 那会越界清掉后面的全局量。
string.memset(&files, 0, 8 * MAX_DEVICE_FILES)
string.memset(&fd_table, 0, _fd_table.__sizeof__() * MAX_FILE_DESCRIPTORS)
def create_single_dir(self, parent: devfs_directory*, dir_name: const str) -> devfs_directory*:
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* = &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: const str) -> devfs_directory*:
global directory_count
if not path or string.samestr(path, "/"): return None
if not self.root_directory:
self.root_directory = &directories[directory_count]
directory_count += 1
string.strcpy(self.root_directory.name, "")
self.root_directory.parent = None
current: devfs_directory* = self.root_directory
buf: [PATH_BUFFER_SIZE]char
string.memset(&buf, 0, PATH_BUFFER_SIZE)
buf_idx: int = 0
p: int = 0
if path[p] == '/': p += 1
while path[p] != '\0':
if path[p] == '/':
if buf_idx > 0:
found_child: devfs_directory* = 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(&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* = 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) -> int:
self.create_directory("/dev")
self.create_directory("/dev/input")
return 0
def create_directory(self, path: const str) -> int:
dir_ptr: devfs_directory* = self.find_or_create_directory(path)
return 0 if dir_ptr else -1
def create_file(self, path: const str, file_ptr: devfs_file*, private_data: rawptr) -> int:
global file_count
path_copy: [PATH_BUFFER_SIZE]char
string.memset(&path_copy, 0, PATH_BUFFER_SIZE)
string.strcpy(&path_copy, path)
last_slash: str = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str = last_slash + 1
dir_ptr: devfs_directory* = 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
# 只登记指针。设备对象由调用方(devfs_register_*)以模块级实例持有,
# 生命周期贯穿内核运行期,因此指针引用安全且能看见派生字段。
files[file_count] = file_ptr
else:
# 不再自建节点:`devfs_file()` 在函数内构造的是**栈实例**,登记进全局 files[] 会悬垂,
# 编译器 LifetimeError 已按"栈地址逃逸"拦截。设备节点只能由调用方以长寿命实例
# (模块级 / 堆)提供;缺对象一律失败,不静默造一个假的占位对象。
return -1
file_count += 1
return 0
def open(self, path: const str, flags: int) -> int:
path_copy: [PATH_BUFFER_SIZE]char
string.memset(&path_copy, 0, PATH_BUFFER_SIZE)
string.strcpy(&path_copy, path)
last_slash: str = string.strrchr(path_copy, '/')
if not last_slash: return -1
last_slash[0] = '\0'
dir_path = path_copy
file_name: str = last_slash + 1
dir_ptr: devfs_directory* = devfs_directory.find(dir_path, self.root_directory)
if not dir_ptr: return -1
file_ptr: devfs_file* = devfs_file.find_in_dir(file_name, dir_ptr)
if not file_ptr: return -1
fd: int = -1
for i in range(MAX_FILE_DESCRIPTORS):
if not fd_table[i].file:
fd = i
break
if fd == -1: return -1
ret: int = 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: int) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file* = fd_table[fd].file
file_ptr.close(fd)
fd_table[fd].file = None
fd_table[fd].flags = 0
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.read(fd, buffer, size)
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.write(fd, buffer, size)
def ioctl(self, fd: int, cmd: uint32, arg: rawptr) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.ioctl(fd, cmd, arg)
def get_mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int:
if fd < 0 or fd >= MAX_FILE_DESCRIPTORS or not fd_table[fd].file:
return -1
file_ptr: devfs_file* = fd_table[fd].file
return file_ptr.mmap_info(fd, out_phys, out_size)
def list_directory(self, path: const str,
buffer: str,
size: uint32) -> int:
dir_ptr: devfs_directory* = devfs_directory.find(path, self.root_directory)
if not dir_ptr or not buffer or size == 0: return -1
pos: int = devfs_directory.list_subdirs(dir_ptr, buffer, size, 0)
pos = devfs_file.list_in_dir(dir_ptr, buffer, size, pos)
return pos
DevFSObject: _DevFSObject* = _DevFSObject()
def devfs_init() -> int:
global DevFSObject
DevFSObject.__init__()
DevFSObject.init()
return 0
def devfs_create_directory(path: const str) -> int:
return DevFSObject.create_directory(path)
def devfs_create_file(path: const str, file_ptr: devfs_file*, private_data: rawptr) -> int:
return DevFSObject.create_file(path, file_ptr, private_data)
def devfs_open(path: const str, flags: int) -> int:
return DevFSObject.open(path, flags)
def devfs_close(fd: int) -> int:
return DevFSObject.close(fd)
def devfs_read(fd: int, buffer: rawptr, size: uint32) -> int:
return DevFSObject.read(fd, buffer, size)
def devfs_write(fd: int, buffer: const rawptr, size: uint32) -> int:
return DevFSObject.write(fd, buffer, size)
def devfs_ioctl(fd: int, cmd: uint32, arg: rawptr) -> int:
return DevFSObject.ioctl(fd, cmd, arg)
def devfs_get_mmap_info(fd: int, out_phys: uint64*, out_size: uint64*) -> int:
return DevFSObject.get_mmap_info(fd, out_phys, out_size)
def devfs_list_directory(path: const str, buffer: str, size: uint32) -> int:
return DevFSObject.list_directory(path, buffer, size)
serial0_file: devfs_file* = devfs_file()
def serial0_open(self: rawptr, path: const str, flags: int) -> int:
serial.puts("[serial0] open: ")
serial.puts(path)
serial.puts("\n")
return 0
def serial0_close(self: rawptr, fd: int) -> int:
serial.puts("[serial0] close\n")
return 0
def serial0_read(self: rawptr, fd: int, buffer: rawptr, size: uint32) -> int:
serial.puts("[serial0] read\n")
return 0
def serial0_write(self: rawptr, fd: int, buffer: const rawptr, size: uint32) -> int:
serial.puts("[serial0] write\n")
serial.puts(buffer)
serial.puts("\n")
return size
def devfs_register_serial0() -> int:
global serial0_file
serial0_file.__init__()
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)
virtual class serial1_device(devfs_file):
def open(self, path: const str, flags: int) -> int:
serial.puts("[serial1] open: ")
serial.puts(path)
serial.puts("\n")
return 0
def close(self, fd: int) -> int:
serial.puts("[serial1] close\n")
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
serial.puts("[serial1] read\n")
return 0
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
serial.puts("[serial1] write\n")
serial.puts(str(buffer))
serial.puts("\n")
return size
serial1_file: serial1_device* = serial1_device()
def devfs_register_serial1() -> int:
global serial1_file
serial1_file.__init__()
return devfs_create_file("/dev/serial1", serial1_file, None)
import drivers.input.keyboard.keyboard as keyboard
import drivers.input.mouse.mouse as mouse
virtual class keyboard_device(devfs_file):
def open(self, path: const str, flags: int) -> int:
return 0
def close(self, fd: int) -> int:
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
count: uint32 = 0
# 以 read_scancode() 的返回值为准(0 = 队列空):每轮要么取到一个字节并前进,
# 要么立即退出,不再依赖第二个计数量做循环条件(那次混合计数会自旋)。
while count < size:
sc: uint8 = keyboard.read_scancode()
if sc == uint8(0): break
ch: char = keyboard.scancode_to_ascii(sc)
char*(buffer)[count] = ch
count += 1
return count
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
return -1
def ioctl(self, fd: int, request: int, arg: rawptr) -> int:
return 0
kbd_file: keyboard_device* = keyboard_device()
def devfs_register_keyboard() -> int:
global kbd_file
kbd_file.__init__()
# 不在这里 keyboard.init():初始化(含 8042 CCB 读改写与 IRQ1 安装)由
# services/keyboard.vp 的 start() 统一负责。两处都调会二次读改写 CCB,
# 第二次可能把键盘时钟/数据上报关掉 —— 表现为"设备能开、只收到一个残留字节"。
return devfs_create_file("/dev/input/keyboard", kbd_file, None)
virtual class mouse_device(devfs_file):
def open(self, path: const str, flags: int) -> int:
return 0
def close(self, fd: int) -> int:
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
if not mouse.has_data(): return 0
pkt: mouse.mouse_packet
mouse.read_packet(&pkt)
if size >= 1:
uint8*(buffer)[0] = pkt.buttons
if size >= 9:
# 布局:[0] = 按键,[1..4] = dx(int32),[5..8] = dy(int32)。
# 【重要】dx/dy 是 PS/2 包里的**相对位移**,不是绝对坐标 —— 光标位置由 explorer
# 持有并做边界钳制,内核只负责设备语义。旧写法填的是 mouse.get_x()/get_y(),
# 而那两个静态量从未被更新(irq_handler 只把包入队),恒为 0 ⇒ 用户一动鼠标,
# explorer 就把光标设到 (0,0),现场表现为"立马跑到左上角"。
buf: uint32* = uint32*(uint64(buffer) + 1)
dx: int = int(pkt.dx)
dy: int = int(pkt.dy)
# uint8 原始字节 → 有符号增量(-128..127);不依赖 int8 转换的语义,直接判借位。
if dx >= 128: dx = dx - 256
if dy >= 128: dy = dy - 256
# PS/2 协议的 Y 正方向是**向上**,而屏幕坐标 Y 向下为正 —— 在这里统一取反成屏幕
# 方向,消费方直接累加即可(与历史 desktop.vp 里 pkt_dy 的用法一致)。
dy = 0 - dy
int32*(buf)[0] = int32(dx)
int32*(buf)[1] = int32(dy)
return 9
return 1
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
return -1
def ioctl(self, fd: int, request: int, arg: rawptr) -> int:
return 0
mse_file: mouse_device* = mouse_device()
def devfs_register_mouse() -> int:
global mse_file
mse_file.__init__()
mouse.init()
return devfs_create_file("/dev/input/mouse", mse_file, None)
# ============================================================================
# /dev/fb0:framebuffer 设备(供用户态图形壳 explorer 接管桌面绘制)。
# · read/write 不支持(-1):像素由用户态经 mmap 取到线性地址后直接写;
# · ioctl(FB_GET_INFO):把几何/格式/物理区间回填进用户缓冲(FbInfo);
# · mmap_info:把本设备的物理区间交给内核 mmap 映射进用户地址空间。
# 本节点只记录 BootInfo 里的物理地址与几何,不自己持有 framebuffer 映射。
# ============================================================================
define FB_GET_INFO = 0x4600
# 等一次垂直回扫:用户态(explorer)没有 IO 端口权限,不能自己 inb 0x3DA,由内核代做。
define FB_WAIT_VBLANK = 0x4601
@__attribute__(packed)
class FbInfo:
width: uint32
height: uint32
pitch: uint32
format: uint32
size: uint64
phys: uint64
virtual class framebuffer_device(devfs_file):
fb_phys: uint64
fb_size: uint64
fb_w: uint32
fb_h: uint32
fb_pitch: uint32
fb_format: uint32
def open(self, path: const str, flags: int) -> int:
return 0
def close(self, fd: int) -> int:
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
return -1
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
return -1
def ioctl(self, fd: int, request: int, arg: rawptr) -> int:
if request == FB_WAIT_VBLANK:
# 等进入垂直回扫期(0x3DA bit3)。返回等待消耗的迭代数:到上限即超时
#(当前没有 VGA 回扫信号),调用方可据此判断同步是否真的生效。
t: int = 100000
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1
t2: int = 100000
while (asm.inb(0x3DA) & 0x08) == 0 and t2 > 0: t2 -= 1
return 100000 - t2
if request != FB_GET_INFO or not arg:
return -1
info: FbInfo* = FbInfo*(uint64(arg))
info.width = self.fb_w
info.height = self.fb_h
info.pitch = self.fb_pitch
info.format = self.fb_format
info.size = self.fb_size
info.phys = self.fb_phys
return 0
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int:
if out_phys: *out_phys = self.fb_phys
if out_size: *out_size = self.fb_size
return 0
fb0_file: framebuffer_device* = framebuffer_device()
def devfs_register_fb0(fb: rawptr, width: int, height: int, pitch: int, format: int, size: uint64) -> int:
global fb0_file
fb0_file.__init__()
fb0_file.fb_phys = uint64(fb)
fb0_file.fb_size = size
fb0_file.fb_w = uint32(width)
fb0_file.fb_h = uint32(height)
fb0_file.fb_pitch = uint32(pitch)
fb0_file.fb_format = uint32(format)
return devfs_create_file("/dev/fb0", fb0_file, None)
# ============================================================================
# /dev/desk:图形壳 explorer 与各 app 之间的**共享内存通道**。
#
# 内核只做两件事:启动时分配一块物理内存、让每个 open 者 mmap 到**同一**物理区间。
# 区间内部的布局(窗口表 / 各窗口像素缓冲 / 请求与事件环形队列)完全由用户态协议
# 约定,见 vpsdk/desk.vp —— 内核不解析其内容,也不参与窗口语义。
#
# 为什么用 devfs 而不是新 syscall:syscall 表已对齐 Linux 且不留私有段;
# "共享一块内存给多方"正好能用 设备 + mmap 表达(Linux 用 shm/memfd + MAP_SHARED,
# 这里用 devfs 设备 + mmap 达到同样效果)。
# ============================================================================
# 共享区大小:vpsdk/desk.vp 里有一份同名同值的镜像(用户在 mmap 时必须先知道长度)。
define DESK_SHM_SIZE = 8388608
virtual class shm_device(devfs_file):
shm_phys: uint64
shm_size: uint64
def open(self, path: const str, flags: int) -> int:
return 0
def close(self, fd: int) -> int:
return 0
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
return -1
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
return -1
def mmap_info(self, fd: int, out_phys: uint64*, out_size: uint64*) -> int:
if out_phys: *out_phys = self.shm_phys
if out_size: *out_size = self.shm_size
return 0
desk_file: shm_device* = shm_device()
def devfs_register_desk(shm: rawptr, size: uint64) -> int:
"""登记 /dev/desk。shm 是内核启动时 mm.malloc 出来的共享区;所有 open 者映射同一区间。"""
global desk_file
if not shm or size == 0:
serial.puts("[devfs] desk shm register skipped (no memory)\n")
return -1
desk_file.__init__()
desk_file.shm_phys = uint64(shm)
desk_file.shm_size = size
return devfs_create_file("/dev/desk", desk_file, None)