Files
ViperOS/VKernel/Kernel/drivers/input/keyboard/keyboard.vp
T
TermiNexus 3a497e38ec fix(usb): 控制传输短读判失败+描述符链完整性校验;CCB 读改写屏蔽 IRQ1;清理已验证探针
uhci.control_transfer:除错误位外再比对 DATA TD 的 Actual Length(bit10:0)。短读时错误位可能全 0(QEMU/硬件不把短包当错误),只查错误位会漏 —— 现场是 USB 鼠标的 25 字节配置描述符只落下前 9 字节 ⇒ 接口解析为 0 ⇒ [usb_mse] no USB mouse found。

usb._parse_config 改为返回实际走完的偏移,enumerate_port 要求恰好走完 total_len,否则重读、仍不完整即报错返回(fail-fast,不带着半截描述符继续);同时删除两条探针。

keyboard.init/mouse.init 读-改-写 CCB 全程 pic.setMask(1):读 CCB 的 0x20 会把 CCB 值放进 0x60 输出缓冲并拉高 IRQ1,IRQ1 handler 会把它当扫描码抢走(现场 sc=0x47),使回读拿到残留值。改为只在校验失败时打印错误,去掉全部 [kbd] 探针。

清理已验证调试输出:idt 的 IRQ1 handler 探针及其计数器、desktop 的 [dbg] back_fb/row_buf/bg_row_buf、main 的 [DBG] LAPIC/idt32/swint/pic_isr 与 [DB] BootInfo 转储、timer 的 [PIT] 与自旋打印及随之无用的 import。
2026-10-16 15:26:35 +08:00

179 lines
6.5 KiB
Plaintext
Raw 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
import string
import t, c
define KBD_DATA = 0x60
define KBD_STATUS = 0x64
define KBD_COMMAND = 0x64
define KBD_BUF_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
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
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 read_scancode() -> uint8:
global buf_head, buf_count
asm.cli()
if buf_count == 0:
asm.sti()
return 0
code: uint8 = 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: uint8) -> char:
code: uint8 = uint8(scancode & ~uint8(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() -> int:
return buf_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")