You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PS/2键盘执行“获取扫描码集”命令异常问题排查

PS/2键盘初始化获取扫描码集在HP Pavilion上超时问题排查

问题背景

我正在开发一款简易操作系统,采用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

问题分析与排查方向

代码层面潜在问题

  1. 固定读取次数不符合标准响应流程
    标准PS/2键盘处理0xF0 0(获取扫描码集)命令的响应是:先返回0xFA确认命令,再返回1字节的扫描码集编号。代码中固定读取3次,属于误判返回数据长度,导致第三次读取无数据可拿而超时。

  2. 超时时间设置不足
    当前kernel_read_ps2的超时为100ms,HP Pavilion的PS/2控制器或键盘可能响应速度更慢,需要更长的等待时间才能返回扫描码集编号。

  3. 缺乏状态寄存器前置检查
    代码直接调用kernel_read_ps2,未先检查0x64端口的状态位(第0位为1表示输出缓冲区就绪),可能在缓冲区未就绪时就开始等待,导致提前触发超时。

硬件层面可能情况

  1. OEM定制硬件的兼容性差异
    部分HP Pavilion笔记本的PS/2键盘是OEM定制款,或PS/2接口由南桥模拟实现,对标准PS/2命令的响应逻辑与传统设备不同,可能不支持0xF0命令获取扫描码集,或返回数据的时序存在差异。

  2. 硬件时序差异
    不同机型的PS/2控制器时钟频率、响应延迟存在差异,导致在100ms超时窗口内无法完成数据传输。

修复建议

  1. 修正读取逻辑
    将dk_get_scancode_set中的固定3次读取改为:先读取确认字节0xFA,再读取1次扫描码集编号(共2次读取);或改为循环读取直到超时,每次读取前检查输出缓冲区状态。

  2. 延长超时时间
    将kernel_read_ps2的超时时间调整为200ms甚至500ms,测试是否能获取到目标数据。

  3. 添加状态检查
    在调用kernel_read_ps2前,循环读取0x64端口,直到第0位为1(输出缓冲区就绪)再执行读取操作,避免无效等待。

  4. 跳过检测直接设置扫描码集
    尝试直接发送0xF0 1(设置扫描码集1)命令,跳过获取步骤,验证硬件是否能正常响应设置命令,判断是否是获取命令本身的兼容性问题。

内容的提问来源于stack exchange,提问作者Ruslan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:43:14