注:以下代码参考了linux2.6和Orange's(minix),但不雷同。若有bug,欢迎指出。
欢迎关注baby的成长:https://github.com/guzhoudiaoke/babyos/wiki
为了模仿linux的终端,还专门研究了下如何实现透明效果,结果发现简单的很,就是背景乘上个比例因子加前景乘上个比例因子。
1.键盘的工作原理
要让键盘工作,首先得搞清除键盘是怎么样工作的。
键盘是由一组排列成矩阵方式的按键开关组成,通常有编码键盘和非编码键盘两种类型。IBM 系列PC键盘属于非编码类型,它不直接提供所按键的编码信息,这给系统软件带来更大的灵活性。
微机键盘主要由单片机、译码器和键开关矩阵三部分组成。单片机采用了INTEL8048单片微处理器控制,单片机周期性地扫描行、列并读回扫描线结果,判断是否有键按下,并根据行列计算按键的位置得到扫描码。当有键按下时,向键盘接口发送接通扫描码,释放时发送断开扫描码。系统主板上的键盘接口按照键盘代码串行传送的应答约定,接受键盘发送来的扫描码。
综上,当一个键按下时,键开关矩阵接通,被8048或其兼容芯片扫描到,根据行、列信息计算按键的位置得到扫描码。然后发送给主板上的键盘接口,主板上的芯片会对这个扫描码按照一定的规则进行转换并放到输入缓冲区中,然后告诉8259A有一个键盘中断产生。我们可以通过读IO端口的方法得到这个转换后的扫描码。如果又有键按下8042将不再接收,直到我们把扫描码从缓冲区读出后,8042才继续响应新的按键。
2.扫描码
有三套扫描码:scan code 1,2,3.键盘支持的是scan code2,但为了兼容性,又转换成了scan code 1.
scan code set1:
http://www.computer-engineering.org/ps2keyboard/scancodes1.html
规律:
按下和抬起扫描码相差0x80,如A按下为0x1E,抬起为0x9E.
a和A是一个键,要区分他们需要软件的支持,即判断是否有Shift(0x2A)按下。
一些功能键以0xE0,0xE1开头
3.8042
------------------------------------------------------------------
寄存器名称 寄存器大小 端口 R/W 用法
--------------------------------------------------------------
输出缓冲区 1 BYTE 0x60 R 读输出缓冲区
输入缓冲区 1 BYTE 0x60 W 写输入缓冲区
状态寄存器 1 BYTE 0x64 R 读取状态
控制寄存器 1 BYTE 0x64 W 发送命令
-------------------------------------------------------------------
代码:
1.将设置中断门和陷阱门的代码放到了gate.c中:
/*************************************************************************
> File: gate.c
> 描述: 实现门相关代码
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2013年01月13日 星期日 23时32分55秒
************************************************************************/
#include <gate.h>
#include <kernel.h>
extern u64 idt;
/*
* 使用陷阱门设置IDT表项:
* 入口点31-16位 | 陷阱门标志
* 代码段选择符 | 入口点15-0位
*/
void set_trap_gate(u32 index, u32 addr)
{
u64 idt_item;
idt_item = 0x00008f0000000000ULL; /* 陷阱门的标志 */
idt_item |= (u64)CODE_SELECTOR << 16; /* 代码段选择子 */
idt_item |= ((u64)addr << 32) & 0xffff000000000000ULL; /* 地址high16位 */
idt_item |= ((u64)addr) & 0xffff; /* 地址low16位 */
*((u64*)((u32)&idt + index*8)) = idt_item;
}
/*
* 使用中断门设置IDT表项:
* 入口点31-16位 | 陷阱门标志
* 代码段选择符 | 入口点15-0位
*/
void set_interrupt_gate(u32 index, u32 addr)
{
u64 idt_item;
idt_item = 0x00008e0000000000ULL; /* 陷阱门的标志 */
idt_item |= (u64)CODE_SELECTOR << 16; /* 代码段选择子 */
idt_item |= ((u64)addr << 32) & 0xffff000000000000ULL; /* 地址high16位 */
idt_item |= ((u64)addr) & 0xffff; /* 地址low16位 */
*((u64*)((u32)&idt + index*8)) = idt_item;
}
2.i8259a.c中添加了初始化中断服务程序和设置中断服务程序、开启中断的代码
void init_irq(void)
{
set_interrupt_gate(32, (u32)&isr_0);
set_interrupt_gate(33, (u32)&isr_1);
set_interrupt_gate(34, (u32)&isr_2);
set_interrupt_gate(35, (u32)&isr_3);
set_interrupt_gate(36, (u32)&isr_4);
set_interrupt_gate(37, (u32)&isr_5);
set_interrupt_gate(38, (u32)&isr_6);
set_interrupt_gate(39, (u32)&isr_7);
set_interrupt_gate(40, (u32)&isr_8);
set_interrupt_gate(41, (u32)&isr_9);
set_interrupt_gate(42, (u32)&isr_10);
set_interrupt_gate(43, (u32)&isr_11);
set_interrupt_gate(44, (u32)&isr_12);
set_interrupt_gate(45, (u32)&isr_13);
set_interrupt_gate(46, (u32)&isr_14);
set_interrupt_gate(47, (u32)&isr_15);
}
void set_irq(u32 irq_no, u32 addr)
{
isr_table[irq_no] = addr;
}
void enable_irq(u32 irq_no)
{
/* 开启irq_no号中断,即将中断屏蔽寄存器相应位置为0 */
u8 mask = io_inb(0x21) & (0xff ^ (1 << irq_no));
io_outb(mask, 0x21);
}
3.isr.s类似与exception.s 为中断服务程序的入口:
/*************************************************************************
> File: isr.s
> 描述: 中断服务程序入口
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2013年01月14日 星期一 23时13分52秒
************************************************************************/
.include "kernel.inc"
.global isr_0, isr_1, isr_2, isr_3, isr_4, isr_5, isr_6, isr_7, isr_8
.global isr_9, isr_10, isr_11, isr_12, isr_13, isr_14, isr_15
# 中断处理过程中,若优先级发生变化,会将原ss,esp压入栈中
# 用户程序(进程)将控制权交给中断处理程序之前CPU将至少12字节(EFLAGS、CS、EIP)
# 压入中断处理程序(而不是被中断代码)的堆栈中,即进程的内核态栈中,这种情况与远调用相似
# 有些异常引起中断时,CPU内部会产生一个出错码压入堆栈
# 然后将中断处理程序地址入栈,堆栈指针指向esp_isr处
# 然后所有32位寄存器入栈
# 然后ds,es,fs,gs入栈,此时堆栈指针指向esp_push_all_regs
#
# 堆栈内容:
# -------------------阶段1,优先级改变,保护原来的堆栈---------------------------
# 78 - 原ss
# 64 - 原esp
# -------------------阶段2,控制权交给中断处理程序之前,CPU自动压栈--------------
# 60 - 原eflags
# 66 - cs <- 代码段选择符
# 52 - eip <- 返回地址
# -------------------阶段3-------------------------------------------------------
# 48 - 中断服务程序地址index
# -------------------阶段4-------------------------------------------------------
# 44 - eax
# 40 - ecx
# 36 - edx
# 32 - ebx
# 28 - esp
# 24 - ebp
# 20 - esi
# 16 - edi <- pushad
# 12 - ds
# 08 - es
# 04 - fs
# 00 - gs
isr_common:
pushal
push %ds
push %es
push %fs
push %gs
movl $DATA_SELECTOR, %edx
movw %dx, %ds
movw %dx, %es
movw %dx, %fs
movw %dx, %gs
movl 48(%esp), %eax
movl isr_table(, %eax, 4), %ebx
/*
pushl $200
pushl $200
pushl %ebx
call draw_hex
addl $12, %esp
*/
call *%ebx # '*'号表示调用操作数指定地址处的函数
pop %gs
pop %fs
pop %es
pop %ds
popal
addl $4, %esp # 跳过48,52处的中断处理程序地址和错误号
iret
isr_0:
pushl $0
jmp isr_common
isr_1:
pushl $1
jmp isr_common
isr_2:
pushl $2
jmp isr_common
isr_3:
pushl $3
jmp isr_common
isr_4:
pushl $4
jmp isr_common
isr_5:
pushl $5
jmp isr_common
isr_6:
pushl $6
jmp isr_common
isr_7:
pushl $7
jmp isr_common
isr_8:
pushl $8
jmp isr_common
isr_9:
pushl $9
jmp isr_common
isr_10:
pushl $10
jmp isr_common
isr_11:
pushl $11
jmp isr_common
isr_12:
pushl $12
jmp isr_common
isr_13:
pushl $13
jmp isr_common
isr_14:
pushl $14
jmp isr_common
isr_15:
pushl $15
jmp isr_common
4.keyboard.c为键盘中断主要代码,实现了一个环形队列来作为按键扫描码的缓冲区,目前只实现了基本的打印任务,以后有所需要再逐渐完善:
/*************************************************************************
> File: keyboard.c
> 描述: 实现一个简单的键盘驱动
> Author: 孤舟钓客
> Mail: guzhoudiaoke@126.com
> Time: 2013年01月13日 星期日 19时35分09秒
************************************************************************/
#include <keyboard.h>
#include <i8259a.h>
#include <font.h>
u8 io_inb (u16 port);
void io_outb(u8 value, u16 port);
void io_cli();
void io_sti();
extern u32 isr_table[NR_IRQ];
kb_queue_t kb_q;
BOOL b_shift_l, b_shift_r;
static void init_kb_queue()
{
kb_q.front = 0;
kb_q.rear = 0;
}
static BOOL is_kb_queue_empty()
{
if (kb_q.rear == kb_q.front)
return TRUE;
return FALSE;
}
static BOOL is_kb_queue_full()
{
if ((kb_q.rear + 1) % KB_BUFFER_SIZE == kb_q.front)
return TRUE;
return FALSE;
}
static BOOL en_kb_queue(u8 val)
{
if (is_kb_queue_full())
return FALSE;
kb_q.buffer[kb_q.rear] = val;
kb_q.rear = (kb_q.rear + 1) % KB_BUFFER_SIZE;
return TRUE;
}
/* 队头元素出队,即将p_head指向的元素拿出来,队头后移 */
static BOOL de_kb_queue(u8* val)
{
if (is_kb_queue_empty())
return FALSE;
*val = kb_q.buffer[kb_q.front];
kb_q.front = (kb_q.front + 1) % KB_BUFFER_SIZE;
return TRUE;
}
void do_keyboard()
{
u8 scan_code; /* 读取扫描码 */
u8 com, val;
static s32 k_x = 60, k_y = 80;
scan_code = io_inb(0x60);
#if 1
if (!is_kb_queue_full()) /* 若队列未满则入队,否则放弃该扫描码 */
{
en_kb_queue(scan_code);
}
#endif
io_outb(0x20, 0x20);
#if 0
draw_hex(scan_code, k_x, k_y);
k_x += 100;
if (k_x + 160 > 750)
{
k_x = 60;
k_y += 20;
}
#endif
}
void init_keyboard(void)
{
set_irq(IRQ_KEYBOARD, (u32)&do_keyboard); /* 设置中断处理程序 */
enable_irq(IRQ_KEYBOARD); /* 通知8259A开启键盘中断 */
init_kb_queue(); /* 初始化缓存区队列 */
b_shift_l = FALSE;
b_shift_r = FALSE;
}
void keyboard_read()
{
u8 scan_code;
u32 key;
BOOL b_shift;
static s32 k_x = 60, k_y = 80;
if (! is_kb_queue_empty())
{
io_cli();
de_kb_queue(&scan_code);
io_sti();
b_shift = b_shift_l || b_shift_r;
key = keymap[scan_code & 0x7f][b_shift];
if (key == K_SHIFT_L)
b_shift_l = !b_shift_l;
if (key == K_SHIFT_R)
b_shift_r = !b_shift_r;
b_shift = b_shift_l || b_shift_r;
if (scan_code & 0x80)
return;
if (KEY_TYPE(keymap[scan_code & 0x7f][b_shift]) != KT_ASCII)
return;
draw_asc((u8)(keymap[scan_code & 0x7f][b_shift]), k_x, k_y);
k_x += 8;
if (k_x + 8 > 750)
{
k_x = 60;
k_y += 20;
}
}
}