/* By Marcus Xing kernel/keyboard.c 与键盘的输入输出有关的代码 */ #include "type.h" #include "const.h" #include "protect.h" #include "proc.h" #include "console.h" #include "tty.h" #include "global.h" #include "keyboard.h" #include "keymap.h" #include "proto.h" /* 内部函数和内部变量声明 */ /* 键盘中断处理程序,只能由本文件调用 */ static void Keyboard_Handler(int int_vec_no); /* 从缓冲区中得到一个扫描码,只能由本文件调用*/ static u8 Get_Byte_From_Buffer(); /* 缓冲区结构变量,只能由本文件使用 */ static KB_Input_Buffer KB_Input_Buf; /* 状态变量,只能由本文件调用 */ static int Shift_L; /* 是否按下左边的SHIFT键 */ static int Shift_R; /* 是否按下右边的SHIFT键 */ static int Ctrl_L; /* 是否按下左边的CTRL键 */ static int Ctrl_R; /* 是否按下右边的CTRL键 */ static int Alt_L; /* 是否按下左边的ALT键 */ static int Alt_R; /* 是否按下右边的ALT键 */ static int With_E0; /* 判断2扫描码并第一个码为E0的变量 */ static int Caps_Lock; /* 是否按下Caps_Lock键 */ static int Num_Lock; /* 是否按下Num Lock键 */ static int Scroll_Lock; /* 是否按下Scroll Lock键 */ /* 仅限于本文件调用的功能函数声明 */ static void Keyboard_Handler(int int_vec_no); static u8 Get_Byte_From_Buffer(); static void KB_Wait(); static void KB_Ack(); static void Set_LED(); /*-----------------------------------------------------------------Init_Keyboard 初始化键盘 */ void Init_Keyboard() { /* 三盏灯全灭 */ Caps_Lock = Num_Lock = Scroll_Lock = 0; Set_LED(); /* 初始化缓冲区结构 */ KB_Input_Buf.head = KB_Input_Buf.tail = KB_Input_Buf.buffer; KB_Input_Buf.size = 0; /* 键盘中断的处理函数的填充 */ IRQ_Handler_Table[1] = Keyboard_Handler; /* 激活键盘中断 */ Enable_IRQ(1); } /*-----------------------------------------------------------------Keyboard_Read 读键盘函数,由终端进程调用 */ void Keyboard_Read(TTY *tty) { u8 scan_code = Get_Byte_From_Buffer(); /* 从缓冲区读取一个扫描码 */ int make; /* 判断是否按下,按下为1,不按下为0 */ int col = 0; /* 决定读取keymap表中当前行的列数 */ u32 *row = 0; /* 指向当前码的行的第一列 */ u32 key = 0; /* 存放当前码对应的表中的值 */ /* 有一个特别的地方就是当shift+方向键或者如INSERT之类的控制键按下时 扫描码分别为0x5a0xe00xaa(相应键的4个码)0xe00x5a0xaa 并在后面的shift键的判断时加入了前缀是否是0xe0的判断 */ if(scan_code == 0xe0) { scan_code = Get_Byte_From_Buffer(); /* 判断是否按下的是PRINTSCREEN键 此键比较特殊,是惟一的按下弹起都是4个码的键 */ if(scan_code == 0x2a) { scan_code = Get_Byte_From_Buffer(); if(scan_code == 0xe0) { if(Get_Byte_From_Buffer() == 0x37) { key = PRINTSCREEN; make = 1; } } } /* 判断是否是PRINTSCREEN键弹起 */ else if(scan_code == 0xb7) { if(Get_Byte_From_Buffer() == 0xe0) { if(Get_Byte_From_Buffer() == 0xaa) { key = PRINTSCREEN; make = 0; } } } /* 否则是以0xe0开头的其它键,按下弹起均为2个码 */ else { With_E0 = 1; /* 定位到第2列的标志置1 */ } } /* 判断是否按下了PAUSE键,此键是惟一以0xe1开头的键 PAUSE键有6个扫描码,没有BREAK CODE */ else if(scan_code == 0xe1) { int flag = 1; int i = 0; u32 pause_scan_code[6] = {0xe1,0x1d,0x45,0xe1,0x9d,0xc5}; for(;i < 6;i++) { /* 如果不匹配,标志置0,跳出 */ if(Get_Byte_From_Buffer() != pause_scan_code[i]) { flag = 0; break; } } /* 如果匹配,key赋值 */ if(flag) { key = PAUSEBREAK; make = 1; } } /* 如果不是PRINTSCREEN和PAUSE键的话 */ if((key != PRINTSCREEN) && (key != PAUSEBREAK)) { /* 是按下还是弹起,结果赋给make,MAKE CODE | FLAG_BREAK = BREAK CODE 如果按下或弹起的是以0xe0开头的键,此时的scan_code为第2个码 */ make = (scan_code & FLAG_BREAK) ? 0 : 1; /* 指向当前码的行的第一列 */ row = &Key_Map[(scan_code & 0x7f) * MAP_COLS]; /* 如果Caps Lock灯亮着再按shift+字母键,则还是小写 */ int caps; caps = Shift_L || Shift_R; if(Caps_Lock) { if(row[0] >= 'a' && row[0] <= 'z') { caps = !caps; } } /* 如果SHIFT键还没放开,读当前列的第2列 */ if(caps) { col = 1; } /* 判断是否在此前按下了以0xe0码开头的键 */ if(With_E0) { col = 2; With_E0 = 0; } key = row[col]; /* 从表中取出值 */ /* 在SHIFT键处理时加入了前缀是否是0xe0的判断 */ switch(key) { /* 按下或释放左SHIFT键 */ case SHIFT_L: /* 如果按下,Shift_L置1,否则置0 */ if(!With_E0) { Shift_L = make; } break; /* 如果按下,Shift_R置1,否则置0 */ case SHIFT_R: if(!With_E0) { Shift_R = make; } break; /* 如果按下,Ctrl_L置1,否则置0 */ case CTRL_L: Ctrl_L = make; break; /* 按下或释放右CTRL键 */ case CTRL_R: /* 如果按下,Ctrl_R置1,否则置0 */ Ctrl_R = make; break; /* 按下或释放左ALT键 */ case ALT_L: /* 如果按下,Alt_L置1,否则置0 */ Alt_L = make; break; /* 按下或释放右ALT键 */ case ALT_R: /* 如果按下,Alt_R置1,否则置0 */ Alt_R = make; break; /* 处理Caps Lock键 */ case CAPS_LOCK: /* 如果按下,则亮灭转换下 */ if(make) { Caps_Lock = !Caps_Lock; Set_LED(); } break; /* 处理Num Lock键 */ case NUM_LOCK: /* 如果按下,则亮灭转换下 */ if(make) { Num_Lock = !Num_Lock; Set_LED(); } break; /* 处理Scroll Lock键 */ case SCROLL_LOCK: /* 如果按下,则亮灭转换下 */ if(make) { Scroll_Lock = !Scroll_Lock; Set_LED(); } break; /* 处理其它键 */ default: break; } } int pad = 0; /* 如果按下 */ if(make) { /* 按下的是小键盘上的键 */ if(key >= PAD_SLASH && key <= PAD_9) { pad = 1; /* 标志按下的是小键盘的键 */ /* 以下代码逻辑简单,不注释了 */ switch(key) { case PAD_SLASH: key = '/'; break; case PAD_STAR: key = '*'; break; case PAD_MINUS: key = '-'; break; case PAD_PLUS: key = '+'; break; case PAD_ENTER: key = '/n'; break; case PAD_DOT: if(Num_Lock) { key = '.'; } else { key = '/b'; } break; default: if(Num_Lock && key >= PAD_0 && key <= PAD_9) { key = key - PAD_0 + '0'; } else { switch(key) { case PAD_UP: key = UP; break; case PAD_DOWN: key = DOWN; break; case PAD_LEFT: key = LEFT; break; case PAD_RIGHT: key = RIGHT; break; case PAD_HOME: key = HOME; break; case PAD_END: key = END; break; case PAD_PAGEUP: key = PAGEUP; break; case PAD_PAGEDOWN: key = PAGEDOWN; break; case PAD_INS: key = INSERT; break; default: break; } } break; } } } /* 如果按下,把SHIFT,CTRL,ALT键的信息附在key后 则把key交给tty.c中的函数处理,无论key是否可打印 */ if(make) { key |= (Shift_L ? FLAG_SHIFT_L : 0); key |= (Shift_R ? FLAG_SHIFT_R : 0); key |= (Ctrl_L ? FLAG_CTRL_L : 0); key |= (Ctrl_R ? FLAG_CTRL_R : 0); key |= (Alt_L ? FLAG_ALT_L : 0); key |= (Alt_R ? FLAG_ALT_R : 0); key |= (pad ? FLAG_PAD : 0); /* 是否是小键盘按下的加以区分 */ /* 分离功能,此函数只负责解析,怎么处理交给In_Process */ In_Process(tty,key); } } /*--------------------------------------------------------------Keyboard_Handler 键盘中断处理程序 */ static void Keyboard_Handler(int int_vec_no) { u8 scan_code; scan_code = In_Byte(IN_BUFFER_8042); /* 读取扫描码到scan_code中 */ /* 如果缓冲区还有空间,则把扫描码放入缓冲区队尾指示的位置 */ if(KB_Input_Buf.size < BUFFER_SIZE) { *KB_Input_Buf.tail = scan_code; /* 放到队尾 */ KB_Input_Buf.tail++; /* 尾指针前进一个位置 */ /* 如果尾指针超出缓冲区,则回到缓冲区首地址 */ if(KB_Input_Buf.tail == KB_Input_Buf.buffer + BUFFER_SIZE) { KB_Input_Buf.tail = KB_Input_Buf.buffer; } KB_Input_Buf.size++; /* 缓冲区大小加1 */ } else { /* 没有空间则直接忽略 */ } } /*----------------------------------------------------------Get_Byte_From_Buffer 从缓冲区中得到一个扫描码 */ static u8 Get_Byte_From_Buffer() { u8 scan_code; /* 如果缓冲区没有扫描码,等待着 */ while(KB_Input_Buf.size <= 0){} /* 下面的开中断和关中断是为了保证两个函数调用之间 的代码保持原子性,一气呵成,因为可能会被键盘中 断打断,而键盘中断处理程序又会操作缓冲区,造成 混乱 */ Disable_Int(); /* 关中断 */ scan_code = *KB_Input_Buf.head; /* 队首指针指示的码给scan_code */ KB_Input_Buf.head++; /* 头指针前进一个位置 */ /* 如果队首指针超出缓冲区,则回到缓冲区首地址 */ if(KB_Input_Buf.head == KB_Input_Buf.buffer + BUFFER_SIZE) { KB_Input_Buf.head = KB_Input_Buf.buffer; } KB_Input_Buf.size--; /* 缓冲区大小减1 */ Enable_Int(); /* 开中断 */ return scan_code; } /*-----------------------------------------------------------------------KB_Wait 判断8042的输入缓冲区是否为空,不为空就死等到空为止 向8042的输入缓冲区填数据是向8048发送命令 */ static void KB_Wait() { u8 status; do { status = In_Byte(STATUS_REG_8042); /* 从0x64端口读取8042的状态 */ }while(status & 2); /* 第1位如果为0,则可以向输入缓冲区写数据 */ } /*------------------------------------------------------------------------KB_Ack 设置LED时是否收到8048的反馈,如果没有接收到则死等 */ void KB_Ack() { u8 data; do { data = In_Byte(IN_BUFFER_8042); /* 从8042输出缓冲区中读取反馈 */ }while(data != KB_ACK); /* 如果不是8048的反馈KB_ACK则死等 */ } /*-----------------------------------------------------------------------Set_LED 设置LED,设置LED的命令字中,0表示灭,1表示亮 第0位表示Scroll Lock,第1位表示Num Lock,第2位表示Caps_Lock */ static void Set_LED() { u8 led = Scroll_Lock | (Num_Lock << 1) | (Caps_Lock << 2); /* 组合命令字节 */ KB_Wait(); /* 判断输入缓冲区是否空闲 */ Out_Byte(OUT_BUFFER_8042,KB_SET_LED_COM); /* 表明要控制LED了 */ KB_Ack(); /* 等待回应 */ KB_Wait(); /* 判断输入缓冲区是否空闲 */ Out_Byte(OUT_BUFFER_8042,led); /* 写命令 */ KB_Ack(); /* 等待回应 */ }