Files
TermiNexus 3583f78efc refactor(kernel): 适配 ViperC 新语法
- 去掉不再需要的 import t, c
- class → struct(纯数据聚合)
- t.CArray[T, N] → [N]T
- static 修饰符移到类型前
- @__attribute(packed) → @__attribute__(packed)
- UHCI 中断回调参数改用 callable[[], int]
- 同步 CPUID 注释中的旧类型名
2026-10-26 22:39:56 +08:00

281 lines
10 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 asm
import intr.idt as idt
import drivers.serial.uart.serial as serial
import platform.pch.pic as pic
define KBD_DATA = 0x60
define KBD_STATUS = 0x64
define KBD_COMMAND = 0x64
define KBD_OUT_SIZE = 1024
define KEY_RELEASED = 0x80
# 唯一的数据队列:IRQ 里把扫描码解码成 ASCII/ANSI 字节后直接入队。
# 读侧只做"取一个字节",不再在 read 里解码 —— 这样 has_data() 与 read_scancode()
# 看的是**同一个计数**,不可能出现"有数据但取不到"的自旋。
out_buf: [KBD_OUT_SIZE]uint8
static out_head: uint32 = 0
static out_tail: uint32 = 0
static out_count: uint32 = 0
static kbd_irq_cnt: uint64 = 0
# 硬件初始化只做一次(init 有多个调用点,见 init() 内注释)
static _kbd_inited: int = 0
static shift_l: uint8 = 0
static shift_r: uint8 = 0
static ctrl_l: uint8 = 0
static alt_l: uint8 = 0
static caps_lock: uint8 = 0
static _e0_pending: 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 _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))
# USB HID 等外部来源注入已解码字节,与 PS/2 IRQ 共用同一个 out_buf。
def inject_byte(b: uint8):
_out_push(b)
def inject_csi(final: uint8):
_emit_csi(final)
def inject_csi_tilde(digit: uint8):
_emit_csi_tilde(digit)
# 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
# 【诊断·临时】确认 IRQ1 是否真的送达
if kbd_irq_cnt == 1:
serial.puts("[kbd] first IRQ1\n")
# 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)
# 【诊断·临时】确认 0xF4 是否被键盘 ACK
if ack == 0xFA:
serial.puts("[kbd] scan-enable ACK ok\n")
else:
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")