PS/2键盘执行“获取扫描码集”命令异常问题排查
问题背景
我正在开发一款简易操作系统,采用Ben Lunt《FYSOS: Input and Output Devices》中的PS/2控制器与键盘初始化方案。该方案在Bochs模拟器及HP Elite Book(Intel Core i5)硬件上运行正常,但在HP Pavilion(Intel Core i5)笔记本上执行“获取扫描码集”命令时出现异常:前两次读取0x60端口返回确认字节,第三次读取(预期获取扫描码集编号)超时。请问是代码存在问题还是硬件存在缺陷?
相关初始化源码
; 子例程 kernel_write_ps2:AL传入要写入的字节,DX传入端口地址,EAX返回结果码 ; 子例程 kernel_read_ps2:读取0x60端口,EAX返回结果码,BL返回读取的字节 ; 两个子例程的超时时间均为100毫秒 dk_init: call dk_set_ps2_for_init ; 禁用两个PS/2通道 mov al, 0xf5 call dk_send_command_wait_acknowledge cmp eax, G_SYSTEM_ERROR_OK jnz .exit mov al, 0xf5 call dk_send_command_wait_acknowledge2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit ; 键盘复位 mov al, 0xff call dk_send_command_wait_acknowledge cmp eax, G_SYSTEM_ERROR_OK jnz .exit mov eax, 750 call kernel_mdelay_pcc ; 延时750毫秒 call kernel_read_ps2 ; 等待输出缓冲区就绪并读取 cmp eax, G_SYSTEM_ERROR_OK jnz .exit cmp bl, 0xaa jz .echo mov eax, G_SYSTEM_ERROR_KB_SELFTEST jmp .exit ; 回显测试 .echo: mov dx, 0x60 mov al, 0xee call kernel_write_ps2 ; 等待输入缓冲区就绪并写入 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit cmp bl, 0xee jz .identify mov eax, G_SYSTEM_ERROR_KB_ECHO jmp .exit ; 获取键盘ID .identify: mov al, 0xf2 call dk_send_command_wait_acknowledge cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit cmp bl, 0xab jz .identify1 mov eax, G_SYSTEM_ERROR_KB_TYPE jmp .exit .identify1: call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call dk_get_scancode_set ; TODO: 重新启用相关设置... .exit: ret ;-------------- dk_send_command_wait_acknowledge: push ebx push edx mov dx, 0x60 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit cmp bl, 0xfa jnz .error mov eax, G_SYSTEM_ERROR_OK jmp .exit .error: mov eax, G_SYSTEM_ERROR_KB_COMMAND_NOT_ACK .exit: pop edx pop ebx ret ;------------- dk_send_command_wait_acknowledge2: ; 用于第二个通道 push ebx push edx mov bl, al mov dx, 0x64 mov al, 0xd4 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit mov al, bl mov dx, 0x60 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit cmp bl, 0xfa jnz .error mov eax, G_SYSTEM_ERROR_OK jmp .exit .error: mov eax, G_SYSTEM_ERROR_KB_COMMAND_NOT_ACK .exit: pop edx pop ebx ret ;---------------- ; 禁用两个通道的中断并关闭翻译功能 dk_set_ps2_for_init: push ebx push edx mov dx, 0x64 mov al, 0x20 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit .change_config: and bl, ~((1 << 6) | (1 << 1) | 1) mov dx, 0x64 mov al, G_PS2_COMMAND_WRITE_RAM_0 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit mov dx, 0x60 mov al, bl call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit .exit: pop edx pop ebx ret ;------------- dk_get_scancode_set: mov dx, 0x60 mov al, 0xf0 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit mov dx, 0x60 mov al, 0 call kernel_write_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit push eax mov al, bl call kernel_print_byte_hex ; 调试打印 pop eax call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit push eax mov al, bl call kernel_print_byte_hex ; 调试打印 pop eax call kernel_read_ps2 cmp eax, G_SYSTEM_ERROR_OK jnz .exit push eax mov al, bl call kernel_print_byte_hex ; 调试打印 pop eax .exit: ret
问题分析与排查方向
代码层面潜在问题
固定读取次数不符合标准响应流程
标准PS/2键盘处理0xF0 0(获取扫描码集)命令的响应是:先返回0xFA确认命令,再返回1字节的扫描码集编号。代码中固定读取3次,属于误判返回数据长度,导致第三次读取无数据可拿而超时。超时时间设置不足
当前kernel_read_ps2的超时为100ms,HP Pavilion的PS/2控制器或键盘可能响应速度更慢,需要更长的等待时间才能返回扫描码集编号。缺乏状态寄存器前置检查
代码直接调用kernel_read_ps2,未先检查0x64端口的状态位(第0位为1表示输出缓冲区就绪),可能在缓冲区未就绪时就开始等待,导致提前触发超时。
硬件层面可能情况
OEM定制硬件的兼容性差异
部分HP Pavilion笔记本的PS/2键盘是OEM定制款,或PS/2接口由南桥模拟实现,对标准PS/2命令的响应逻辑与传统设备不同,可能不支持0xF0命令获取扫描码集,或返回数据的时序存在差异。硬件时序差异
不同机型的PS/2控制器时钟频率、响应延迟存在差异,导致在100ms超时窗口内无法完成数据传输。
修复建议
修正读取逻辑
将dk_get_scancode_set中的固定3次读取改为:先读取确认字节0xFA,再读取1次扫描码集编号(共2次读取);或改为循环读取直到超时,每次读取前检查输出缓冲区状态。延长超时时间
将kernel_read_ps2的超时时间调整为200ms甚至500ms,测试是否能获取到目标数据。添加状态检查
在调用kernel_read_ps2前,循环读取0x64端口,直到第0位为1(输出缓冲区就绪)再执行读取操作,避免无效等待。跳过检测直接设置扫描码集
尝试直接发送0xF0 1(设置扫描码集1)命令,跳过获取步骤,验证硬件是否能正常响应设置命令,判断是否是获取命令本身的兼容性问题。
内容的提问来源于stack exchange,提问作者Ruslan

