键盘驱动系列---JIURL键盘驱动 5

7 键盘驱动的运作

7.1 输入数据队列简介

i8042prt 和 kbdclass 各有一个输入数据队列,他们是循环使用的缓冲区。他们的每个单元是一个 KEYBOARD_INPUT_DATA 结构。

i8042prt 的自定义的设备扩展中,保存着一些指针和计数值,用来使用它的那个输入数据队列。

PKEYBOARD_INPUT_DATA 类型的 InputData , DataIn , DataOut , DataEnd。ULONG 类型的 InputCount。KEYBOARD_INPUT_DATA 类型的 CurrentInput 。

InputData 这个指针,指向输入数据队列的开头。

DataEnd 这个指针,指向输入数据队列的结尾。

DataIn 这个指针,指向要进入队列的新数据,将要被放在队列中的位置。

DataOut 这个指针,指向要出队列的数据,在队列中开始的位置。

InputCount 这个值为输入数据队列中,数据的个数。

CurrentInput 存放当前从i8042芯片中获得的数据。

kbdclass 的自定义的设备扩展中,保存着一些指针和计数值,用来使用它的那个输入数据队列。

PKEYBOARD_INPUT_DATA 类型的 InputData , DataIn , DataOut , DataEnd。ULONG 类型的 InputCount。

InputData 这个指针,指向输入数据队列的开头。

DataEnd 这个指针,指向输入数据队列的结尾。

DataIn 这个指针,指向要进入队列的新数据,将要被放在队列中的位置。

DataOut 这个指针,指向要出队列的数据,在队列中开始的位置。

InputCount 这个值为输入数据队列中,数据的个数。

KEYBOARD_INPUT_DATA 结构在 ntddkbd.h 中定义

typedef struct _KEYBOARD_INPUT_DATA { // 0xC

USHORT UnitId;

USHORT MakeCode;

USHORT Flags;

USHORT Reserved;

ULONG ExtraInformation;

} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;

7.2 键盘驱动对读请求的处理

    键盘驱动的应用层线程 win32k!RawInputThread,总是会发一个 IRP_MJ_READ 的 IRP 给键盘设备栈的栈顶设备对象,要求读入数据。键盘设备栈栈顶设备对象所在的驱动 kbdclass 的 kbdclass!KeyboardClassRead 处理这个 IRP_MJ_READ 的 IRP。在初始化过程中 win32k!RawInputThread 发第一个 IRP_MJ_READ 的 IRP。之后,每当这个 IRP_MJ_READ 的 IRP 得到数据结束返回给应用层之后,应用层又会再发一个 IRP_MJ_READ 的 IRP。

我们使用WinDbg的!irp命令,看看 kbdclass!KeyboardClassRead 传入的参数 Irp

kd> !irp fe43e008

Irp is active with 6 stacks 6 is current (= 0xfe43e12c)

No Mdl System buffer = fe42d868 Thread fe42ed60: Irp stack trace. 

cmd cl Device File Completion-Context

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

>[ 3, 0] 0 0 fe4f5df0 fe42d908 00000000-00000000 

\Driver\Kbdclass

Args: 00000078 00000000 00000000 00000000

查看当前的 IO_STACK_LOCATION 中的详细内容

kd> !strct io_stack_location fe43e12c

struct _IO_STACK_LOCATION (sizeof=36)

+00 byte MajorFunction = 03 .

+01 byte MinorFunction = 00 .

+02 byte Flags = 00 .

+03 byte Control = 00 .

+04 union __unnamed19 Parameters

+04 struct __unnamed23 Read

+04 uint32 Length = 00000078

+08 uint32 Key = 00000000

+0c union _LARGE_INTEGER ByteOffset

+0c uint32 LowPart = 00000000

+10 int32 HighPart = 00000000

+0c struct __unnamed3 u

+0c uint32 LowPart = 00000000

+10 int32 HighPart = 00000000

+0c int64 QuadPart = 0000000000000000

+14 struct _DEVICE_OBJECT *DeviceObject = FE4F5DF0

+18 struct _FILE_OBJECT *FileObject = FE42D908

+1c function *CompletionRoutine = 00000000

+20 void *Context = 00000000

我们看到当前 IO_STACK_LOCATION 的 MajorFunction 为 0x03,即 IRP_MJ_READ。Parameters.Read.Length 为 0x78,表明 Irp->AssociatedIrp.SystemBuffer 指向0x78字节大小的空间,一个 KEYBOARD_INPUT_DATA 的大小为 0xc,所以 Irp->AssociatedIrp.SystemBuffer 中最多可以放10(十进制)个KEYBOARD_INPUT_DATA。

我这里 kbdclass 和 i8042prt 的输入数据队列的大小都为 0x64*sizeof(KEYBOARD_INPUT_DATA),即100(十进制)个 KEYBOARD_INPUT_DATA 的大小。

kbdclass!KeyboardClassRead 中,

调用 IoMarkIrpPending(Irp) ,使这个 IRP pending,然后调用 IoStartPacket 。IoStartPacket 将调用驱动的 StartIo 例程,kbdclass 的 StartIo 例程是 KeyboardClassStartIo。

kbdclass!KeyboardClassStartIo 的作用是,检查 kbdclass 的输入数据队列中是否有数据,如果有的话,直接从 kbdclass 的输入数据队列中,取出数据,满足读请求。

kbdclass 的输入数据队列中的数据是从哪里来的呢?如果从 i8042prt 的输入数据队列中来的输入数据太多,一个 IRP_MJ_READ 的 IRP 读不完的话,则 IRP 读走它所能读的 10 个 KEYBOARD_INPUT_DATA,而剩余的数据就会被放入 kbdclass 的输入数据队列中。通常情况下,从 i8042prt 的输入数据队列中来的输入数据都能被一个 IRP_MJ_READ 的 IRP 全部取走,不会有剩余。

kbdclass!KeyboardClassStartIo 中,

判断 deviceExtension->InputCount 是否为0。deviceExtension->InputCount 中保存着 kbdclass 的输入数据队列的中数据的个数。也就是判断 kbdclass 的输入数据队列中是否有数据。

没有数据的话,通常是这种情况,设置 deviceExtension->RequestIsPending 为 TRUE,返回。

有数据的话,调用 RtlMoveMemory 把kbdclass的输入数据队列中的数据复制到 Irp->AssociatedIrp.SystemBuffer 中,设置 deviceExtension->RequestIsPending 为 FALSE,调用 IoCompleteRequest 结束这个 IRP_MJ_READ 的 IRP。

简单的说,应用层发来一个 IRP_MJ_READ 的 IRP 要求读数据。

如果 kbdclass 的输入数据队列中没有数据的话,这个 IRP_MJ_READ 的 IRP 就 pending 在那里,等待键盘上的键被按下,产生输入数据。通常都是这种情况。

如果 kbdclass 的输入数据队列中有数据的话,也就是说键盘的输入数据太多,上一个 IRP_MJ_READ 的 IRP 没有取完,剩下的放在了 kbdclass 的输入数据队列中,那么现在这个 IRP_MJ_READ 的 IRP 从 kbdclass 的输入数据队列中直接取出数据,然后被完成。当我们按住一个键不放的时候,就能看到这种情况。

7.3 键盘驱动的中断

键盘驱动在初始化的过程中,调用 IoConnectInterrupt 连接了键盘的驱动。其中调用时的参数 ServiceRoutine 为 I8042KeyboardInterruptService ,参数 Vector 为 0xb3。

我们使用 WinDbg 的 !idt 命令,查看这时的中断描述符表

kd> !idt

IDT for processor #0

00: 804625e6 (nt!KiTrap00)

...

b3: fe4cf264 (Vector:b3,Irql:a,SyncIrql:a,Connected:TRUE,No:0,ShareVector:FALSE,Mode:Latched,ISR:i8042prt!I8042KeyboardInterruptService(fe1c1750))

b4: 80461138 (nt!KiUnexpectedInterrupt132)

...

ff: 804613f0 (nt!KiUnexpectedInterrupt207)

可以看到中断向量为 b3 的中断服务例程的入口地址为 fe4cf264,而并不是 i8042prt!I8042KeyboardInterruptService 的入口地址 fe1c1750。那么这个入口地址是什么呢?原来,

调用 IoConnectInterrupt ,将产生一个 KINTERRUPT 结构,中断描述符表中保存的中断服务例程的入口地址就是这个 KINTERRUPT 结构的 DispatchCode 的地址。中断时,执行这个 DispatchCode ,DispatchCode 进行些处理后,会调用 nt!KiInterruptDispatch ,而 nt!KiInterruptDispatch 根据 KINTERRUPT 结构中的 ServiceRoutine ,也就是 IoConnectInterrupt 的参数 ServiceRoutine 的入口地址,调用驱动中的服务例程。

我们从 http://www.insidewindows.info/ntifs.h 可以找到 KINTERRUPT 的结构定义

typedef struct _KINTERRUPT { // Size: 0x1E4

/*000*/ CSHORT Type;

/*002*/ CSHORT Size;

/*004*/ LIST_ENTRY InterruptListEntry;

/*00C*/ PKSERVICE_ROUTINE ServiceRoutine;

/*010*/ PVOID ServiceContext;

/*014*/ KSPIN_LOCK SpinLock;

/*018*/ ULONG TickCount;

/*01C*/ PKSPIN_LOCK ActualLock;

/*020*/ PVOID DispatchAddress;

/*024*/ ULONG Vector;

/*028*/ KIRQL Irql;

/*029*/ KIRQL SynchronizeIrql;

/*02A*/ BOOLEAN FloatingSave;

/*02B*/ BOOLEAN Connected;

/*02C*/ CHAR Number;

/*02D*/ UCHAR ShareVector;

/*030*/ KINTERRUPT_MODE Mode;

/*034*/ ULONG ServiceCount;

/*038*/ ULONG DispatchCount;

/*03C*/ ULONG DispatchCode[106];

} KINTERRUPT, *PKINTERRUPT;

我们从中断描述符表中看到的中断向量为 b3 的中断服务例程的入口地址fe4cf264,就是一个 KINTERRUPT 中的 DispatchCode 的地址。fe4cf264-3c 就是这个KINTERRUPT 的首地址。fe4cf264-3c+c 处就是 KINTERRUPT 中的 ServiceRoutine。我们看到 fe4cf264-3c+c 处为 fe1c1750,正是 i8042prt!I8042KeyboardInterruptService 的入口地址。

当键盘中断产生时,将会执行 KINTERRUPT 中的 DispatchCode,DispatchCode 会调用 nt!KiInterruptDispatch ,nt!KiInterruptDispatch 会调用 KINTERRUPT 结构中的 ServiceRoutine ,也就是 i8042prt!I8042KeyboardInterruptService。

7.4 按一个普通键-a

7.4.1 调试信息

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf710, DataOut 0xfe4cf710

8042: I8xWriteDataToKeyboardQueue: InputCount 0

8042: I8xWriteDataToKeyboardQueue: new InputCount 1

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x9e

i8042 isr (kb): real scan code

i8042 isr (kb): BREAK code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf71c, DataOut 0xfe4cf710

8042: I8xWriteDataToKeyboardQueue: InputCount 1

8042: I8xWriteDataToKeyboardQueue: new InputCount 2

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

8042: I8042KeyboardIsrDpc: enter

8042: I8xDpcVariableOperation: enter

8042: Performing increment at 0xfe4f529c (current value 0xffffffff)

8042: I8xDpcVariableOperation: exit with value 0x0

8042: I8xGetDataQueuePointer: enter

8042: I8xGetDataQueuePointer: keyboard

8042: I8xGetDataQueuePointer: DataIn 0xfe4cf728, DataOut 0xfe4cf710

8042: I8xGetDataQueuePointer: exit

8042: I8042KeyboardIsrDpc: calling class callback

8042: I8042KeyboardIsrDpc: with Start 0xfe4cf710 and End 0xfe4cf728

KBDCLASS-KeyboardClassServiceCallback: enter

KBDCLASS-KeyboardClassServiceCallback: port queue length 0x18, read length 0x78

KBDCLASS-KeyboardClassServiceCallback: number of bytes to move from port to SystemBuffer 0x18

KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf710 to 0xfe385d88

KBDCLASS-KeyboardClassServiceCallback: bytes remaining after move to SystemBuffer 0x0

KBDCLASS-KeyboardClassServiceCallback: exit

8042: I8042KeyboardIsrDpc: Call callback consumed 2 items, left 0

8042: I8xSetDataQueuePointer: enter

8042: I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf710, InputCount 2

8042: I8xSetDataQueuePointer: Okay to log keyboard overflow

8042: I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf728, InputCount 0

8042: I8xSetDataQueuePointer: exit

8042: I8xDpcVariableOperation: enter

8042: Performing decrement at 0xfe4f529c (current value 0x0)

8042: I8xDpcVariableOperation: exit with value 0xffffffff

8042: I8042KeyboardIsrDpc: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5938, DataOut 0xfe4f5938

KBDCLASS-KeyboardClassStartIo: entries in queue 0

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

7.4.2 分析

按一个a键,按下时产生一个 Make Code,引发一个键盘中断,松开时产生一个 Break Code,引发一个键盘中断。a键的 Scan Code Set 1 的 Make Code 为 0x1e,Break Code 为 0x9e。

按下时产生一个 Make Code,引发键盘中断,最终导致 i8042prt!I8042KeyboardInterruptService 被执行。i8042prt!I8042KeyboardInterruptService 中,

首先从0x64端口读出 i8042 的状态寄存器,确保i8042状态寄存器的 Bit0 为1,然后从0x60端口读出数据,这里是a键的Make Code 0x1e。

检查 deviceExtension->IsrHookCallback 是否为空,不为空就会调用 deviceExtension->IsrHookCallback。

这说明了驱动 i8042prt 支持了hook,我们可以通过DeviceIoControl IOCTL_INTERNAL_I8042_HOOK_KEYBOARD 来设置这个 deviceExtension->IsrHookCallback 。

我们这里 deviceExtension->IsrHookCallback 为空,没有hook。

判断是否是 RESEND 或者 ACKNOWLEDGE,RESEND 为 0xFE,ACKNOWLEDGE 为 0xFA。如果都不是的话,就认为是一个扫描码。我们这里就是扫描码。

判断 deviceExtension->CurrentScanState 中保存的状态。当从i8042读出扩展码的0xe0或者0xe1时,会设置这里,随后读入的数据,就知道是扩展码的一部分。

我们这里是普通状态,判断从i8042读入的数据是否为 0xe0 或者 0xe1,如果是的话,就设置 deviceExtension->CurrentScanState 为 GotE0 或者 GotE1。我们这里是 0x1e ,都不是。

判断从i8042读入的数据是否大于 0x7F,也就是判断是 Make Code 还是 Break Code。

我们这里是 0x1E 小于 0x7F,是 Make Code。

对于 Make Code,仅仅是把这个 Make Code 保存在 CurrentInput 的 MakeCode 中。

将 deviceExtension->CurrentScanState 设置为普通。

调用 I8xQueueCurrentKeyboardInput。

I8xQueueCurrentKeyboardInput 中,

调用 I8xWriteDataToKeyboardQueue 将 CurrentInput 放入 i8042prt 的输入数据队列。

调用 KeInsertQueueDpc ,将 deviceExtension->KeyboardIsrDpc 排队,以待执行。deviceExtension->KeyboardIsrDpc 的 DeferredRoutine 在键盘驱动的初始化过程中已经被赋值为 i8042prt!I8042KeyboardIsrDpc。

按下时引发键盘中断到此结束。

松开时产生一个 Break Code,引发键盘中断,最终导致 i8042prt!I8042KeyboardInterruptService 被执行。i8042prt!I8042KeyboardInterruptService 中,

大致和 Make Code 的情况相同直到,

判断从i8042读入的数据是否大于 0x7F,也就是判断是 Make Code 还是 Break Code。

我们这里是 0x9E 大于 0x7F,是 Break Code。

对于 Break Code,把这个 Break Code & 0x7F,也就是最高位清0,然后保存在 CurrentInput 的 MakeCode 中,然后设置 CurrentInput 的 Flags 为 KEY_BREAK,表明这是个 Break Code。

然后将 CurrentInput 放入 i8042prt 的输入数据队列。将 deviceExtension->KeyboardIsrDpc 也就是 i8042prt!I8042KeyboardIsrDpc 的那个 DPC 排队,以待执行。

松开时引发键盘中断到此结束。 

当从中断的 IRQL 降下来时,刚才排队的 DPC(延迟过程调用)i8042prt!I8042KeyboardIsrDpc 就有机会被执行了。

i8042prt!I8042KeyboardIsrDpc 中,以i8042prt的设备扩展中的使用i8042prt的输入数据队列的那些指针为参数,调用保存在 deviceExtension->ConnectData.ClassService 中的回调例程 kbdclass!KeyboardClassServiceCallback。键盘驱动初始化过程中,kbdclass 告诉 i8042prt ,它使用 kbdclass!KeyboardClassServiceCallback 处理输入数据,于是 i8042prt把这个函数的入口地址保存在了 deviceExtension->ConnectData.ClassService 中。kbdclass!KeyboardClassServiceCallback 会从 i8042prt的输入数据队列中取走数据。从 kbdclass!KeyboardClassServiceCallback 返回之后,根据取走数据的情况,设置i8042prt的设备扩展中的 DataOut 和 InputCount。整个按a键的驱动处理过程结束。

kbdclass!KeyboardClassServiceCallback 中,

判断kbdclass的 deviceExtension->RequestIsPending ,如果为TRUE,说明已经有 IRP_MJ_READ 的 IRP 在等待数据,就满足这个 IRP。绝大多数情况都有一个 IRP 在等待数据。

我们这里就有一个 IRP_MJ_READ 的 IRP 在等待数据,而我们这时i8042prt的输入数据队列中有两个数据,一个a键的Make Code,一个a键的Break Code。一个 IRP_MJ_READ 的 IRP 最多一次可以读10个数据,所以我们现在的这个情况,可以一次读完。于是调用 RtlMoveMemory 把i8042prt中的两个数据复制到 irp->AssociatedIrp.SystemBuffer 中,之后会调用 IoCompleteRequest 结束这个 IRP。

而对于deviceExtension->RequestIsPending ,为FALSE,说明上一个 IRP 结束后,应用层在处理中,还没有再发一个 IRP 到键盘驱动中。或者是i8042prt的输入数据队列中的数据很多,一个IRP读完之后,还有剩余。那么 i8042prt 的输入数据队列中剩余的数据将被 kbdclass!KeyboardClassServiceCallback 放入 kbdclass 的输入数据队列。

按a键的驱动处理过程中有不少代码是用来解决使用输入数据队列的问题的,关于输入数据队列的问题我们在后面讨论。

7.4.3 示意图

按a之前。

处理,按下a产生的MakeCode,的i8042prt!I8042KeyboardInterruptService结束之后。

处理,松开a产生的BreakCode,的i8042prt!I8042KeyboardInterruptService结束之后。

kbdclass!KeyboardClassServiceCallback, 中将 i8042prt 的输入数据队列中的数据复制到irp->AssociatedIrp.SystemBuffer之后。

kbdclass!KeyboardClassServiceCallback 中调用 IoCompleteRequest 完成这个 IRP 之后。

i8042prt!I8042KeyboardIsrDpc 设置 DataOut 和 InputCount 之后。

 

7.5 按一个扩展键-向上箭头

7.5.1 调试信息

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xe0

i8042 isr (kb): real scan code

i8042 isr (kb): change state to GotE0

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x48

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf60c, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 0

8042: I8xWriteDataToKeyboardQueue: new InputCount 1

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xe0

i8042 isr (kb): real scan code

i8042 isr (kb): change state to GotE0

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xc8

i8042 isr (kb): real scan code

i8042 isr (kb): BREAK code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf618, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 1

8042: I8xWriteDataToKeyboardQueue: new InputCount 2

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

8042: I8042KeyboardIsrDpc: enter

8042: I8xDpcVariableOperation: enter

8042: Performing increment at 0xfe4f529c (current value 0xffffffff)

8042: I8xDpcVariableOperation: exit with value 0x0

8042: I8xGetDataQueuePointer: enter

8042: I8xGetDataQueuePointer: keyboard

8042: I8xGetDataQueuePointer: DataIn 0xfe4cf624, DataOut 0xfe4cf60c

8042: I8xGetDataQueuePointer: exit

8042: I8042KeyboardIsrDpc: calling class callback

8042: I8042KeyboardIsrDpc: with Start 0xfe4cf60c and End 0xfe4cf624

KBDCLASS-KeyboardClassServiceCallback: enter

KBDCLASS-KeyboardClassServiceCallback: port queue length 0x18, read length 0x78

KBDCLASS-KeyboardClassServiceCallback: number of bytes to move from port to SystemBuffer 0x18

KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf60c to 0xfe426748

KBDCLASS-KeyboardClassServiceCallback: bytes remaining after move to SystemBuffer 0x0

KBDCLASS-KeyboardClassServiceCallback: exit

8042: I8042KeyboardIsrDpc: Call callback consumed 2 items, left 0

8042: I8xSetDataQueuePointer: enter

8042: I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf60c, InputCount 2

8042: I8xSetDataQueuePointer: Okay to log keyboard overflow

8042: I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf624, InputCount 0

8042: I8xSetDataQueuePointer: exit

8042: I8xDpcVariableOperation: enter

8042: Performing decrement at 0xfe4f529c (current value 0x0)

8042: I8xDpcVariableOperation: exit with value 0xffffffff

8042: I8042KeyboardIsrDpc: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5938, DataOut 0xfe4f5938

KBDCLASS-KeyboardClassStartIo: entries in queue 0

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

7.5.2 分析

按一个向上箭头键,按下时产生一个 Make Code,引发一个键盘中断,松开时产生一个 Break Code,引发一个键盘中断。向上箭头键的 Scan Code Set 1 的 Make Code 为 0xE0,0x48,Break Code 为 0xE0,0xC8。这个键的扫描码是扩展码。

对于普通码和扩展码,只在 i8042prt!I8042KeyboardInterruptService 中的处理稍有不同。

首先按下向上箭头键,产生Make Code,0xE0,0x48。

0xe0 首先引发一个键盘中断,导致 i8042prt!I8042KeyboardInterruptService 被执行。i8042prt!I8042KeyboardInterruptService 中,读取0x60端口,得到的将是 0xe0。此时 deviceExtension->CurrentScanState 为普通状态,当判断发现为读入数据为 0xe0 时,设置CurrentInput 的 Flags 为 KEY_E0,deviceExtension->CurrentScanState 设置为 GotE0。对于 0xe0 的中断处理全部结束,0xe0不会被写入i8042prt的数据队列,也不会排队一个 DPC。

0x48 接着引发一个键盘中断,导致 i8042prt!I8042KeyboardInterruptService 被执行。

i8042prt!I8042KeyboardInterruptService 中,读取0x60端口,得到的将是 0x48。此时 deviceExtension->CurrentScanState 为 GotE0 状态,将不再判断得到的数据是否是e0或者e1,之后判断是否大于 0x7f,也就是判断是 MakeCode 还是 BreakCode,我们这里是MakeCode,于是 CurrentInput 的 MakeCode 被赋值为 0x48。我们要注意一下此时的 CurrentInput 和非扩展键时的 CurrentInput 的不同,他们的 MakeCode 都是按键的扫描码,不过非扩展键时的Flags为0,扩展键时的Flags为KEY_E0。然后将 deviceExtension->CurrentScanState 设置为普通状态,然后将这个CurrentInput放入i8042prt的输入数据队列,然后排队一个DPC。

松开向上箭头键,产生Break Code,0xE0,0xC8。

0xe0 首先引发一个键盘中断,导致 i8042prt!I8042KeyboardInterruptService 被执行。i8042prt!I8042KeyboardInterruptService 中,读取0x60端口,得到的将是 0xe0。此时 deviceExtension->CurrentScanState 为普通状态,当判断发现为读入数据为 0xe0 时,设置CurrentInput 的 Flags 为 KEY_E0,deviceExtension->CurrentScanState 设置为 GotE0。对于 0xe0 的中断处理全部结束,0xe0不会被写入i8042prt的数据队列,也不会排队一个 DPC。

0x48 接着引发一个键盘中断,导致 i8042prt!I8042KeyboardInterruptService 被执行。

i8042prt!I8042KeyboardInterruptService 中,读取0x60端口,得到的将是 0xC8。此时 deviceExtension->CurrentScanState 为 GotE0 状态,将不再判断得到的数据是否是e0或者e1,之后判断是否大于 0x7f,也就是判断是 MakeCode 还是 BreakCode,我们这里是BreakCode,于是 CurrentInput 的 MakeCode 被赋值为 0xC8。我们要注意一下此时的 CurrentInput 和非扩展键时的 CurrentInput 的不同,他们的 MakeCode 都是按键的扫描码,不过非扩展键时的Flags为KEY_BREAK,扩展键时的Flags为KEY_E0|KEY_BREAK。然后将 deviceExtension->CurrentScanState 设置为普通状态,然后将这个CurrentInput放入i8042prt的输入数据队列,然后排队一个DPC。

之后 DPC 中的处理,和非扩展键没有区别。

我们来讨论一下 扩展键与非扩展键的 KEYBOARD_INPUT_DATA 数据的区别。

KEYBOARD_INPUT_DATA 中的 Flags 的定义在 ntddkbd.h 中

#define KEY_MAKE 0

#define KEY_BREAK 1

#define KEY_E0 2

#define KEY_E1 4

对于从 i8042 中读到 E0 的情况,E0并不写入输入数据队列 ,而只是设置 KEYBOARD_INPUT_DATA 中的 Flags 

对于非扩展键的 MakeCode 的 Flags 为 KEY_MAKE,即 0。

对于非扩展键的 BreakCode 的 Flags 为 KEY_BREAK,即 1。

对于E0扩展键的 MakeCode 的 Flags 为 KEY_MAKE|KEY_E0,即 2。

对于E0扩展键的 BreakCode 的 Flags 为 KEY_BREAK|KEY_E0,即 3。

对于E1扩展键的 MakeCode 的 Flags 为 KEY_MAKE|KEY_E1,即 4。

对于E1扩展键的 BreakCode 的 Flags 为 KEY_BREAK|KEY_E1,即 5。

7.6 按一个设置指示灯的键-Caps Lock-点亮LED

7.6.1 调试信息

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x3a

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf5c8, DataOut 0xfe4cf5c8

8042: I8xWriteDataToKeyboardQueue: InputCount 0

8042: I8xWriteDataToKeyboardQueue: new InputCount 1

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xba

i8042 isr (kb): real scan code

i8042 isr (kb): BREAK code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf5d4, DataOut 0xfe4cf5c8

8042: I8xWriteDataToKeyboardQueue: InputCount 1

8042: I8xWriteDataToKeyboardQueue: new InputCount 2

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

8042: I8042KeyboardIsrDpc: enter

8042: I8xDpcVariableOperation: enter

8042: Performing increment at 0xfe4f529c (current value 0xffffffff)

8042: I8xDpcVariableOperation: exit with value 0x0

8042: I8xGetDataQueuePointer: enter

8042: I8xGetDataQueuePointer: keyboard

8042: I8xGetDataQueuePointer: DataIn 0xfe4cf5e0, DataOut 0xfe4cf5c8

8042: I8xGetDataQueuePointer: exit

8042: I8042KeyboardIsrDpc: calling class callback

8042: I8042KeyboardIsrDpc: with Start 0xfe4cf5c8 and End 0xfe4cf5e0

KBDCLASS-KeyboardClassServiceCallback: enter

KBDCLASS-KeyboardClassServiceCallback: port queue length 0x18, read length 0x78

KBDCLASS-KeyboardClassServiceCallback: number of bytes to move from port to SystemBuffer 0x18

KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf5c8 to 0xfe428868

KBDCLASS-KeyboardClassServiceCallback: bytes remaining after move to SystemBuffer 0x0

KBDCLASS-KeyboardClassServiceCallback: exit

8042: I8042KeyboardIsrDpc: Call callback consumed 2 items, left 0

8042: I8xSetDataQueuePointer: enter

8042: I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf5c8, InputCount 2

8042: I8xSetDataQueuePointer: Okay to log keyboard overflow

8042: I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf5e0, InputCount 0

8042: I8xSetDataQueuePointer: exit

8042: I8xDpcVariableOperation: enter

8042: Performing decrement at 0xfe4f529c (current value 0x0)

8042: I8xDpcVariableOperation: exit with value 0xffffffff

8042: I8042KeyboardIsrDpc: exit

KBDCLASS-KeyboardClassDeviceControl: enter

8042: IOCTL: enter

8042: IOCTL: keyboard set indicators

8042: I8042StartIo: enter

8042: I8xControllerRoutine: keyboard set indicators

8042: I8xInitiateIo: enter

8042: I8xInitiateIo: sending byte #0 (0xed)

8042: I8xPutByteAsynchronous: enter

8042: I8xPutByteAsynchronous: sending 0xed to data port

8042: I8xPutByteAsynchronous: exit

8042: I8xInitiateIo: exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xfa

i8042 isr (kb): : ACK, i8042 isr (kb): now initiate send of byte #1

8042: I8xInitiateIo: enter

8042: I8xInitiateIo: sending byte #1 (0x4)

8042: I8xPutByteAsynchronous: enter

8042: I8xPutByteAsynchronous: sending 0x4 to data port

8042: I8xPutByteAsynchronous: exit

8042: I8xInitiateIo: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0xfa

i8042 isr (kb): : ACK, i8042 isr (kb): all bytes have been sent

i8042 isr (kb): exit

8042: I8042StartIo: exit

8042: I8042CompletionDpc: enter

8042: I8042CompletionDpc: keyboard set indicators updated

8042: I8042CompletionDpc: new LED flags 0x4

8042: I8042CompletionDpc: exit

8042: IOCTL: exit (0x103)

KBDCLASS-KeyboardClassDeviceControl: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5908, DataOut 0xfe4f5908

KBDCLASS-KeyboardClassStartIo: entries in queue 0

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

7.6.2 分析

按一个 Caps Lock 键,按下时产生一个 Make Code,引发一个键盘中断,松开时产生一个 Break Code,引发一个键盘中断。向上箭头键的 Scan Code Set 1 的 Make Code 为 0x3A,Break Code 为 0xBA。我们是在 Caps Lock 的 LED 指示灯熄灭的状态下按的。熄灭LED灯和点亮LED灯的处理没有本质区别。

键盘驱动对于按 Caps Lock 键的输入数据的处理,和普通的键没有区别。

而当应用层中处理输入数据时,发现是 Caps Lock,将会调用 DeviceIoControl ,给键盘设备栈发一个 IRP_MJ_DEVICE_CONTROL IOCTL: IOCTL_KEYBOARD_SET_INDICATORS 的 IRP。这将执行 kbdclass!KeyboardClassDeviceControl。

kbdclass!KeyboardClassDeviceControl 被执行时的 Call Stack

# ChildEBP RetAddr Args to Child 

00 f90ef800 8041f54b fe4f5df0 fe385368 fe385368 kbdclass!KeyboardClassDeviceControl(struct _DEVICE_OBJECT * DeviceObject = 0xfe4f5df0, struct _IRP * Irp = 0xfe385368) (CONV: stdcall)

01 f90ef814 804ba5e8 fe3b38ec 00000000 fe385368 nt!IopfCallDriver+0x35 (FPO: [0,0,2])

02 f90ef828 804ac5de fe4f5df0 fe385368 fe426728 nt!IopSynchronousServiceTail+0x60 (FPO: [Non-Fpo])

03 f90ef8f4 804a8f1e 000000d4 00000000 00000000 nt!IopXxxControlFile+0x5e4 (FPO: [Non-Fpo])

04 f90ef928 80461691 000000d4 00000000 00000000 nt!NtDeviceIoControlFile+0x28 (FPO: [Non-Fpo])

05 f90ef928 80400b51 000000d4 00000000 00000000 nt!KiSystemService+0xc4 (FPO: [0,0] TrapFrame @ f90ef958)

06 f90ef9c8 a0000503 000000d4 00000000 00000000 nt!ZwDeviceIoControlFile+0xb (FPO: [10,0,0])

07 f90efa00 a00ee42a 00000000 e19534f0 f90efa9c win32k!UpdateKeyLights+0xfe (FPO: [1,0,3])

08 f90efa54 a00886ae 00000014 0000003a 000a5013 win32k!xxxKeyEvent+0x2fe (FPO: [Non-Fpo])

09 f90efa7c a008c77f f90efa14 00000000 00000000 win32k!xxxProcessKeyEvent+0x142 (FPO: [Non-Fpo])

0a f90efaa8 a005c98b e1953448 fe3853a8 80430982 win32k!ProcessKeyboardInput+0x181 (FPO: [Non-Fpo])

0b f90efab4 80430982 e1953448 e1953468 00000000 win32k!InputApc+0x3b (FPO: [3,0,1])

0c f90efae8 80403a44 00000000 00000000 00000000 nt!KiDeliverApc+0xdb (FPO: [Non-Fpo])

0d f90efb08 8042d33d 80400b46 00000001 00000000 nt!KiSwapThread+0xfc (FPO: [EBP 0xf90efb3c] [0,0,4])

0e f90efb3c a000eaf5 00000004 fe427ac8 00000001 nt!KeWaitForMultipleObjects+0x266 (FPO: [Non-Fpo])

0f f90efda8 804524f6 00000002 00000000 00000000 win32k!RawInputThread+0x3c2 (FPO: [Non-Fpo])

10 f90efddc 80465b62 a000e7cd f8d5f7d0 00000000 nt!PspSystemThreadStartup+0x69 (FPO: [Non-Fpo])

11 00000000 f000ff53 f000e2c3 f000ff53 f000ff53 nt!KiThreadStartup+0x16

WARNING: Frame IP not in any known module. Following frames may be wrong.

12 f000ff53 00000000 00000000 00000000 00000000 0xf000ff53

我们使用 WinDbg 的 !irp 命令来看看这个传入的 IRP

kd> !irp fe385368

Irp is active with 6 stacks 6 is current (= 0xfe38548c)

No Mdl System buffer = fe3b38e8 Thread fe427b00: Irp stack trace. 

cmd cl Device File Completion-Context

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

[ 0, 0] 0 0 00000000 00000000 00000000-00000000 

Args: 00000000 00000000 00000000 00000000

>[ e, 0] 0 0 fe4f5df0 fe426728 00000000-00000000 

\Driver\Kbdclass

Args: 00000000 00000004 000b0008 00000000

看看IRP的当前 IO_STACK_LOCATION 中的详细内容

kd> !strct io_stack_location fe38548c

struct _IO_STACK_LOCATION (sizeof=36)

+00 byte MajorFunction = 0e .

+01 byte MinorFunction = 00 .

+02 byte Flags = 00 .

+03 byte Control = 00 .

+04 union __unnamed19 Parameters

+04 struct __unnamed33 DeviceIoControl

+04 uint32 OutputBufferLength = 00000000

+08 uint32 InputBufferLength = 00000004

+0c uint32 IoControlCode = 000b0008

+10 void *Type3InputBuffer = 00000000

+14 struct _DEVICE_OBJECT *DeviceObject = FE4F5DF0

+18 struct _FILE_OBJECT *FileObject = FE426728

+1c function *CompletionRoutine = 00000000

+20 void *Context = 00000000

kbdclass!KeyboardClassDeviceControl 中对这个 IRP 不做处理,将下一个 IO_STACK_LOCATION 的 MajorFunction 设置为 IRP_MJ_INTERNAL_DEVICE_CONTROL,然后向下传。

驱动 i8042prt 中,将向i8042发0xed命令,然后发参数,让i8048点亮 Caps Lock 的LED指示灯。

7.7 按住a键不放一段时间

7.7.1 调试信息

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf60c, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 0

8042: I8xWriteDataToKeyboardQueue: new InputCount 1

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

8042: I8042KeyboardIsrDpc: enter

8042: I8xDpcVariableOperation: enter

8042: Performing increment at 0xfe4f529c (current value 0xffffffff)

8042: I8xDpcVariableOperation: exit with value 0x0

8042: I8xGetDataQueuePointer: enter

8042: I8xGetDataQueuePointer: keyboard

8042: I8xGetDataQueuePointer: DataIn 0xfe4cf618, DataOut 0xfe4cf60c

8042: I8xGetDataQueuePointer: exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf618, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 1

8042: I8xWriteDataToKeyboardQueue: new InputCount 2

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf624, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 2

8042: I8xWriteDataToKeyboardQueue: new InputCount 3

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf630, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 3

8042: I8xWriteDataToKeyboardQueue: new InputCount 4

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf63c, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 4

8042: I8xWriteDataToKeyboardQueue: new InputCount 5

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf648, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 5

8042: I8xWriteDataToKeyboardQueue: new InputCount 6

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf654, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 6

8042: I8xWriteDataToKeyboardQueue: new InputCount 7

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf660, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 7

8042: I8xWriteDataToKeyboardQueue: new InputCount 8

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf66c, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 8

8042: I8xWriteDataToKeyboardQueue: new InputCount 9

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf678, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 9

8042: I8xWriteDataToKeyboardQueue: new InputCount 10

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf684, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 10

8042: I8xWriteDataToKeyboardQueue: new InputCount 11

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf690, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 11

8042: I8xWriteDataToKeyboardQueue: new InputCount 12

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x1e

i8042 isr (kb): real scan code

i8042 isr (kb): MAKE code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf69c, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 12

8042: I8xWriteDataToKeyboardQueue: new InputCount 13

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

i8042 isr (kb): enter

i8042 isr (kb): scanCode 0x9e

i8042 isr (kb): real scan code

i8042 isr (kb): BREAK code

8042: I8xWriteDataToKeyboardQueue: enter

8042: I8xWriteDataToKeyboardQueue: DataIn 0xfe4cf6a8, DataOut 0xfe4cf60c

8042: I8xWriteDataToKeyboardQueue: InputCount 13

8042: I8xWriteDataToKeyboardQueue: new InputCount 14

8042: I8xWriteDataToKeyboardQueue: exit

i8042 isr (kb): exit

8042: I8042KeyboardIsrDpc: calling class callback

8042: I8042KeyboardIsrDpc: with Start 0xfe4cf60c and End 0xfe4cf618

KBDCLASS-KeyboardClassServiceCallback: enter

KBDCLASS-KeyboardClassServiceCallback: port queue length 0xc, read length 0x78

KBDCLASS-KeyboardClassServiceCallback: number of bytes to move from port to SystemBuffer 0xc

KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf60c to 0xfe3a0b88

KBDCLASS-KeyboardClassServiceCallback: bytes remaining after move to SystemBuffer 0x0

KBDCLASS-KeyboardClassServiceCallback: exit

8042: I8042KeyboardIsrDpc: Call callback consumed 1 items, left 0

8042: I8xSetDataQueuePointer: enter

8042: I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf60c, InputCount 14

8042: I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf618, InputCount 13

8042: I8xSetDataQueuePointer: exit

8042: I8xDpcVariableOperation: enter

8042: Performing decrement at 0xfe4f529c (current value 0xd)

8042: I8xDpcVariableOperation: exit with value 0xc

8042: I8xDpcVariableOperation: enter

8042: Performing write at 0xfe4f529c (current value 0xc)

8042: Writing 0x0

8042: I8xDpcVariableOperation: exit with value 0x0

8042: I8042KeyboardIsrDpc: loop in DPC

8042: I8xGetDataQueuePointer: enter

8042: I8xGetDataQueuePointer: keyboard

8042: I8xGetDataQueuePointer: DataIn 0xfe4cf6b4, DataOut 0xfe4cf618

8042: I8xGetDataQueuePointer: exit

8042: I8042KeyboardIsrDpc: calling class callback

8042: I8042KeyboardIsrDpc: with Start 0xfe4cf618 and End 0xfe4cf6b4

KBDCLASS-KeyboardClassServiceCallback: enter

KBDCLASS-KeyboardClassServiceCallback: bytes remaining after move to SystemBuffer 0x9c

KBDCLASS-KeyboardClassServiceCallback: unused bytes in class queue 0x4b0, remaining bytes in port queue 0x9c

KBDCLASS-KeyboardClassServiceCallback: total number of bytes to move to class queue 0x9c

KBDCLASS-KeyboardClassServiceCallback: number of bytes to end of class buffer 0x12c

KBDCLASS-KeyboardClassServiceCallback: number of bytes in first move to class 0x9c

KBDCLASS-KeyboardClassServiceCallback: move bytes from 0xfe4cf618 to 0xfe4f5c8c

KBDCLASS-KeyboardClassServiceCallback: changed InputCount to 13 entries in the class queue

KBDCLASS-KeyboardClassServiceCallback: DataIn 0xfe4f5d28, DataOut 0xfe4f5c8c

KBDCLASS-KeyboardClassServiceCallback: Input data items consumed = 13

KBDCLASS-KeyboardClassServiceCallback: exit

8042: I8042KeyboardIsrDpc: Call callback consumed 13 items, left 0

8042: I8xSetDataQueuePointer: enter

8042: I8xSetDataQueuePointer: old keyboard DataOut 0xfe4cf618, InputCount 13

8042: I8xSetDataQueuePointer: Okay to log keyboard overflow

8042: I8xSetDataQueuePointer: new keyboard DataOut 0xfe4cf6b4, InputCount 0

8042: I8xSetDataQueuePointer: exit

8042: I8xDpcVariableOperation: enter

8042: Performing decrement at 0xfe4f529c (current value 0x0)

8042: I8xDpcVariableOperation: exit with value 0xffffffff

8042: I8042KeyboardIsrDpc: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5d28, DataOut 0xfe4f5c8c

KBDCLASS-KeyboardClassStartIo: entries in queue 13

KBDCLASS-KeyboardClassStartIo: queue size 0x9c, read length 0x78

KBDCLASS-KeyboardClassStartIo: bytes to end of queue 0x12c

KBDCLASS-KeyboardClassStartIo: number of bytes in first move 0x78

KBDCLASS-KeyboardClassStartIo: move bytes from 0xfe4f5c8c to 0xfe3a0b88

KBDCLASS-KeyboardClassStartIo: new DataIn 0xfe4f5d28, DataOut 0xfe4f5d04

KBDCLASS-KeyboardClassStartIo: new InputCount 3

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5d28, DataOut 0xfe4f5d04

KBDCLASS-KeyboardClassStartIo: entries in queue 3

KBDCLASS-KeyboardClassStartIo: queue size 0x24, read length 0x78

KBDCLASS-KeyboardClassStartIo: bytes to end of queue 0xb4

KBDCLASS-KeyboardClassStartIo: number of bytes in first move 0x24

KBDCLASS-KeyboardClassStartIo: move bytes from 0xfe4f5d04 to 0xfe3a0b88

KBDCLASS-KeyboardClassStartIo: Okay to log overflow

KBDCLASS-KeyboardClassStartIo: new DataIn 0xfe4f5d28, DataOut 0xfe4f5d28

KBDCLASS-KeyboardClassStartIo: new InputCount 0

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

KBDCLASS-KeyboardClassRead: enter

KBDCLASS-KeyboardClassStartIo: enter

KBDCLASS-KeyboardClassStartIo: DataIn 0xfe4f5d28, DataOut 0xfe4f5d28

KBDCLASS-KeyboardClassStartIo: entries in queue 0

KBDCLASS-KeyboardClassStartIo: exit

KBDCLASS-KeyboardClassRead: exit

7.7.2 分析

由于按住不放,使得不断的发生中断,让排队的DPC没有机会执行,从而在i8042prt的输入数据队列中累积了大量的数据。导致了在kbdclass层等待的那个 IRP_MJ_READ 的 IRP 不能一次把所有数据读走,于是 KeyboardClassServiceCallback 把剩余的数据放入 kbdclass 的输入数据队列。之后应用层发来的 IRP_MJ_READ 的 IRP 将直接从 kbdclass 的输入数据队列中读数据,直到所有的数据都被读完了,应用层再发来的 IRP_MJ_READ 的 IRP 才会在 kbdclass 上等待数据。

由于第一个 MakeCode 和第二个 MakeCode 之间有一个 Typematic Delay ,之间间隔时间比较长,所以 DPC 有机会被执行。这里就是被执行,不过执行了一半,又被后面的 MakeCode 中断。关于 Typematic Delay 的详细介绍,请看前面对于 ps/2 键盘硬件的介绍。

7.8 输入数据队列讨论

i8042prt 和 kbdclass 各有自己的一个输入数据队列,循环使用的缓冲区。他们的每个单元是一个 KEYBOARD_INPUT_DATA 结构。

kbclass 和 i8042prt 的输入数据队列是在键盘驱动的初始化过程中分配内存的。

kbdclass 的输入数据队列是在 kbdclass!KeyboardAddDevice 中,分配内存,并初始化设备扩展中的使用输入数据队列的相关域。

i8042prt 的输入数据队列是在 i8042prt!I8xKeyboardStartDevice 中,分配内存,并初始化设备扩展中的使用输入数据队列的相关域。

kbdclass 和 i8042prt 的输入数据队列的单元个数都是读取注册表中的参数决定的,如果注册表中不存在相应的键值,那么使用默认值。默认值为 0x64(十进制100)。

对于这两个输入数据队列,

放入一个数据,这个数据应该被放在 DataIn 处,然后DataIn 后移一格,InputCount 加 1。当 DataIn 移到队列的结尾时,将从队列的开头重新开始。

取出一个数据,这个数据应该从 DataOut 处取出,然后 DataOut 后移一格,InputCount 减 1。当 DataOut 移到队列的结尾时,将从队列的开头重新开始。

对于 i8042prt 的输入数据队列。键盘中断服务例程中,从i8042读出的按键信息,放入i8042prt的输入数据队列。上层处理输入的回调函数中,取出i8042prt的输入数据队列中的数据。由于一次键盘中断只会放入一个数据,所以i8042prt的输入数据队列,只会放入一个一个的放入数据。

对于 kbdclass 的输入数据队列。只有当那个等待的 IRP_MJ_READ IRP 要求读的大小,小于i8042prt的输入队列中放入的数据时,才被使用。也就是说,只有当那个等着读的 IRP 读不完输入数据时,才使用 kbdclass 的输入数据队列。当那个等着读的 IRP 读完要求的数据后,还有剩余时,剩余的数据放入kbdclass的输入数据队列。下一次的应用层发来的 IRP_MJ_READ IRP,当发现kbdclass的输入数据队列中有数据时,直接从kbdclass的输入数据队列中读出数据。

输入数据队列反转的判断和处理

随着数据的输入,DataIn 不断后移,就会出现下图的情况。

遇到这种情况的话,要想从输入数据队列中复制数据,就需要把数据分为两段来复制,从 DataOut 到 DataEnd 一段,从 InputData 到 DataIn 是另一段。

对于i8042prt的输入数据队列,如果 DataOut >= DataIn ,并且 InputCount > 0,就说明遇到了这种情况。分两段,分别调用 KeyboardClassServiceCallback。

对于kbdclass的输入数据队列,如果要传给 IRP 的数据大小>从DataOut到输入数据队列尾部的大小,说明遇到了这种情况。分两段,分两次复制到IRP的 AssociatedIrp.SystemBuffer 中。

输入数据队列覆盖的判断和处理

随着数据的输入,DataIn 不断后移,最终用完了整个输入数据队列,如下图所示。

如果再有输入数据,DataIn 再后移,就会出现下图的情况。

覆盖会使数据不正确。

i8042prt的输入数据队列,一次只会写入一个数据。写入数据时做判断,如果 DataIn 等于 DataOut ,并且 InputCount 不为0的话,说明如果将当前的输入数据写入输入数据队列会造成覆盖。处理方法就是丢弃当前的输入数据不写入输入数据队列。并且丢弃前一个输入数据,用腾出的单元放一个 MakeCode 为KEYBOARD_OVERRUN_MAKE_CODE 的数据。

DataIn移到队尾时的处理

对于 i8042prt 的输入数据队列,写入数据时判断,如果写入数据后 DataIn 等于 DataEnd,那么说明 DataIn 已经移到了队尾,将 DataIn 设置为队头 InputData。

对于 kbdclass 的输入数据队列,只有当需要移入数据的大小>从当前 DataIn 到队尾的大小时,才会出现 DataIn 移到队尾的情况。遇到这种情况,移入数据将分两次移入,第一次移入到 DataIn 到 队尾,移完之后DataIn就到了队尾,然后把DataIn 设置为队头 InputData。

DataOut移到队尾时的处理

对于 i8042prt 的输入数据队列,只有分两段时,DataOut才会出现移到队尾的情况。分两段取走数据,当第一段(从 DataOut 到 DataEnd)全部取完之后,就把 DataOut 设置为队头 InputData。

对于 kbdclass 的输入数据队列,只有分两段时,DataOut才会出现移到队尾的情况。分两段,分两次复制到IRP的 AssociatedIrp.SystemBuffer 中。DataOut 到队尾不需要特殊处理。

8 升华

    学会了写驱动,就可以控制硬件了,想想吧,有多好,能干多少有意思的事啊。

    我们以键盘驱动为对象,从硬件直到应用层,这一整套做了一定的研究。对碰到的各种 Windows 驱动的基本问题,比如驱动对象,设备对象,设备栈,IRP机制,等等,也做了一定的研究。对一个的了解,就可以推及其他。对 Windows 驱动建立起了整体的认识。键盘驱动成了一个我们十分熟悉的很好的范例,以后在其他驱动中遇到的很多问题,很可能都可以在键盘驱动中找到值得参考的解决方法。最后我们以键盘驱动为例,来谈一谈 Windows 驱动中的一些概念,认识。

    与所有硬件设备的通信,对所有硬件设备的控制,使用,都是通过读写端口。要给一个硬件写驱动,需要对这个硬件有一定的了解,尤其是通过端口访问的一些寄存器,一些命令。比如键盘驱动中,通过对0x60和0x64这两个端口的读写,完成从键盘上读数据,命令键盘点亮led指示灯,之类的工作。

    FDO, Functional Device Object ,功能设备对象。i8042prt的用于键盘的那个设备对象就是键盘设备栈中的 FDO,而所有与键盘硬件有关的操作都是在 i8042prt 中实现。

    PDO,Physical Device Object ,物理设备对象。acpi的用于键盘的那个设备对象就是键盘设备栈中的 PDO,它是设备栈中最低下的那个设备对象,管一些总线的事,比如处理电源管理之类。对于键盘相关的各种处理不起什么作用。

    port driver,端口驱动,驱动i8042prt就是键盘驱动中的端口驱动,所有的读写端口的工作都是在这个驱动进行的,这也就是为什么叫做端口驱动。

    class driver,类驱动,驱动kbdclass就是键盘驱动中的类驱动。ps/2键盘的端口驱动和usb键盘的端口驱动是绝对不同的,而ps/2键盘和usb键盘的类驱动都是kbdclass,kbdclass驱动完成的是硬件无关的,键盘这一类设备共同的工作,这也就是为什么叫做类驱动。

    从这些概念中,我们也可以对驱动为什么要分层,如何分层,有一点感觉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值