参考《linux内核完全注释》和网上相关文章
/*
* 控制台显示操作
*/
/*
* linux/kernel/console.c
*
* (C) 1991 Linus Torvalds
*/
/*
* console.c
*
* This module implements the console io functions
* 'void con_init(void)'
* 'void con_write(struct tty_queue * queue)'
* Hopefully this will be a rather complete VT102 implementation.
*
* Beeping thanks to John T Kohl.
*/
/**********************
* con_init con_write *
**********************/
/*
* NOTE!!! We sometimes disable and enable interrupts for a short while
* (to put a word in video IO), but this will work even for keyboard
* interrupts. We know interrupts aren't enabled when getting a keyboard
* interrupt, as we use trap-gates. Hopefully all is well.
*/
/*
* Code to check for different video-cards mostly by Galen Hunt,
*/
#include <linux/sched.h>
#include <linux/tty.h> // 定义tty_struct结构,串行通信方面的参数和常量
#include <asm/io.h> // 硬件对应的汇编io指令
#include <asm/system.h>
/*
* These are set up by the setup-routine at boot-time:
*/
#define ORIG_X (*(unsigned char *)0x90000) // 光标列号
#define ORIG_Y (*(unsigned char *)0x90001) // 光标行号
#define ORIG_VIDEO_PAGE (*(unsigned short *)0x90004) // 显示页面
#define ORIG_VIDEO_MODE ((*(unsigned short *)0x90006) & 0xff) // 显示模式
#define ORIG_VIDEO_COLS (((*(unsigned short *)0x90006) & 0xff00) >> 8) // 字符列数
#define ORIG_VIDEO_LINES (25) // 字符行数
#define ORIG_VIDEO_EGA_AX (*(unsigned short *)0x90008) // ??
#define ORIG_VIDEO_EGA_BX (*(unsigned short *)0x9000a) // 显示内存大小和色彩模式
#define ORIG_VIDEO_EGA_CX (*(unsigned short *)0x9000c) // 显示卡特性参数
#define VIDEO_TYPE_MDA 0x10 /* Monochrome Text Display,单色文本 */
#define VIDEO_TYPE_CGA 0x11 /* CGA Display,CGA 显示器 */
#define VIDEO_TYPE_EGAM 0x20 /* EGA/VGA in Monochrome Mode,EGA/VGA 单色 */
#define VIDEO_TYPE_EGAC 0x21 /* EGA/VGA in Color Mode,EGA/VGA 彩色 */
#define NPAR 16
extern void keyboard_interrupt(void); // 键盘中断处理程序
/* 全局变量,指示显示器的相关信息 */
static unsigned char video_type; /* Type of display being used */
static unsigned long video_num_columns; /* Number of text columns */
static unsigned long video_size_row; /* Bytes per row,每行使用的字节数 */
static unsigned long video_num_lines; /* Number of test lines,屏幕文本行数 */
static unsigned char video_page; /* Initial video page,初始显示页面 */
static unsigned long video_mem_start; /* Start of video RAM */
static unsigned long video_mem_end; /* End of video RAM (sort of) */
static unsigned short video_port_reg; /* Video register select port */
/* 显示控制索引寄存器端口 */
static unsigned short video_port_val; /* Video register value port */
static unsigned short video_erase_char; /* Char+Attrib to erase with,擦除字符属性与字符 */
/* 下面的全局变量是屏幕卷屏操作所使用 */
static unsigned long origin; /* Used for EGA/VGA fast scroll */ // 滚屏起始内存地址
static unsigned long scr_end; /* Used for EGA/VGA fast scroll */ // 滚屏末端内存地址
static unsigned long pos; // 当前光标位置相对于显存的位置
static unsigned long x,y; // 当前光标
static unsigned long top,bottom; // 滚动时顶行行号和底行行号
/**************************************************************/
static unsigned long state=0; // 处理ESC 转义序列时的当前进行到哪
static unsigned long npar,par[NPAR]; // 放ESC 序列的中间处理参数
/************************************************************/
static unsigned long ques=0;
static unsigned char attr=0x07; // char
static void sysbeep(void);
/*
* this is what the terminal answers to a ESC-Z or csi0c
* query (= vt100 response).
*/
#define RESPONSE "\033[?1;2c"
/* NOTE! gotoxy thinks x==video_num_columns is ok */
/* 更新当前光标位置 */
/*
* |----------------------------------------------------------> x
* | video_size_row
* |--------------------------------------| -|------> top顶行行号
* |--------------------------------------| |
* | | |
* | * * * | | --> video_num_row
* P(x, y) |
* |--------------------------------------| -|------> button底行行号
* |--------------------------------------|
* | |
* | video_num_columns
* |
* | y
*
* all video memory = video_size_row * video_num_row
* the screen point P(x, y) is mapping the main memory
* video_mem_start + y * video_size_row + (x << 1)
*/
static inline void gotoxy(unsigned int new_x,unsigned int new_y)
{
if (new_x > video_num_columns || new_y >= video_num_lines)
return; // 错误检查
x=new_x; // 更新x,y
y=new_y;
pos=origin + y*video_size_row + (x<<1); // 重新计算全局变量pos值
}
/* 设置滚屏起始显示内存地址 */
static inline void set_origin(void)
{
cli();
outb_p(12, video_port_reg); // 向择显示控制数据寄存器r12写入卷屏起始地址高字节
outb_p(0xff&((origin-video_mem_start)>>9), video_port_val);
outb_p(13, video_port_reg); // 向显示控制数据寄存器r13写入卷屏起始地址底字节
outb_p(0xff&((origin-video_mem_start)>>1), video_port_val);
sti();
}
/* 向上卷动一行 */
static void scrup(void)
{
/*
* 我们可以认为的是显存就是一片内存区域,显示器在决定显示部分时,根据的是
* 显示控制器的的内存起始位置值。但是在实际的操作系统中,显存是固定在一个
* 区域内的。
*
* 将屏幕看作是显示内存上对应屏幕内容的一个窗口
*/
/* EGA/VGA */
if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
{
// 如果移动起始行top=0,移动最底行bottom=video_num_lines=25,则
// 表示整屏窗口向下移动
if (!top && bottom == video_num_lines)
{
// 更新数据
origin += video_size_row;
pos += video_size_row;
scr_end += video_size_row;
// 如果屏幕末端最后一个显示字符所对应的显示内存指针scr_end
// 超出了实际显示内存的末端。保证所有当前屏幕数据都落在显示
// 内存范围内
if (scr_end > video_mem_end)
{
// 屏幕内容内存数据移动到显示内存的起始位置video_mem_start
// 处,并在出现的新行上填入空格字符
__asm__("cld\n\t"
"rep\n\t"
"movsl\n\t" // 移动到显示内存起始处
"movl _video_num_columns,%1\n\t"
"rep\n\t" // 在新行上填入空格字符
"stosw"
::"a" (video_erase_char),
"c" ((video_num_lines-1)*video_num_columns>>1),
"D" (video_mem_start),
"S" (origin)
:"cx","di","si");
// 更新数据
scr_end -= origin-video_mem_start;
pos -= origin-video_mem_start;
origin = video_mem_start;
}
else // scr_end 没有超出显示内存的末端video_mem_end
{
// 新行上填入擦除字符(空格字符)
__asm__("cld\n\t"
"rep\n\t"
"stosw"
::"a" (video_erase_char),
"c" (video_num_columns),
"D" (scr_end-video_size_row)
:"cx","di");
}
set_origin(); // 向显示控制器中写入新的屏幕内容对应的内存起始位置值
}
else // 表示不是整屏移动
{
__asm__("cld\n\t"
"rep\n\t"
"movsl\n\t"
"movl _video_num_columns,%%ecx\n\t"
"rep\n\t"
"stosw"
::"a" (video_erase_char),
"c" ((bottom-top-1)*video_num_columns>>1),
"D" (origin+video_size_row*top),
"S" (origin+video_size_row*(top+1))
:"cx","di","si");
}
}
else /* Not EGA/VGA */
{
// 因为MDA 显示控制卡会自动调整超出显示范围的情况,也即会自动
// 翻卷指针,所以这里不对屏幕内容对应内存超出显示内存的情况单独处理
__asm__("cld\n\t"
"rep\n\t"
"movsl\n\t"
"movl _video_num_columns,%%ecx\n\t"
"rep\n\t"
"stosw"
::"a" (video_erase_char),
"c" ((bottom-top-1)*video_num_columns>>1),
"D" (origin+video_size_row*top),
"S" (origin+video_size_row*(top+1))
:"cx","di","si");
}
}
/* 向下卷动一行,该函数只是根据top和button来实现的卷屏 */
static void scrdown(void)
{
if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
{
__asm__("std\n\t" // 置方向位
"rep\n\t" // 重复操作,向下移动从top 行到bottom-1 行
"movsl\n\t" // 对应的内存数据
"addl $2,%%edi\n\t" /* %edi has been decremented by 4 */
"movl _video_num_columns,%%ecx\n\t" // 置ecx=1 行字符数
"rep\n\t" // 将擦除字符填入上方新行中
"stosw"
::"a" (video_erase_char),
"c" ((bottom-top-1)*video_num_columns>>1),
"D" (origin+video_size_row*bottom-4),
"S" (origin+video_size_row*(bottom-1)-4)
:"ax","cx","di","si");
}
else /* Not EGA/VGA */
{
__asm__("std\n\t"
"rep\n\t"
"movsl\n\t"
"addl $2,%%edi\n\t" /* %edi has been decremented by 4 */
"movl _video_num_columns,%%ecx\n\t"
"rep\n\t"
"stosw"
::"a" (video_erase_char),
"c" ((bottom-top-1)*video_num_columns>>1),
"D" (origin+video_size_row*bottom-4),
"S" (origin+video_size_row*(bottom-1)-4)
:"ax","cx","di","si");
}
}
/* 光标位置下移一行 */
static void lf(void)
{
if (y+1<bottom)
{
y++;
pos += video_size_row;
return;
}
scrup(); // 函数还没有退出,将屏幕内容上移一行
}
/* 光标上移一行,同上lf */
static void ri(void)
{
if (y>top) {
y--;
pos -= video_size_row;
return;
}
scrdown();
}
/* 光标回到第1 列(0 列)左端 */
static void cr(void)
{
pos -= x<<1; // x / 2
x=0;
}
/* 擦除光标前一字符 */
static void del(void)
{
if (x) //如果光标没有处在0 列
{
pos -= 2; // 光标对应内存位置指针pos 后退2 字节
x--; // 当前光标变量列值减1
*(unsigned short *)pos = video_erase_char; // 光标所在位置字符擦除
}
}
/* 删除屏幕上的相关区域 */
static void csi_J(int par)
{
long count __asm__("cx"); // // 设为寄存器变量
long start __asm__("di");
switch (par)
{
case 0: /* erase from cursor to end of display */
/* 擦除光标到屏幕底端 */
count = (scr_end-pos)>>1;
start = pos;
break;
case 1: /* erase from start to cursor */
/* 删除从屏幕开始到光标处的字符 */
count = (pos-origin)>>1;
start = origin;
break;
case 2: /* erase whole display */
/* 删除整个屏幕上的字符 */
count = video_num_columns * video_num_lines;
start = origin;
break;
default:
return;
}
// 使用擦除字符填写删除字符的地方
// %0 - ecx(要删除的字符数count);%1 - edi(删除操作开始地址);
// %2 - eax(填入的擦除字符)
__asm__("cld\n\t"
"rep\n\t"
"stosw\n\t"
::"c" (count),
"D" (start),"a" (video_erase_char)
:"cx","di");
}
/* 根据par的不同,删除与行相关的字符,并填充其他字符 */
static void csi_K(int par)
{
long count __asm__("cx"); // 设置寄存器变量
long start __asm__("di");
/* 计算count和start的值,以便在下面实现字符的擦除 */
switch (par)
{
case 0: /* erase from cursor to end of line */
/* 删除光标到行尾字符 */
if (x>=video_num_columns)
return;
count = video_num_columns-x;
start = pos;
break;
case 1: /* erase from start of line to cursor */
/* 删除从行开始到光标处 */
start = pos - (x<<1);
count = (x<video_num_columns)?x:video_num_columns;
break;
case 2: /* erase whole line */
start = pos - (x<<1);
count = video_num_columns;
break;
default:
return;
}
// 使用擦除字符填写删除字符的地方
__asm__("cld\n\t"
"rep\n\t"
"stosw\n\t"
::"c" (count),
"D" (start),"a" (video_erase_char)
:"cx","di");
}
/* 重新设置字符显示方式 */
void csi_m(void)
{
int i;
for (i=0;i<=npar;i++)
switch (par[i])
{
case 0:attr=0x07;break; // n = 0 正常显示
case 1:attr=0x0f;break; // 1 加粗
case 4:attr=0x0f;break; // 4 加下划线
case 7:attr=0x70;break; // 7 反显
case 27:attr=0x07;break; // 27 正常显示
}
}
/* 根据显示内存光标对应位置pos,设置显示控制器光标的显示位置 */
static inline void set_cursor(void)
{
cli();
// 使用索引寄存器端口选择显示控制数据寄存器r14(光标当前显示位置高字节)
outb_p(14, video_port_reg);
// 当前位置高字节
outb_p(0xff&((pos-video_mem_start)>>9), video_port_val);
// 再使用索引寄存器选择r15
outb_p(15, video_port_reg);
// 光标当前位置低字节写入其中
outb_p(0xff&((pos-video_mem_start)>>1), video_port_val);
sti();
}
/* ,终端相应外部命令,将响应序列放入读缓冲队列中 */
/*
* console (tty同时对应screen)
*
* |---struct tty_queue read_q
* console --> tty_struct -|
* |---struct tty_queue write_q
*
*/
static void respond(struct tty_struct * tty)
{
char * p = RESPONSE; // "\033[?1;2c"
cli();
while (*p)
{
PUTCH(*p,tty->read_q);
p++;
}
sti();
copy_to_cooked(tty); // 转换成规范模式(放入辅助队列中)
}
/* 在光标处插入一空格字符 */
static void insert_char(void)
{
int i=x;
unsigned short tmp, old = video_erase_char;
unsigned short * p = (unsigned short *) pos;
/*
* |----------|-|----------------------------|
* |-----swap---->|
* 光标开始的所有字符右移一格,并将擦除字符插入在光标所在处
*/
while (i++<video_num_columns)
{
tmp=*p;
*p=old;
old=tmp;
p++;
}
}
/* 在光标处插入一行,并且光标将处在新的空行上 */
static void insert_line(void)
{
int oldtop,oldbottom;
oldtop=top;
oldbottom=bottom;
top=y; // 设置屏幕卷动开始行
bottom = video_num_lines; // 设置屏幕卷动最后行
scrdown();
top=oldtop;
bottom=oldbottom;
}
/* 删除光标处的一个字符 */
static void delete_char(void)
{
int i;
unsigned short * p = (unsigned short *) pos;
if (x>=video_num_columns) // 如果光标超出屏幕最右列
return;
i = x;
while (++i < video_num_columns) // 从光标右一个字符开始到行末所有字符左移一格
{
*p = *(p+1);
p++;
}
*p = video_erase_char; // 最后一个字符处填入擦除字符(空格字符)
}
/* 删除光标所在行,同insert_line */
static void delete_line(void)
{
int oldtop,oldbottom;
oldtop=top;
oldbottom=bottom;
top=y;
bottom = video_num_lines;
scrup();
top=oldtop;
bottom=oldbottom;
}
/* 在光标处插入nr 个字符 */
static void csi_at(unsigned int nr)
{
if (nr > video_num_columns) // 如果插入的字符数大于一行字符数
nr = video_num_columns; // 截为一行字符数
else if (!nr) // nr == 0 ?
nr = 1; // nr = 1,插入1 个字符
while (nr--) // 循环插入指定的字符数
insert_char();
}
/* 在光标位置处插入nr 行 */
static void csi_L(unsigned int nr)
{
if (nr > video_num_lines)
nr = video_num_lines;
else if (!nr)
nr = 1;
while (nr--)
insert_line();
}
/* 删除光标处的nr 个字符,调用函数delete_char */
static void csi_P(unsigned int nr)
{
if (nr > video_num_columns)
nr = video_num_columns;
else if (!nr)
nr = 1;
while (nr--)
delete_char();
}
/* 删除光标处的nr 行 */
static void csi_M(unsigned int nr)
{
if (nr > video_num_lines)
nr = video_num_lines;
else if (!nr)
nr=1;
while (nr--)
delete_line();
}
static int saved_x=0; // 保存光标列号
static int saved_y=0; // 保存光标行号
static void save_cur(void)
{
saved_x=x;
saved_y=y;
}
static void restore_cur(void)
{
gotoxy(saved_x, saved_y);
}
/* 从终端对应的tty 写缓冲队列中取字符,并显示在屏幕上 */
void con_write(struct tty_struct * tty)
{
int nr;
char c;
nr = CHARS(tty->write_q); // 首先取得写缓冲队列中现有字符数nr
while (nr--) // 针对每个字符进行处理
{
// 从写队列中取一字符c,根据前面所处理字符的状态state 分别处理
GETCH(tty->write_q,c);
switch(state)
{
case 0:
if (c>31 && c<127)
{
// 符不是控制字符(c>31),并且也不是扩展字符(c<127)
if (x>=video_num_columns)
{
// 若当前光标处在行末端或末端以外,则将光标移到下行头列。
// 并调整光标位置对应的内存指针pos
x -= video_num_columns;
pos -= video_size_row;
lf();
}
// 将字符c 写到显示内存中pos 处,并将光标右移1 列,
// 同时也将pos 对应地移动2 个字节。
__asm__("movb _attr,%%ah\n\t"
"movw %%ax,%1\n\t"
::"a" (c),"m" (*(short *)pos)
:"ax");
pos += 2;
x++;
}
// // 如果字符c 是转义字符ESC,则转换状态state 到1
else if (c==27)
state=1;
// 如果字符c 是换行符(10),或是垂直制表符VT(11),或者是
// 换页符FF(12),则移动光标到下一行
else if (c==10 || c==11 || c==12)
lf();
// 如果字符c 是回车符CR(13),则将光标移动到头列(0 列)
else if (c==13)
cr();
// 如果字符c 是DEL(127),则将光标右边一字符擦除(用空格
// 字符替代),并将光标移到被擦除位置
else if (c==ERASE_CHAR(tty))
del();
// 如果字符c 是BS(backspace,8),则将光标右移1 格,并相应
// 调整光标对应内存位置指针pos
else if (c==8)
{
if (x)
{
x--;
pos -= 2;
}
}
// 如果字符c 是水平制表符TAB(9),则将光标移到8 的倍数列上
else if (c==9)
{
c=8-(x&7);
x += c;
pos += c<<1;
if (x>video_num_columns)
{
// 若此时光标列数超出屏幕最大列数,
// 则将光标移到下一行上
x -= video_num_columns;
pos -= video_size_row;
lf();
}
c=9;
}
// 如果字符c 是响铃符BEL(7),则调用蜂鸣函数,是扬声器发声
else if (c==7)
sysbeep();
break;
// 如果原状态是0,并且字符是转义字符ESC(0x1b = 033 = 27),则转到状态1 处理
case 1:
state=0;
if (c=='[') // 如果字符c 是'[',则将状态state 转到2
state=2;
else if (c=='E') // 如果字符c 是'E',则光标移到下一行开始处(0 列)
gotoxy(0,y+1);
else if (c=='M') // 如果字符c 是'M',则光标上移一行
ri();
else if (c=='D') // 如果字符c 是'D',则光标下移一行
lf();
else if (c=='Z') // 如果字符c 是'Z',则发送终端应答字符序列
respond(tty);
else if (x=='7') // 如果字符c 是'7',则保存当前光标位置
save_cur();
else if (x=='8') // 如果字符c 是'8',则恢复到原保存的光标位置
restore_cur();
break;
// 如果原状态是1,并且上一字符是'[',则转到状态2 来处理
case 2:
// 首先对ESC 转义字符序列参数使用的处理数组par[]清零
for(npar=0;npar<NPAR;npar++)
par[npar]=0;
npar=0; // 索引变量npar 指向首项
state=3; // 设置状态3
if (ques=(c=='?')) // 若此时字符不是'?',则直接转到状态3 去处理
break;
// 如果原来是状态2;或者原来就是状态3,但原字符是';'或数字,则在下面处理
case 3:
if (c==';' && npar<NPAR-1)
{ // 如果字符c 是分号';',并且数组par 未满,则索引值加1
npar++;
break;
}
else if (c>='0' && c<='9')
{
// 如果字符c 是数字字符'0'-'9',则将该字符转换成数值并与npar
// 所索引的项组成10 进制数
par[npar]=10*par[npar]+c-'0';
break;
}
else // 否则转到状态4
state=4;
// 如果原状态是状态3,并且字符不是';'或数字,则转到状态4 处理
case 4:
state=0; // 首先复位状态state=0
switch(c)
{
// 如果字符c 是'G'或'`',则par[]中第一个参数代表列号。若列
// 号不为零,则将光标右移一格
case 'G': case '`':
if (par[0]) par[0]--;
gotoxy(par[0],y);
break;
// 如果字符c 是'A',则第一个参数代表光标上移的行数。若参数
// 为0 则上移一行
case 'A':
if (!par[0]) par[0]++;
gotoxy(x,y-par[0]);
break;
// 如果字符c 是'B'或'e',则第一个参数代表光标下移的行数。若
// 参数为0 则下移一行
case 'B': case 'e':
if (!par[0]) par[0]++;
gotoxy(x,y+par[0]);
break;
// 如果字符c 是'C'或'a',则第一个参数代表光标右移的格数。若
// 参数为0 则右移一格
case 'C': case 'a':
if (!par[0]) par[0]++;
gotoxy(x+par[0],y);
break;
// 如果字符c 是'D',则第一个参数代表光标左移的格数。若参数为
// 0 则左移一格
case 'D':
if (!par[0]) par[0]++;
gotoxy(x-par[0],y);
break;
// 如果字符c 是'E',则第一个参数代表光标向下移动的行数,并回
// 到0 列。若参数为0 则下移一行
case 'E':
if (!par[0]) par[0]++;
gotoxy(0,y+par[0]);
break;
// 如果字符c 是'F',则第一个参数代表光标向上移动的行数,并回
// 到0 列。若参数为0 则上移一行
case 'F':
if (!par[0]) par[0]++;
gotoxy(0,y-par[0]);
break;
// 如果字符c 是'd',则第一个参数代表光标所需在的行号(从0 计数)
case 'd':
if (par[0]) par[0]--;
gotoxy(x,par[0]);
break;
// 如果字符c 是'H'或'f',则第一个参数代表光标移到的行号,第二
// 个参数代表光标移到的列号
case 'H': case 'f':
if (par[0]) par[0]--;
if (par[1]) par[1]--;
gotoxy(par[1],par[0]);
break;
// 如果字符c 是'J',则第一个参数代表以光标所处位置清屏的方式
case 'J':
csi_J(par[0]);
break;
// 如果字符c 是'K',则第一个参数代表以光标所在位置对行中字符进行
// 删除处理的方式
case 'K':
csi_K(par[0]);
break;
// 如果字符c 是'L',表示在光标位置处插入n 行
case 'L':
csi_L(par[0]);
break;
// 如果字符c 是'M',表示在光标位置处删除n 行
case 'M':
csi_M(par[0]);
break;
// 如果字符c 是'P',表示在光标位置处删除n 个字符
case 'P':
csi_P(par[0]);
break;
// 如果字符c 是'@',表示在光标位置处插入n 个字符
case '@':
csi_at(par[0]);
break;
// 如果字符c 是'm',表示改变光标处字符的显示属性
case 'm':
csi_m();
break;
// 如果字符c 是'r',则表示用两个参数设置滚屏的起始行号和终止行号
case 'r':
if (par[0]) par[0]--;
if (!par[1]) par[1] = video_num_lines;
if (par[0] < par[1] &&
par[1] <= video_num_lines)
{
top=par[0];
bottom=par[1];
}
break;
// 如果字符c 是's',则表示保存当前光标所在位置
case 's':
save_cur();
break;
// 如果字符c 是'u',则表示恢复光标到原保存的位置处
case 'u':
restore_cur();
break;
}
}
}
// 最后根据上面设置的光标位置,向显示控制器发送光标显示位置
set_cursor();
}
/*
* void con_init(void);
*
* This routine initalizes console interrupts, and does nothing
* else. If you want the screen to clear, call tty_write with
* the appropriate escape-sequece.
*
* Reads the information preserved by setup.s to determine the current display
* type and sets everything accordingly.
*/
/*
* 读取setup.s 程序保存的信息,用以确定当前显示器类型,并且设置所有相关参数
*/
void con_init(void)
{
register unsigned char a;
char *display_desc = "????";
char *display_ptr;
video_num_columns = ORIG_VIDEO_COLS; // 显示器显示字符列数
video_size_row = video_num_columns * 2; // 每行需使用字节数
video_num_lines = ORIG_VIDEO_LINES; // 显示器显示字符行数
video_page = ORIG_VIDEO_PAGE; // 当前显示页面
video_erase_char = 0x0720; // 擦除字符(0x20 显示字符, 0x07 是属性)
// 是单色显示器?
if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */
{
video_mem_start = 0xb0000; // 设置单显映象内存起始地址
video_port_reg = 0x3b4; // 设置单显索引寄存器端口
video_port_val = 0x3b5; // 设置单显数据寄存器端口
/*
* 根据BIOS 中断int 0x10 功能0x12 获得的显示模式信息,判断
* 显示卡单色显示卡还是彩色显示卡
*/
if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
{
video_type = VIDEO_TYPE_EGAM; // 设置显示类型(EGA 单色)
video_mem_end = 0xb8000; // 设置显示内存末端地址
display_desc = "EGAm"; // 设置显示描述字符串
}
else
{
video_type = VIDEO_TYPE_MDA; // 设置显示类型(MDA 单色)
video_mem_end = 0xb2000; // 设置显示内存末端地址
display_desc = "*MDA"; // 设置显示描述字符串
}
}
else /* If not, it is color. */
{
video_mem_start = 0xb8000; // 显示内存起始地址
video_port_reg = 0x3d4; // 设置彩色显示索引寄存器端口
video_port_val = 0x3d5; // 设置彩色显示数据寄存器端口
// 再判断显示卡类别
if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
{
video_type = VIDEO_TYPE_EGAC;
video_mem_end = 0xbc000;
display_desc = "EGAc";
}
else
{
video_type = VIDEO_TYPE_CGA;
video_mem_end = 0xba000;
display_desc = "*CGA";
}
}
/* Let the user known what kind of display driver we are using */
/* 在屏幕的右上角显示显示描述字符串。采用的方法是直接将字符串写到显示内存的相应位置处 */
display_ptr = ((char *)video_mem_start) + video_size_row - 8;
while (*display_desc)
{
*display_ptr++ = *display_desc++; // 复制字符
display_ptr++; // 空开属性字节位置
}
/* Initialize the variables used for scrolling (mostly EGA/VGA) */
origin = video_mem_start; // 滚屏起始显示内存地址
scr_end = video_mem_start + video_num_lines * video_size_row; // // 滚屏结束内存地址
top = 0; // 最顶行号
bottom = video_num_lines; // 最底行号
gotoxy(ORIG_X,ORIG_Y); // 初始化光标位置x,y 和对应的内存位置pos
set_trap_gate(0x21,&keyboard_interrupt); // 设置键盘中断陷阱门
outb_p(inb_p(0x21)&0xfd,0x21); // // 取消8259A 中对键盘中断的屏蔽
a=inb_p(0x61); // // 延迟读取键盘端口0x61
outb_p(a|0x80,0x61); // 设置禁止键盘工作
outb(a,0x61); // 再允许键盘工作,用以复位键盘操作
}
/* from bsd-net-2: */
/* 停止蜂鸣 */
void sysbeepstop(void)
{
/* disable counter 2 */
outb(inb_p(0x61)&0xFC, 0x61);
}
int beepcount = 0;
/* 开通蜂鸣 */
static void sysbeep(void)
{
/* enable counter 2 */
/* 开启定时器2 */
outb_p(inb_p(0x61)|3, 0x61);
/* set command for counter 2, 2 byte write */
/* 送设置定时器2 命令 */
outb_p(0xB6, 0x43);
/* send 0x637 for 750 HZ */
/* 设置频率为750HZ,因此送定时值0x637 */
outb_p(0x37, 0x42);
outb(0x06, 0x42);
/* 1/8 second */
beepcount = HZ/8;
}