console.c
static void
lpt_putc(int c)
{
int i;
/* 0x378~0x37A parallel printer port
0378 w data port
0379 r/w status port
bit 7 = 0 busy * 0x80
bit 6 = 0 acknowledge
bit 5 = 1 out of paper
bit 4 = 1 printer is selected
bit 3 = 0 error
bit 2 = 0 IRQ has occurred
bit 1-0 reserved
037A r/w control port
bit 7-5 reserved
bit 4 = 1 enable IRQ
bit 3 = 1 select printer * 0x08
bit 2 = 0 initialize printer *0x04
bit 1 = 1 automatic line feed
bit 0 = 1 strobe * 0x01
*/
for (i = 0; !(inb(0x378+1) & 0x80) && i < 12800; i++) // when not busy, or time out
delay();
outb(0x378+0, c); // write char c
outb(0x378+2, 0x08|0x04|0x01); // initialize printer
outb(0x378+2, 0x08); // select printer
}
void cga_init(void)
{
volatile uint16_t *cp;
uint16_t was;
unsigned pos;
cp = (uint16_t*) (KERNBASE + CGA_BUF);
/*
CGA_BUF: 0xB8000 (物理地址)
为了使地址转换后(-KERNBASE)还是0xB8000,那么就需要+KERNBASE
*/
was = *cp; //保存当前光标所在值
*cp = (uint16_t)0xA55A; // ?? *********
if (*cp != 0xA55A) {
cp = (uint16_t*)(KERNBASE + MONO_BUF);
addr_6845 = MONO_BASE; /* CGA_BASE: 0x3B4 */
}else {
*cp = was; // 还原原来的值
addr_6845 = CGA_BASE; /* CGA_BASE: 0x3D4 */
}
/* Extract cursor location */
outb(addr_6845, 14); // 写控制寄存器,0x0E:获得cursor location high,其值在下一个寄存器
pos = inb(addr_6845 + 1) << 8;
outb(addr_6845, 15); // 写控制寄存器,0x0F:获得cursor location low,其值在下一个寄存器
pos |= inb(addr_6845 + 1);
crt_buf = (uint16_t*) cp; //临时变量赋值给全局变量
crt_pos = pos; //保存当前光标位置
}
void
cga_putc(int c)
{
// if no attribute given, then use black on white
if (!(c & ~0xFF)) // c 低16位为字符值,高16位为显示属性
c |= 0x0700;
switch (c & 0xff) { // 去除属性
case '\b': // backspace
if (crt_pos > 0) {
crt_pos--;
crt_buf[crt_pos] = (c & ~0xff) | ' '; // 删除处使用空格填充
}
break;
case '\n': //new line:换行, 自动添加回车
crt_pos += CRT_COLS;
/* fallthru */
case '\r': //carriage return:回车, 跳动行首
crt_pos -= (crt_pos % CRT_COLS);
break;
case '\t': //table:制表符,转换成5个空格
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
cons_putc(' ');
break;
default:
crt_buf[crt_pos++] = c; /* write the character */
break;
}
// What is the purpose of this? 如果一屏写满,则向上滚动一行
if (crt_pos >= CRT_SIZE) {
int i;
// 所有数据向前挪动一行,最上面一行数据丢失
memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
// 清空最后一行,用空格填充
for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
crt_buf[i] = 0x0700 | ' ';
crt_pos -= CRT_COLS;
}
// 写光标位置
/* move that little blinky thing */
outb(addr_6845, 14);
outb(addr_6845 + 1, crt_pos >> 8);
outb(addr_6845, 15);
outb(addr_6845 + 1, crt_pos);
}
/*早期的键盘实际上是一种5pin的键盘,称作AT键盘,是1984年IBM PC的标准键盘。在1987年IBM进行了改进,从而变成了现在的PS/2键盘。
当按下一个键或释放一个键,键盘都会发送键盘扫描码到主机。比如按下A,键盘就会发送0x1C到主机。如果持续按A,当经过一个给定时间后,就会发送0x1C到主机。当键盘被释放,键盘会发送0xF0加键码到主机,告诉主机键盘哪个键被释放。当再次按下A,键盘就会再次发送0x1C到主机。键盘的每一个键都有一个特定的键码,无论SHIFT、Num Lock、Caps Lock、Scroll Lock键是否被按下,键盘总是发送同样的键码,主机的键盘BIOS负责区分SHIFT、Num Lock、Caps Lock、Scroll Lock键的状态。键盘有101个键,而PS/2接口只有8比特。因此,并不是所有的键都只有一个字节的键码。扩展键盘中有一些键的键码是双字节的,以 E0开头,比如向左键为E04B。有些键的扫描码非常夸张,比如Pause Brk键的键码为E11D45
*/
/*
PS/2接口只有8比特,单字节最多能表示256个值,每个键被按下和被释放均需有不同的值表示,所以256个最多表示128个按键,为了区分和便于记忆,键被释放时的码值=0x80+键被按下时的码值,0和0x80没有被用作键值,最早的键盘按键较少,一个字节够用,随着扩展键的引入,单字节键值显得不够用了,就得使用多字节(2字节居多),以双字节为被按下键值=E0后面加一个字节xx,那么被释放时键值=E0后面加(0x80+xx)
比如:
a 的被按下键值为1E,那么被释放时键值为9E
HOME 的被按下键值为0E47,被释放时键值为0EC7
Pause Brk键的被按下键码为E11D45,被释放时的键码为E19DC5
*/
/***** Keyboard input code *****/
#define NO 0
#define SHIFT (1<<0)
#define CTL (1<<1)
#define ALT (1<<2)
#define CAPSLOCK (1<<3)
#define NUMLOCK (1<<4)
#define SCROLLLOCK (1<<5)
#define E0ESC (1<<6)
static uint8_t shiftcode[256] =
{
[0x1D] CTL, //左Ctrl, 1D
[0x2A] SHIFT, //左shift 2A
[0x36] SHIFT, //右shift 36
[0x38] ALT, //左alt 38
[0x9D] CTL, //右Ctrl E01D
[0xB8] ALT //右alt E038
};
static uint8_t togglecode[256] =
{
[0x3A] CAPSLOCK, // 3A
[0x45] NUMLOCK, // 45
[0x46] SCROLLLOCK // 46
};
// 按键码对应字符的asiic码(没有按下shift键),如 0x2 => '1'
static uint8_t normalmap[256] =
{
NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00
'7', '8', '9', '0', '-', '=', '\b', '\t',
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10
'o', 'p', '[', ']', '\n', NO, 'a', 's',
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20
'\'', '`', NO, '\\', 'z', 'x', 'c', 'v',
'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30
NO, ' ', NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
'8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
[0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/,
[0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP,
[0xC9] KEY_PGUP, [0xCB] KEY_LF,
[0xCD] KEY_RT, [0xCF] KEY_END,
[0xD0] KEY_DN, [0xD1] KEY_PGDN,
[0xD2] KEY_INS, [0xD3] KEY_DEL
};
// 当按下shift键时,按键码对应字符的asiic码,如 0x2 => '!'
static uint8_t shiftmap[256] =
{
NO, 033, '!', '@', '#', '$', '%', '^', // 0x00
'&', '*', '(', ')', '_', '+', '\b', '\t',
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10
'O', 'P', '{', '}', '\n', NO, 'A', 'S',
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20
'"', '~', NO, '|', 'Z', 'X', 'C', 'V',
'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30
NO, ' ', NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
'8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
[0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/,
[0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP,
[0xC9] KEY_PGUP, [0xCB] KEY_LF,
[0xCD] KEY_RT, [0xCF] KEY_END,
[0xD0] KEY_DN, [0xD1] KEY_PGDN,
[0xD2] KEY_INS, [0xD3] KEY_DEL
};
#define C(x) (x - '@')
static uint8_t ctlmap[256] =
{
NO, NO, NO, NO, NO, NO, NO, NO,
NO, NO, NO, NO, NO, NO, NO, NO,
C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'),
C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'),
C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO,
NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'),
C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO,
[0x97] KEY_HOME,
[0xB5] C('/'), [0xC8] KEY_UP,
[0xC9] KEY_PGUP, [0xCB] KEY_LF,
[0xCD] KEY_RT, [0xCF] KEY_END,
[0xD0] KEY_DN, [0xD1] KEY_PGDN,
[0xD2] KEY_INS, [0xD3] KEY_DEL
};
static uint8_t *charcode[4] = {
normalmap,
shiftmap,
ctlmap,
ctlmap
};
/*
* Get data from the keyboard. If we finish a character, return it. Else 0.
* Return -1 if no data.
*/
static int
kbd_proc_data(void)
{
int c;
uint8_t data;
static uint32_t shift; // 数据存在bss段中,这部分数据初始化为0,下次调用该函数时shift里的值还保存着
// 读键盘控制状态寄存器0x64,如果第一位为1则表示有数据;KBSTATP,KBS_DIB在inc/kbdreg.h中定义
if ((inb(KBSTATP) & KBS_DIB) == 0)
return -1;
//读键盘数据寄存器0x60
data = inb(KBDATAP);
/* 如果想知道键被按下和被释放时的键盘扫描码,可以加上一条输出语句输出码值,很直观!
cprintf(" %02x ",data);
*/
if (data == 0xE0) {
// E0 escape character,扩展按键,扫描码为2字节;E0后面还跟有一个字节值
shift |= E0ESC;
return 0;
} else if (data & 0x80) {
// Key released,除去E0,最高位为1(+0x80)的,应该是键释放的扫描码
// 如果该扫描码是E0 escape的,那么保持值不变;否则清除最高位的1,其值
// 变为建按下是的码值。这么做的目的就是为了处理左右ctrl,alt和shift键,
// 即在建释放时清除其shiftcode值,因为它们的shiftcode值会影响其他字符
// 的解析
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC); // 清除之前的shiftcode,表示
// 释放了ctrl,alt或者shift
// 如果该扫描码是E0 escape的
// 清除E0 escape标记
return 0;
} else if (shift & E0ESC) {
// Last character was an E0 escape; or with 0x80
// 上一个字节值为E0,那么该字节应该是键被按下时扫描码的第二字节,
// 让其与上0x80,用以后面处理时区分单字节扫描码
data |= 0x80;
shift &= ~E0ESC; // 清除E0 escape
}
shift |= shiftcode[data]; // 键被按下时,置ctrl,alt和shift键被按下的状态
shift ^= togglecode[data]; // CAPSLOCK,NUMLOCK,SCROLLLOCK这三个键没按一次,
// 它们的状态就改变(0,1变化),这里用异或巧妙的实现
// 它们的状态也会影响后面按键字符的解析
// static uint8_t *charcode[4] = {normalmap,shiftmap,ctlmap,ctlmap};
// CTL = 1 (01); SHIFT = 2 (10). CTL | SHIFT = 3 (11)
// CTL和SHIFT二者组合值为0~3, 对应charcode里面的四个不同字符值map
c = charcode[shift & (CTL | SHIFT)][data];
if (shift & CAPSLOCK) { // CAPSLOCK 影响字母字符解析
if ('a' <= c && c <= 'z') // 小写->大写
c += 'A' - 'a';
else if ('A' <= c && c <= 'Z') // 大写(shift键按下时)->小写
c += 'a' - 'A';
}
// Process special keys
// Ctrl-Alt-Del: reboot
if (!(~shift & (CTL | ALT)) && c == KEY_DEL) {
cprintf("Rebooting!\n");
outb(0x92, 0x3); // courtesy of Chris Frost
// PS/2 POS, 0092: bit 1 = 1 indicates A20 active
// bit 0 = 0 system reset or write
// 1 pulse alternate reset pin (alternate CPU reset)
}
return c;
}
// return the next input character from the console, or 0 if none waitingint
cons_getc(void)
{
int c;
// poll for any pending input characters,
// so that this function works even when interrupts are disabled
// (e.g., when called from the kernel monitor).
serial_intr(); // 输入又可能来自串口或者键盘, xxx_intr设置终端响应函数
kbd_intr(); // 调用cons_intr(int (*proc)(void)),proc为各自的接口处理函数
// 将输入保存到cons.buf内
// grab the next character from the input buffer.
if (cons.rpos != cons.wpos) { // buffer内有输入;使用数组作为循环buffer
c = cons.buf[cons.rpos++];
if (cons.rpos == CONSBUFSIZE) // 循环buffer
cons.rpos = 0;
return c;
}
return 0;
}
// output a character to the consolevoid
cons_putc(int c)
{
lpt_putc(c); // 输出到parallel printer port,打印机
cga_putc(c); // 输出到Color Graphics Adapter,显示器
}