fix(keyboard,explorer): 键盘解码单队列化消除读循环自旋;关窗清理悬垂状态

① 卡死/键盘无效:has_data() 把'原始待解码'与'已解码可读'混成一个计数,而 read_scancode() 只看已解码队列;一旦出现 has_data()>0 但本次取不到字节(断码/修饰键/E0 前缀),devfs 的读循环里 count 不前进而 has_data() 恒真 ⇒ asm.sti() 后无 yield 自旋,协作式调度下整机停死。改为解码下沉到 IRQ、只保留一个输出队列(has_data()==out_count,read_scancode() 返回 0 ⟺ 队列空),ANSI 序列整条原子入队;devfs 读循环以 read_scancode() 返回 0 为唯一终止条件。② 关窗:DESTROY 与关闭键路径清 drag_win,focus/_activate_from_taskbar 校验槽位存活;poll_requests 单轮最多处理一圈(head 已推进,剩余留到下轮,不丢消息也不自旋)。③ vpui:孤立 ESC 后紧跟的普通键不再被吞。
This commit is contained in:
TermiNexus committed 2026-10-17 13:20:22 +08:00
1 parent e839b37ee2
commit 850c4b8adc
4 files changed
+264 -263

No files matched your search

+13 -2
View File
@@ -786,7 +786,9 @@ class DeskServer:
# 点击图标:最小化先恢复,再走既有 focus(置顶并整体置脏)
def _activate_from_taskbar(self, win: int):
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
if s.minimized != 0:
s.minimized = 0
self.mark_win(s.uy, s.h)
@@ -1151,6 +1153,8 @@ class DeskServer:
self.mark(s.uy, s.h)
s.used = 0
if self.focused == win: self.focused = -1
# 拖拽中的窗口被销毁:必须清掉 drag_win,否则后续移动会继续操作这个死槽位。
if self.drag_win == win: self.drag_win = -1
self.mark_all_windows()
self.mark_titles()
elif ty == uint32(desk.DESK_REQ_MOVE):
@@ -1183,8 +1187,11 @@ class DeskServer:
self.mark(0, self.fb_h)
def poll_requests(self):
while desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0:
# 单轮最多处理一整圈:head 已前进,剩余请求留到下一轮,不会重复也不会自旋。
n: int = 0
while n < desk.DESK_RING_SLOTS and desk.ring_pop(self.shm + desk.desk_req_ring_off(), &self.msg) != 0:
self.handle_request(self.msg)
n += 1
# ---------------- 输入 ----------------
def hit_test(self, x: int, y: int) -> int:
@@ -1200,8 +1207,10 @@ class DeskServer:
return best
def focus(self, win: int):
if win < 0: return
# 只允许聚焦"活"的槽位:死槽位上的 z_order/focused 会把焦点指向已销毁窗口。
if win < 0 or win >= desk.DESK_MAX_WIN: return
s: desk.WinSlot* = self.slot(win)
if s.used == 0: return
self.next_z += 1
s.z_order = self.next_z
self.focused = win
@@ -1226,6 +1235,7 @@ class DeskServer:
kb: [16]char
kn: int = fs.read(self.kfd, &kb, uint32(16))
if kn > 0:
# 无目标窗口(target < 0)时本批字节丢弃:这里只读一次、不重试,不会空转。
target: int = self._key_target()
if target >= 0:
i: int = 0
@@ -1298,6 +1308,7 @@ class DeskServer:
self.mark_win(s.uy, s.h)
s.used = 0
if self.focused == hit: self.focused = -1
if self.drag_win == hit: self.drag_win = -1
self.mark_titles()
elif self.cy < s.uy + TITLE_H:
# 标题栏(非关闭按钮)⇒ 开始拖动
+6 -4
View File
@@ -408,12 +408,14 @@ virtual class keyboard_device(devfs_file):
def read(self, fd: int, buffer: rawptr, size: uint32) -> int:
count: uint32 = 0
while count < size and keyboard.has_data():
# 以 read_scancode() 的返回值为准(0 = 队列空):每轮要么取到一个字节并前进,
# 要么立即退出,不再依赖第二个计数量做循环条件(那次混合计数会自旋)。
while count < size:
sc: uint8 = keyboard.read_scancode()
if sc == uint8(0): break
ch: char = keyboard.scancode_to_ascii(sc)
if ch != '\0':
char*(buffer)[count] = ch
count += 1
char*(buffer)[count] = ch
count += 1
return count
def write(self, fd: int, buffer: const rawptr, size: uint32) -> int:
+244 -256
View File
@@ -1,65 +1,61 @@
import asm
import intr.idt as idt
import drivers.serial.uart.serial as serial
import platform.pch.pic as pic
import string
import t, c
define KBD_DATA = 0x60
define KBD_STATUS = 0x64
define KBD_COMMAND = 0x64
define KBD_BUF_SIZE = 1024
import asm
import intr.idt as idt
import drivers.serial.uart.serial as serial
import platform.pch.pic as pic
import string
import t, c
define KBD_DATA = 0x60
define KBD_STATUS = 0x64
define KBD_COMMAND = 0x64
define KBD_OUT_SIZE = 1024
define KEY_RELEASED = 0x80
buffer: [KBD_BUF_SIZE]uint8
buf_head: static uint32 = 0
buf_tail: static uint32 = 0
buf_count: static uint32 = 0
kbd_irq_cnt: static uint64 = 0
# 硬件初始化只做一次(init 有多个调用点,见 init() 内注释)
_kbd_inited: static int = 0
# 解码后的 ASCII / ANSI 转义字节队列(read_scancode 从原始缓冲解码后填入)
# 唯一的数据队列:IRQ 里把扫描码解码成 ASCII/ANSI 字节后直接入队。
# 读侧只做"取一个字节",不再在 read 里解码 —— 这样 has_data() 与 read_scancode()
# 看的是**同一个计数**,不可能出现"有数据但取不到"的自旋。
out_buf: [KBD_OUT_SIZE]uint8
out_head: static uint32 = 0
out_tail: static uint32 = 0
out_count: static uint32 = 0
kbd_irq_cnt: static uint64 = 0
# 硬件初始化只做一次(init 有多个调用点,见 init() 内注释)
_kbd_inited: static int = 0
shift_l: static uint8 = 0
shift_r: static uint8 = 0
ctrl_l: static uint8 = 0
alt_l: static uint8 = 0
caps_lock: static uint8 = 0
_e0_pending: static uint8 = 0
scancode_set1: [128]char = [
'\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: [128]char = [
'\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'
]
_e0_pending: static uint8 = 0
scancode_set1: [128]char = [
'\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: [128]char = [
'\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'
]
# 清空 8042 输出缓冲(上电自检字节 / 遗留 ACK / 读 CCB 时压入的值)
def _drain_outbuf():
drain: uint32 = 0
@@ -68,211 +64,203 @@ def _drain_outbuf():
drain += 1
if drain > 1000: break
def _wait_read():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x01) != 0: return
timeout -= 1
def _wait_write():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x02) == 0: return
timeout -= 1
def irq_handler() -> int:
global buf_head, buf_tail, buf_count, kbd_irq_cnt
kbd_irq_cnt += 1
# 0x60 是键盘/鼠标**共享**的输出缓冲:AUX 位(0x64 bit5)置位表示这是鼠标字节,
# 不属于键盘 —— 直接读走会让鼠标驱动永远拿不到(本 ISR 只收键盘字节)。
st: uint8 = asm.inb(KBD_STATUS)
if (st & 0x20) != 0:
return 0
scancode: uint8 = 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 _out_push(b: uint8):
global out_tail, out_count
if out_count >= KBD_OUT_SIZE: return
out_buf[out_tail] = b
out_tail = out_tail + 1
if out_tail >= KBD_OUT_SIZE:
out_tail = 0
out_count += 1
def _emit_csi(final: uint8):
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(final)
def _emit_csi_tilde(digit: uint8):
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(digit)
_out_push(uint8(0x7E))
# CapsLock 只翻转字母大小写(符号仍由 Shift 决定)
def _is_letter_code(code: uint8) -> bool:
if code >= uint8(0x10) and code <= uint8(0x19): return True
if code >= uint8(0x1E) and code <= uint8(0x26): return True
if code >= uint8(0x2C) and code <= uint8(0x32): return True
return False
# 扫描码 -> ASCII / ANSI 转义字节,追加到输出队列
def _decode_scancode(sc: uint8):
global shift_l, shift_r, ctrl_l, alt_l, caps_lock, _e0_pending
if sc == uint8(0xE0):
_e0_pending = 1
return
if sc == uint8(0xE1):
_e0_pending = 0
return
released: bool = (sc & uint8(KEY_RELEASED)) != 0
code: uint8 = uint8(sc & ~uint8(KEY_RELEASED))
if _e0_pending != 0:
_e0_pending = 0
# E0 1D / 38 = 右 Ctrl / 右 Alt
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if released: return
if code == uint8(0x48): _emit_csi(uint8(0x41)) # Up
elif code == uint8(0x50): _emit_csi(uint8(0x42)) # Down
elif code == uint8(0x4D): _emit_csi(uint8(0x43)) # Right
elif code == uint8(0x4B): _emit_csi(uint8(0x44)) # Left
elif code == uint8(0x47): _emit_csi(uint8(0x48)) # Home
elif code == uint8(0x4F): _emit_csi(uint8(0x46)) # End
elif code == uint8(0x53): _emit_csi_tilde(uint8(0x33)) # Delete
elif code == uint8(0x49): _emit_csi_tilde(uint8(0x35)) # PageUp
elif code == uint8(0x51): _emit_csi_tilde(uint8(0x36)) # PageDown
elif code == uint8(0x52): _emit_csi_tilde(uint8(0x32)) # Insert
return
if code == uint8(0x2A):
shift_l = 0
if not released: shift_l = 1
return
if code == uint8(0x36):
shift_r = 0
if not released: shift_r = 1
return
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if code == uint8(0x3A):
if not released: caps_lock = caps_lock ^ uint8(1)
return
if released: return
shifted: bool = (shift_l or shift_r) != 0
if caps_lock and _is_letter_code(code):
shifted = not shifted
ch: char = scancode_set1[code]
if shifted:
ch = scancode_set1_shift[code]
if ch != '\0':
_out_push(uint8(ch))
def read_scancode() -> uint8:
global buf_head, buf_count, out_head, out_count
asm.cli()
# 原始扫描码持续解码,直到产出至少一个可读字节
while out_count == 0 and buf_count > 0:
raw: uint8 = buffer[buf_head]
buf_head = buf_head + 1
if buf_head >= KBD_BUF_SIZE:
buf_head = 0
buf_count -= 1
_decode_scancode(raw)
if out_count == 0:
asm.sti()
return 0
code: uint8 = out_buf[out_head]
out_head = out_head + 1
if out_head >= KBD_OUT_SIZE:
out_head = 0
out_count -= 1
asm.sti()
return code
def scancode_to_ascii(scancode: uint8) -> char:
# read_scancode() 已解码为 ASCII/转义字节,这里只做透传
return char(scancode)
def has_data() -> int:
return int(buf_count) + int(out_count)
def init():
global _kbd_inited
# 幂等:init() 有两个调用点(devfs 注册 /dev/input/keyboard、kbd_svc.start),
# 硬件初始化只允许做一次。
if _kbd_inited != 0:
return
_kbd_inited = 1
# 清空控制器输出缓冲(上电自检字节 / 上一次遗留的 ACK 等)
_drain_outbuf()
# 读-改-写 CCB(与 mouse.init 同款写法):
# bit0=1 使能键盘 IRQ1;bit6=1 启用 scancode set1 翻译;bit4=0 使能键盘时钟。
# 这里**不再发 0xAD/0xAE**(禁用 / 启用键盘接口):QEMU 下 0xAD 之后那次 CCB 回写
# 经常失败(键盘数据串入 CCB 读取、回读为 0),0xAE 便恢复不了 ⇒ 键盘接口停在
# 禁用态 ⇒ 之后按键完全不产生任何字节/IRQ1(现场:ki 恒为 2/3、无任何键盘字节)。
# 鼠标没有这个序列,所以它一直正常 —— 键盘对齐鼠标的写法即可。
#
# 全程屏蔽 IRQ1:读 CCB 的 0x20 命令会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1,
# IRQ1 handler 一旦运行就会把这个字节当扫描码读走(现场:handler 装上后读到
# sc=0x47,正是鼠标清 bit5 后的 CCB),这里的 inb(0x60) 便只能拿到残留值 ⇒ 可能把
# 错误的 CCB 写回。屏蔽期 8259 仍会锁存请求,所以解屏蔽前先清空输出缓冲,
# 避免解屏蔽瞬间摄入一个陈旧字节。
pic.setMask(1)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
ccb: uint8 = 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)
# 回读校验:CCB 没写进去 ⇒ 扫描不使能 ⇒ 按键永不产生字节(必须报错,不能静默继续)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
rb: uint8 = asm.inb(KBD_DATA)
if rb != ccb:
serial.puts("[kbd] CCB write verify failed\n")
pic.clearMask(1)
return
# 使能键盘扫描(0xF4):非 0xFA ⇒ 命令没送达键盘
_wait_write()
asm.outb(KBD_DATA, 0xF4)
_wait_read()
ack: uint8 = asm.inb(KBD_DATA)
if ack != 0xFA:
serial.puts("[kbd] scan-enable not ACKed\n")
_drain_outbuf()
# 解屏蔽 IRQ1(对齐 mouse.init 的 pic.clearMask(12)),再装 handler
pic.clearMask(1)
idt.irqInstallHandler(1, irq_handler, "keyboard")
def _wait_read():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x01) != 0: return
timeout -= 1
def _wait_write():
timeout: uint32 = 100000
while timeout > 0:
if (asm.inb(KBD_STATUS) & 0x02) == 0: return
timeout -= 1
def _out_push(b: uint8):
global out_tail, out_count
if out_count >= KBD_OUT_SIZE: return
out_buf[out_tail] = b
out_tail = out_tail + 1
if out_tail >= KBD_OUT_SIZE:
out_tail = 0
out_count += 1
def _emit_csi(final: uint8):
# 整条序列要么全进、要么全丢:半条 CSI 会让 app 侧状态机吞掉下一个字符。
if int(out_count) + 3 > KBD_OUT_SIZE: return
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(final)
def _emit_csi_tilde(digit: uint8):
if int(out_count) + 4 > KBD_OUT_SIZE: return
_out_push(uint8(0x1B))
_out_push(uint8(0x5B))
_out_push(digit)
_out_push(uint8(0x7E))
# CapsLock 只翻转字母大小写(符号仍由 Shift 决定)
def _is_letter_code(code: uint8) -> bool:
if code >= uint8(0x10) and code <= uint8(0x19): return True
if code >= uint8(0x1E) and code <= uint8(0x26): return True
if code >= uint8(0x2C) and code <= uint8(0x32): return True
return False
# 扫描码 -> ASCII / ANSI 转义字节,追加到输出队列(只在 IRQ 上下文调用)
def _decode_scancode(sc: uint8):
global shift_l, shift_r, ctrl_l, alt_l, caps_lock, _e0_pending
if sc == uint8(0xE0):
_e0_pending = 1
return
if sc == uint8(0xE1):
_e0_pending = 0
return
released: bool = (sc & uint8(KEY_RELEASED)) != 0
code: uint8 = uint8(sc & ~uint8(KEY_RELEASED))
if _e0_pending != 0:
_e0_pending = 0
# E0 1D / 38 = 右 Ctrl / 右 Alt
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if released: return
if code == uint8(0x48): _emit_csi(uint8(0x41)) # Up
elif code == uint8(0x50): _emit_csi(uint8(0x42)) # Down
elif code == uint8(0x4D): _emit_csi(uint8(0x43)) # Right
elif code == uint8(0x4B): _emit_csi(uint8(0x44)) # Left
elif code == uint8(0x47): _emit_csi(uint8(0x48)) # Home
elif code == uint8(0x4F): _emit_csi(uint8(0x46)) # End
elif code == uint8(0x53): _emit_csi_tilde(uint8(0x33)) # Delete
elif code == uint8(0x49): _emit_csi_tilde(uint8(0x35)) # PageUp
elif code == uint8(0x51): _emit_csi_tilde(uint8(0x36)) # PageDown
elif code == uint8(0x52): _emit_csi_tilde(uint8(0x32)) # Insert
return
if code == uint8(0x2A):
shift_l = 0
if not released: shift_l = 1
return
if code == uint8(0x36):
shift_r = 0
if not released: shift_r = 1
return
if code == uint8(0x1D):
ctrl_l = 0
if not released: ctrl_l = 1
return
if code == uint8(0x38):
alt_l = 0
if not released: alt_l = 1
return
if code == uint8(0x3A):
if not released: caps_lock = caps_lock ^ uint8(1)
return
if released: return
shifted: bool = (shift_l or shift_r) != 0
if caps_lock and _is_letter_code(code):
shifted = not shifted
ch: char = scancode_set1[code]
if shifted:
ch = scancode_set1_shift[code]
if ch != '\0':
_out_push(uint8(ch))
def irq_handler() -> int:
global kbd_irq_cnt
kbd_irq_cnt += 1
# 0x60 是键盘/鼠标**共享**的输出缓冲:AUX 位(0x64 bit5)置位表示这是鼠标字节,
# 不属于键盘 —— 直接读走会让鼠标驱动永远拿不到(本 ISR 只收键盘字节)。
st: uint8 = asm.inb(KBD_STATUS)
if (st & 0x20) != 0:
return 0
scancode: uint8 = asm.inb(KBD_DATA)
_decode_scancode(scancode)
return 0
# 取一个已解码字节(0 = 当前没有)。每次调用最多消耗 out_buf 里的一个字节。
def read_scancode() -> uint8:
global out_head, out_count
asm.cli()
if out_count == 0:
asm.sti()
return 0
code: uint8 = out_buf[out_head]
out_head = out_head + 1
if out_head >= KBD_OUT_SIZE:
out_head = 0
out_count -= 1
asm.sti()
return code
def scancode_to_ascii(scancode: uint8) -> char:
# read_scancode() 已解码为 ASCII/转义字节,这里只做透传
return char(scancode)
# 只统计解码后的可读字节:>0 时 read_scancode() 必定返回一个非 0 字节
def has_data() -> int:
return int(out_count)
def init():
global _kbd_inited
# 幂等:init() 有两个调用点(devfs 注册 /dev/input/keyboard、kbd_svc.start),
# 硬件初始化只允许做一次。
if _kbd_inited != 0:
return
_kbd_inited = 1
# 清空控制器输出缓冲(上电自检字节 / 上一次遗留的 ACK 等)
_drain_outbuf()
# 读-改-写 CCB(与 mouse.init 同款写法):
# bit0=1 使能键盘 IRQ1;bit6=1 启用 scancode set1 翻译;bit4=0 使能键盘时钟。
# 这里**不再发 0xAD/0xAE**(禁用 / 启用键盘接口):QEMU 下 0xAD 之后那次 CCB 回写
# 经常失败(键盘数据串入 CCB 读取、回读为 0),0xAE 便恢复不了 ⇒ 键盘接口停在
# 禁用态 ⇒ 之后按键完全不产生任何字节/IRQ1(现场:ki 恒为 2/3、无任何键盘字节)。
# 鼠标没有这个序列,所以它一直正常 —— 键盘对齐鼠标的写法即可。
#
# 全程屏蔽 IRQ1:读 CCB 的 0x20 命令会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1,
# IRQ1 handler 一旦运行就会把这个字节当扫描码读走(现场:handler 装上后读到
# sc=0x47,正是鼠标清 bit5 后的 CCB),这里的 inb(0x60) 便只能拿到残留值 ⇒ 可能把
# 错误的 CCB 写回。屏蔽期 8259 仍会锁存请求,所以解屏蔽前先清空输出缓冲,
# 避免解屏蔽瞬间摄入一个陈旧字节。
pic.setMask(1)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
ccb: uint8 = 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)
# 回读校验:CCB 没写进去 ⇒ 扫描不使能 ⇒ 按键永不产生字节(必须报错,不能静默继续)
_wait_write()
asm.outb(KBD_COMMAND, 0x20)
_wait_read()
rb: uint8 = asm.inb(KBD_DATA)
if rb != ccb:
serial.puts("[kbd] CCB write verify failed\n")
pic.clearMask(1)
return
# 使能键盘扫描(0xF4):非 0xFA ⇒ 命令没送达键盘
_wait_write()
asm.outb(KBD_DATA, 0xF4)
_wait_read()
ack: uint8 = asm.inb(KBD_DATA)
if ack != 0xFA:
serial.puts("[kbd] scan-enable not ACKed\n")
_drain_outbuf()
# 解屏蔽 IRQ1(对齐 mouse.init 的 pic.clearMask(12)),再装 handler
pic.clearMask(1)
idt.irqInstallHandler(1, irq_handler, "keyboard")
+1 -1
View File
@@ -1802,8 +1802,8 @@ virtual class Tk(Wm, Misc):
if ch == 91:
self._ansi_state = 2
return 0
# 孤立 ESC:只丢弃 ESC,当前字节照常分发 —— 否则会连带吞掉下一个键入字符。
self._ansi_state = 0
evt.x = 27
return 1
if ch >= 48 and ch <= 57:
self._ansi_param = self._ansi_param * 10 + (ch - 48)