旧内核桌面 desktop.vp 里有完整的键盘解码(修饰键状态、特殊键映射、重复),S3e 下沉后丢了,只剩一个简化 scancode_to_ascii(shift/caps 字段从未赋值、方向键返回 0 被丢)。① 内核 keyboard.vp:新增解码层 _decode_scancode + 输出队列,跟踪 Shift(0x2A/0x36)/CapsLock(0x3A)/Ctrl/Alt,Shift 输出大写与上档符号、Caps 只翻转字母区;E0 前缀的独立方向键/Home/End/Delete/PageUp/PageDown/Insert 输出 ANSI 序列(ESC[A/B/C/D、ESC[H/F、ESC[3~/5~/6~/2~);释放码只清状态不产字符。devfs 的 ASCII 字节流契约不变。② vpui:Tk 新增 _ansi_state/_ansi_param 与 _fold_ansi_key(),在 KEY 分发前折叠 ANSI 序列,映射到既有 VK_* 语义(Text/Entry 里那些分支不再是死代码)。Trim:Insert 无 VK_INSERT 定义、被吞掉;typematic 重复未恢复;USB 键盘未涉及。
279 lines
10 KiB
Plaintext
279 lines
10 KiB
Plaintext
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
|
||
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 从原始缓冲解码后填入)
|
||
out_buf: [KBD_OUT_SIZE]uint8
|
||
out_head: static uint32 = 0
|
||
out_tail: static uint32 = 0
|
||
out_count: static uint32 = 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'
|
||
]
|
||
|
||
# 清空 8042 输出缓冲(上电自检字节 / 遗留 ACK / 读 CCB 时压入的值)
|
||
def _drain_outbuf():
|
||
drain: uint32 = 0
|
||
while (asm.inb(KBD_STATUS) & 0x01) != 0:
|
||
asm.inb(KBD_DATA)
|
||
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")
|