linux0.11字符设备驱动及访问请求管理程序阅读注释笔记
时间:2023-10-01 16:07:02
[ 1] linux0.11指导程序阅读注释。
[ 2] linux0.11实际模式进入保护模式程序阅读注释 。
[ 3] linux0.初始化程序阅读注释11护理模式。
[ 4] linux0.阅读注释11主存管理程序。
[ 5] linux0.相关程序阅读注释初始设置为中断/异常机制。
[ 6] linux0.阅读缓冲区管理程序注释。
[ 7] linux0.阅读注释文件系统管理程序。
[ 8] linux0.阅读注释11个设备驱动和访问请求管理程序。
通过浏览器的搜索功能,篇幅较长(Ctrl f)了解相应函数的实现机制,如搜索函数名 tty_read。
[9] linux0.阅读注释
回顾[5]中粗略制定的阅读计划——他确实有一些不合理之处。然而,本文坚持克服这些不合理之处。这篇文章将再坚持一段时间。
/* 此外,学习字符设备管理程序后,可以粗略了解计算机(
键盘)数据输入流向。 * |------------| * | terminal | * |------------| * |UART|network| * |------------| * ^ * |将其发送到串口/网口控制器 * v * |------| * | CPU | to echo |----------| |-------| * |======| ------> |video card|-->|monitor| * |QUEUES| |----------| |-------| * |======| * ^ * |I/O指令和中断机制 * V * |--------------------------| * |keyboard && its controller| * |--------------------------| */
main.c
/* head.s保护模式初始化完成后, * 便跳转执行C程序入口处指令。*/ void main(void) { /* ... */ /* 字符设备(串口&&键盘&&显卡等相关)初始化。*/ chr_dev_init(); tty_init(); /* ... */ }
tty_io.c
/* * linux/kernel/tty_io.c * * (C) 1991 Linus Torvalds */ /* * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles * or rs-channels. It also implements echoing, cooked mode etc. * * Kill-line thanks to John T Kohl. */ #include #include #include #define ALRMMASK (1<<(SIGALRM-1)) #define KILLMASK (1<<(SIGKILL-1)) #define INTMASK (1<<(SIGINT-1)) #define QUITMASK (1<<(SIGQUIT-1)) #define TSTPMASK (1<<(SIGTSTP-1)) #include #include #include #include /* 判断字符设备本地模式,输入模式, * 标志是否设置在输出模式中?f。 * * 表达式值为1时,表示已设置标志f。*/ #define _L_FLAG(tty,f) ((tty)->termios.c_lflag & f) #define _I_FLAG(tty,f) ((tty)->termios.c_iflag & f) #define _O_FLAG(tty,f) ((tty)->termios.c_oflag & f) /* 判断字符设备本地模式是否设置。 * 标准化标志,生成过程信号标志,返回标志, * 标准模式下的擦除标志,标准模式下的删除行标志, * 回显控制字符标志,回显删除行标志。 * * 表达值为1,表示已设置。*/ #define L_CANON(tty) _L_FLAG((tty),ICANON) #define L_ISIG(tty) _L_FLAG((tty),ISIG) #define L_ECHO(tty) _L_FLAG((tty),ECHO) #define L_ECHOE(tty) _L_FLAG((tty),ECHOE) #define L_ECHOK(tty) _L_FLAG((tty),ECHOK) #define L_ECHOCTL(tty) _L_FLAG((tty),ECHOCTL) #define L_ECHOKE(tty) _L_FLAG((tty),ECHOKE) /* 判断是否设置了字符设备的输入模式 * 输入字符转大写字符标志,输入换行符转回车辆标志, * 输入回车转换行符标志,忽略回车标志。 * * 表达值为1,表示已设置。*/ #define I_UCLC(tty) _I_FLAG((tty),IUCLC) #define I_NLCR(tty) _I_FLAG((tty),INLCR) #define I_CRNL(tty) _I_FLAG((tty),ICRNL) #define I_NOCR(tty) _I_FLAG((tty),IGNCR) /* 判断是否设置了字符设备的输出模式 * 输出处理标志、换回车标志、转回车标志、, * 遇换行回车处理标志,小写字符转大写字符标志。 * * 表达时返回1时表示已设置。*/ #define O_POST(tty) _O_FLAG((tty),OPOST) #define O_NLCR(tty) _O_FLAG((tty),ONLCR) #define O_CRNL(tty) _O_FLAG((tty),OCRNL) #define O_NLRET(tty) _O_FLAG((tty),ONLRET) #define O_LCUC(tty) _O_FLAG((tty),OLCUC) /* tty_table, * 接收和发送管理字符设备数据的全局数组。 * tty_table[0] - 管理控制台终端(console)接收和发送数据; * tty_table[1] - 管理串口1接收和发送数据; * tty_table[2] - 接收和发送管理串口2的数据。*/ struct tty_struct tty_table[] = { { {ICRNL, /* change incoming CR to NL */ OPOST|ONLCR, /* change outgoing NL to CRNL */ 0, /* 初始化为0的控制模式 */ /* 生成过程信号;规范模式;回显;控制字符回显;删除行时回显; */ ISIG | ICANON | ECHO | ECHOCTL | ECHOKE, 0, /* console termio */ INIT_C_CC}, /* 控制字符序列 */ 0, /* initial pgrp */ 0, /* initial stopped */ con_write, /* 写入数据后的终端写队列回调函数 */ {0,0,0,0,""}, /* console read-queue */ {0,0,0,0,""}, /* console write-queue */ {0,0,0,0,""} /* console secondary queue */ },{ {0, /* no translation */ 0, /* no translation */ B2400 | CS8, /* 波特率=2400,每字符8位 */ 0, /* 本地模式初始化为0 */ 0, /* 线速初始化为0 */ INIT_C_CC}, /* 控制字符序列 */ 0, /* 进程组号初始化为0 */ 0, /* 停止标志初始化为0 */ rs_write, /* 串口1写下队列被写入数据后的回调函数 */ {0x3f8,0,0,0,""}, /* 0x3f8是串口1端口的起始地址,串口1读取队列的初始化 */ {0x3f8,0,0,0,""}, /* 串口1写队列初始化 */ {0,0,0,0,""} /* 串口1辅助队列初始化 */ },{ {0, /* no translation */ 0, /* no translation */ B2400 | CS8, /* 波特率=2400,每字符8位 */ 0, /* 本地模式始化为0 */
0, /* 线路速率初始化为0 */
INIT_C_CC}, /* 控制字符序列 */
0, /* 进程组号初始化为0 */
0, /* 停止标志初始化为0 */
rs_write, /* 串口2写队列被写入数据后的回调函数 */
{0x2f8,0,0,0,""}, /* 0x2f8为串口2端口起始地址,串口2读队列初始化 */
{0x2f8,0,0,0,""}, /* 串口2写队列初始化 */
{0,0,0,0,""} /* 串口2辅助队列初始化 */
}
};
/*
* these are the tables used by the machine code handlers.
* you can implement pseudo-tty's or something by changing
* them. Currently not done.
*/
/* table_list
* -------------------------------------------
* | | | | | | |
* -------------------------------------------
* +0 +4 +8 +12 +16 +20
* table_list + 8(12)为串口1读(写)队列首地址;
* table_list + 16(20)为串口2读(写)队列首地址。*/
struct tty_queue * table_list[]={
&tty_table[0].read_q, &tty_table[0].write_q,
&tty_table[1].read_q, &tty_table[1].write_q,
&tty_table[2].read_q, &tty_table[2].write_q
};
/* tty_init,
* 初始化串口(UART)和控制台终端的通信方式(中断),激活键盘。*/
void tty_init(void)
{
rs_init();
con_init();
}
/* tty_intr,
* 产生mask信号给tty所在进程组的进程。*/
void tty_intr(struct tty_struct * tty, int mask)
{
int i;
if (tty->pgrp <= 0)
return;
for (i=0;ipgrp==tty->pgrp)
task[i]->signal |= mask;
}
/* sleep_if_empty,
* 若当前进程无其他需处理的信号且queue指向的队列为空则进入睡眠。*/
static void sleep_if_empty(struct tty_queue * queue)
{
cli(); /* 本进程睡眠过程中进制CPU处理中断 */
while (!current->signal && EMPTY(*queue))
/* 让本进程进入睡眠直到有其他进程调用
* wake_up(&queue->proc_list)将本进程唤醒。
*
* interruptible_sleep_on跟sleep_on的区别为
* 经interruptible_sleep_on函数睡眠的进程的
* 状态为TASK_INTERRUPTIBLE,可被进程的signal
* 将进程状态设置为TASK_RUNNING即重新运行;而
* 后者只能通过显示设置进程状态为TASK_RUNNING
* 时才能唤醒该进程。*/
interruptible_sleep_on(&queue->proc_list);
sti();
}
/* sleep_if_full,
* 若当前进程无其他需处理的信号且queue所指队列已满则进入睡眠。*/
static void sleep_if_full(struct tty_queue * queue)
{
if (!FULL(*queue))
return;
cli(); /* 睡眠过程中进制CPU处理本进程中断 */
while (!current->signal && LEFT(*queue)<128)
/* 进程无其他信号处理且队列空余数小于128时则睡眠
* 直到被其它进程调用wake_up(&queue->proc_list)将
* 本进程唤醒。interruptible_sleep_on跟sleep_on的
* 区别为经interruptible_sleep_on函数睡眠的进程的
* 状态为TASK_INTERRUPTIBLE,可被进程的signal将进程
* 状态设置为TASK_RUNNING即重新运行;而后者只能通过
* 显示设置进程状态为TASK_RUNNING时才能唤醒该进程。*/
interruptible_sleep_on(&queue->proc_list);
sti();
}
/* wait_for_keypress,
* (让使用控制台终端的进程)等待键盘输入。*/
void wait_for_keypress(void)
{
sleep_if_empty(&tty_table[0].secondary);
}
/* copy_to_cooked,
* 将从字符设备所读字符(转换为规范模式)并存入辅助队列中。
*
* 字符设备控制器接收数据中断-->PIC-->CPU执行串口读中断
* 处理函数(read_char)-->copy_to_cooked。当字符设备读队
* 列中的所有字符被存入辅助队列中后,即可唤醒等待读字符设
* 备辅助队列的进程,以接收到字符设备数据。*/
void copy_to_cooked(struct tty_struct * tty)
{
signed char c;
/* 字符设备读队列不为空且辅助队列非满 */
while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {
/* [1] 从字符设备读队列中读取1个字符 */
GETCH(tty->read_q,c);
/* [2] 若字符为回车且为设备设置了回车转换行标志,则转换字符,
* 若设置了忽略回车字符则将其忽略,继续读取下一个字符;
*
* 若字符为换行且设备设置了换行转回车标志则转换字符;
* 若为设备设置了将字符转换为小写字符标志,则进行转换。*/
if (c==13) /* 回车键 */
if (I_CRNL(tty))
c=10;
else if (I_NOCR(tty))
continue;
else ;
else if (c==10 && I_NLCR(tty))
c=13;
if (I_UCLC(tty))
c=tolower(c);
/* 若为字符设备开启了规范模式标志 */
if (L_CANON(tty)) {
/* 若当前字符为删除行的字符,则作删除当前行的处理 */
if (c==KILL_CHAR(tty)) {
/* deal with killing the input line */
/* 若读取到删除当前行字符后,则删除当前行
* 字符直到遇到上一行回车或文件结束符。若
* 为设备设置了回显标志,则将删除字符写入设
* 备写的队列中(控制字符共2字节,需两个删除
* 字符), 删除辅助队列中1字符时,减少写队列
* 数据头索引。*/
while(!(EMPTY(tty->secondary) ||
(c=LAST(tty->secondary))==10 ||
c==EOF_CHAR(tty))) {
if (L_ECHO(tty)) {
if (c<32)
PUTCH(127,tty->write_q);
PUTCH(127,tty->write_q);
tty->write(tty);
}
DEC(tty->secondary.head);
}
/* 当前行删除后,从读队列中读取下一个字符 */
continue;
}
/* 若当前字符为删除字符, */
if (c==ERASE_CHAR(tty)) {
/* 若辅助队列为空或者辅助队列尾数据为换行字
* 符或为文件结束字符则继续读取下一个字符处理 */
if (EMPTY(tty->secondary) ||
(c=LAST(tty->secondary))==10 ||
c==EOF_CHAR(tty))
continue;
/* 若辅助队列非空数据尾字符非换行符或结束标志则向设备的
* 写队列中写入删除字符,对于控制字符则需写入两个删除字符 */
if (L_ECHO(tty)) {
if (c<32)
PUTCH(127,tty->write_q);
PUTCH(127,tty->write_q);
tty->write(tty);
}
/* 删除辅助队列中数据头字符后减少辅助队列数据头索引,
* 并继续从设备读队列中读取下一字符以处理。*/
DEC(tty->secondary.head);
continue;
}
/* 若读取字符为停止控制字符则置位tty停止输出
* 标志并继续处理读队列中的下1字符 */
if (c==STOP_CHAR(tty)) {
tty->stopped=1;
continue;
}
/* 若读取字符为开始控制字符则复位tty停止输出
* 标志并继续处理读队列中的下1字符 */
if (c==START_CHAR(tty)) {
tty->stopped=0;
continue;
}
}
/* 若没有为当前设备设置规范标志,则直接将读队列中的字符存储到到辅助队列中。*/
/* 若为设备置位了ISIG标志,若收到INTR,QUIT,SUSP,DSUSP控制字符时,
* 则向进程输出相应信号。若收到键盘中断控制符(^C)则向当前进程所
* 在进程组中的所有进程发送中断信号。若收到退出符(^\)则向当前进
* 程所在组的所有进程发送退出信号。做完这些处理后继续处理下1字符。*/
if (L_ISIG(tty)) {
if (c==INTR_CHAR(tty)) {
tty_intr(tty,INTMASK);
continue;
}
if (c==QUIT_CHAR(tty)) {
tty_intr(tty,QUITMASK);
continue;
}
}
/* 若当前字符为换行符或文件结束符,
* 表示已处理完一行字符,则将辅助队
* 列成员data增1,以表示往辅助队列中
* 又转换了一行输入。*/
if (c==10 || c==EOF_CHAR(tty))
tty->secondary.data++;
/* 若为当前设备设置了回显标志,则将读入字符写回设备显示。
* 若当前字符为换行符,则往设备回写换行符和回车符;若是控
* 制字符,则将其写回设备显示(若为设备设置了回显控制符标
* 志,则将控制字符转换为诸如^H形式显示)。*/
if (L_ECHO(tty)) {
if (c==10) {
PUTCH(10,tty->write_q);
PUTCH(13,tty->write_q);
} else if (c<32) {
if (L_ECHOCTL(tty)) {
PUTCH('^',tty->write_q);
PUTCH(c+64,tty->write_q);
}
} else
PUTCH(c,tty->write_q);
tty->write(tty);
}
PUTCH(c,tty->secondary);
}
/* 当字符设备读队列中的字符处理完毕后,即可唤醒等在辅助队列上的进程 */
wake_up(&tty->secondary.proc_list);
}
/* tty_read,
* 从当前进程的字符设备的辅助队列中读取nr字节数据到buf中,
* channel = 0, 控制台终端,
* channel = 1, 串口1终端,
* channel = 2, 串口2终端。
*
* 系统调用读文件(read) --> 内核(sys_read) -->
* 区分读字符设备(rw_char) --> 区分字符设备类型
* (rw_ttyx,rw_tty) --> 从字符设备辅助队列中读取字符(tty_read)。
*
* 字符设备数据到辅助队列:当字符设备接收到字符时以中
* 断方式通知CPU读取该字符到字符设备对应的读队列中,
* 然后通过中断C处理函数将该字符读(转换)到辅助队列中。*/
int tty_read(unsigned channel, char * buf, int nr)
{
struct tty_struct * tty;
char c, * b=buf;
int minimum,time,flag=0;
long oldalarm;
/* 根据channel获取 管理字符设备的结构体 */
if (channel>2 || nr<0) return -1;
tty = &tty_table[channel];
/* 备份当前进程的定时值;
* 获取为字符设备设置的超时值(0则未设置)和达到该超
* 时值应读取的字符数,若当前进程没有设置超时值或者
* 读取字符设备的超时值小于进程原设置的超时值,则用
* 读取字符的超时值覆盖进程原超时值。待任务调度函数
* 执行时(如当前进程进入睡眠后)会检查当前进程是否超
* 时,若超时则会给任务置超时信号。*/
oldalarm = current->alarm;
time = 10L*tty->termios.c_cc[VTIME];
minimum = tty->termios.c_cc[VMIN];
if (time && !minimum) {
minimum=1;
if (flag=(!oldalarm || time+jiffiesalarm = time+jiffies;
}
if (minimum>nr)
minimum=nr;
/* 从字符设备辅助队列中读取nr个字符到buf中。
*
* 首先检查读取字符是否超时,若超时或进程有其他
* 信号要处理则停止读取;若辅助队列不满足读取条
* 件则尝试睡眠等待辅助队列满足被读条件;待辅助
* 队列满足阅读条件时再检查读取字符是否超时,满
* 足条件后从辅助队列中读取字符到buf中,直到遇到
* 如文件结束符等结束标志。*/
while (nr>0) {
/* 若当前进程超时值为字符读取超时值(flag),检查
* 本进程是否有超时信号需处理,若是则清除进程超
* 时信号后退出循环。*/
if (flag && (current->signal & ALRMMASK)) {
current->signal &= ~ALRMMASK;
break;
}
/* 若当前进程还有其他信号需处理则停止字符的继续读取 */
if (current->signal)
break;
/* 若字符设备辅助队列空或者在为字符设备开启规范标志
* 前提下(以行为单位进行读取),当辅助队列中的字符数少
* 于1行且辅助队列剩余空间大于20则进入睡眠(在队列为空
* 时sleep_if_empty才会真正地进入睡眠,这种情况只有等超
* 时退出读取咯)。待真正进入睡眠时,待被其它进程唤醒后
* 若未超时将继续读取。*/
if (EMPTY(tty->secondary) || (L_CANON(tty) &&
!tty->secondary.data && LEFT(tty->secondary)>20)) {
sleep_if_empty(&tty->secondary);
continue;
}
/* 从辅助队列中读取字符序列依次存到buf中,
* 在字符设备开启规范标志时,若读到文件结束
* 符,换行符时则结束读操作;或在读满nr字符或
* 读完辅助队列内容方结束。*/
do {
GETCH(tty->secondary,c);
if (c==EOF_CHAR(tty) || c==10)
tty->secondary.data--;
if (c==EOF_CHAR(tty) && L_CANON(tty))
return (b-buf);
else {
put_fs_byte(c,b++);
if (!--nr)
break;
}
} while (nr>0 && !EMPTY(tty->secondary));
/* 检查并更新当前进程的超时值。
*
* 若字符设备设置了超时时间且没有开启规范模式,
* 若当前进程无超时值或其超时值比字符设备所设
* 置的超时值要大时,则更新当前进程的超时值为
* 字符设备所设置的超时值,否则恢复进程原本超时值。*/
if (time && !L_CANON(tty))
if (flag=(!oldalarm || time+jiffiesalarm = time+jiffies;
else
current->alarm = oldalarm;
/* 在一次读取循环结束后,
* 在规范模式下,只要读到字符便结束本次读取;
* 在非规范模式下,当读取到超时所对应字符数时才停止本次读取。*/
if (L_CANON(tty)) {
if (b-buf)
break;
} else if (b-buf >= minimum)
break;
}
/* 恢复进程的超时值,若读取超时且没有读取到任何
* 字符则返回相应错误码,否则返回读取成功的字符数。*/
current->alarm = oldalarm;
if (current->signal && !(b-buf))
return -EINTR;
return (b-buf);
}
/* tty_write,
*
*
* 系统调用写文件(write)
* --> 内核(sys_write)
* --> 区分写字符设备(rw_char)
* --> 区分字符设备类型(rw_ttyx,rw_tty)
* --> 往字符设备写队列中写字符(tty_write)
* --> 开启字符设备发送中断(rs_write)
* <--> 字符设备发送中断处理函数发送字符(rs_interrupt,write_char)
* --> 关字符设备发送中断。*/
int tty_write(unsigned channel, char * buf, int nr)
{
static cr_flag=0;
struct tty_struct * tty;
char c, *b=buf;
if (channel>2 || nr<0) return -1;
tty = channel + tty_table;
/* 向字符设备写队列中写入buf内存段的nr字节数据,直到
* nr字节内容被完全写入或者当前进程收到其他处理信号。*/
while (nr>0) {
/* 若字符设备写队列为空则睡眠等待写队列非空 */
sleep_if_full(&tty->write_q);
if (current->signal)
break;
/* 当写队列非满时,将buf中的nr字符往写队列中。
* 若设置了输出处理标志则根据相应输出处理标志
* 对字符做转换后再写入。*/
while (nr>0 && !FULL(tty->write_q)) {
c=get_fs_byte(b);
if (O_POST(tty)) {
if (c=='\r' && O_CRNL(tty))
c='\n';
else if (c=='\n' && O_NLRET(tty))
c='\r';
if (c=='\n' && !cr_flag && O_NLCR(tty)) {
cr_flag = 1;
PUTCH(13,tty->write_q);
continue;
}
if (O_LCUC(tty))
c=toupper(c);
}
b++; nr--;
cr_flag = 0;
PUTCH(c,tty->write_q);
}
/* 当完成一次循环写入队列后,调用写队列后的回调函数;
* 若还未写完nr字符但写队列已满时则调用任务调度函数
* 切换任务,待切换到本任务时继续发送,在任务切换函数
* 中有可能会为当前进程置某种信号(signal)。*/
tty->write(tty);
if (nr>0)
schedule();
}
/* 返回写入字节数 */
return (b-buf);
}
/*
* Jeh, sometimes I really like the 386.
* This routine is called from an interrupt,
* and there should be absolutely no problem
* with sleeping even in an interrupt (I hope).
* Of course, if somebody proves me wrong, I'll
* hate intel for all time :-). We'll have to
* be careful and see to reinstating the interrupt
* chips before calling this, though.
*
* I don't think we sleep here under normal circumstances
* anyway, which is good, as the task sleeping might be
* totally innocent.
*/
/* do_tty_interrupt,
* 字符设备读中断C处理函数,
* 将所读字符存储到字符设备的辅助队列中。
*
* 由字符设备接收中断处理函数read_char调用。*/
void do_tty_interrupt(int tty)
{
copy_to_cooked(tty_table+tty);
}
void chr_dev_init(void)
{
}
rs_io.s
/*
* linux/kernel/rs_io.s
*
* (C) 1991 Linus Torvalds
*/
/*
* rs_io.s
*
* This module implements the rs232 io interrupts.
*/
/* rs_io.s
* 本程序实现了 rs232 I/O中断处理程序。*/
.text
.globl _rs1_interrupt,_rs2_interrupt
size = 1024 /* must be power of two !
and must match the value
in tty_io.c!!! */
/* these are the offsets into the read/write buffer structures */
/* 串口读写队列结构体中各成员偏移量 */
rs_addr = 0
head = 4
tail = 8
proc_list = 12
buf = 16
startup = 256 /* chars left in write queue when we restart it */
/*
* These are the actual interrupt routines. They look where
* the interrupt is coming from, and take appropriate action.
*/
.align 2
_rs1_interrupt:
pushl $_table_list+8 /* table_list第3个元素的地址 */
jmp rs_int
.align 2
_rs2_interrupt:
pushl $_table_list+16 /* table_list第5个元素的地址 */
rs_int:
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
push %es
push %ds /* as this is an interrupt, we cannot */
pushl $0x10 /* know that bs is ok. Load it */
pop %ds /* es=ds=10h, 加载内核数据段到数据段寄存器中 */
pushl $0x10
pop %es
movl 24(%esp),%edx /* edx=table_list + 8 or 16 */
movl (%edx),%edx /* 取串口1或串口2读队列内存首地址 */
movl rs_addr(%edx),%edx /* 取读队列data成员即串口1或2的起始端口(0x3f8, 0x2f8) */
addl $2,%edx /* interrupt ident. reg 0x3fa(0x2fa) */
rep_int:
xorl %eax,%eax
inb %dx,%al /* 读0x3fa(0x2fa)即读中断标识寄存器以判断串口中断类型 */
testb $1,%al /* bit[1]=1则表示无中断 */
jne end
cmpb $6,%al /* 接收状态有错,this shouldn't happen, but ... */
ja end
movl 24(%esp),%ecx /* ecx=table_list + 8 or 16 */
pushl %edx
subl $2,%edx /* 传递给子程序的参数edx=0x3f8(0x2f8) */
/* al=110,接收状态有错,奇偶错etc,读线路状态寄存器复位;
* al=100,接收数据就绪,接收器数据有效,读接收数据寄存器复位;
* al=010,发送保持寄存器空,发送器准备就绪,写入发送保持寄存器;
* al=000,MODEM状态有变化,输入状态有变化,读MODEM状态寄存器;
*
* 根据串口中断类型跳转执行jmp_table表中相应的中断中断处理函数。 */
call jmp_table(,%eax,2) /* NOTE! not *4, bit0 is 0 already */
popl %edx
jmp rep_int /* 直到串口无中断方结束 */
end: movb $0x20,%al
outb %al,$0x20 /* EOI,向PIC发送结束中断命令 */
pop %ds
pop %es
popl %eax
popl %ebx
popl %ecx
popl %edx
addl $4,%esp # jump over _table_list entry
iret
/* 各串扣中断对应的中断处理程序表,他们分别是
* MODEM寄存器有变化,发送保持寄存器空,接收数据,接收状态错误
* 的中断处理程序。*/
jmp_table:
.long modem_status,write_char,read_char,line_status
.align 2
modem_status: /* 读MODEM状态寄存器(0x3fe或0x2fe)以让MODEM寄存器复位 */
addl $6,%edx /* clear intr by reading modem status reg */
inb %dx,%al
ret
.align 2
line_status: /* 读线路状态寄存器(0x3fd或0x2fd)以让MODEM寄存器复位 */
addl $5,%edx /* clear intr by reading line status reg. */
inb %dx,%al
ret
/* 读串口所接收到的数据 */
.align 2
read_char:
inb %dx,%al /* 读0x3f8(0x2f8), 读接收数据寄存器 */
movl %ecx,%edx /* edx=ecx=table_list+8 or +16 */
subl $_table_list,%edx /* edx=8 or 16 */
shrl $3,%edx /* edx=1 or 2 */
/* ecx=table_list[8 or 16)]即&tty_table[1 or 2].read_q,
* 获取串口读队列地址赋给ecx。*/
movl (%ecx),%ecx
/* movl (%ecx+head), ebx,
* ebx=*( (long *)(&tty_table[1 or 2].read_q + 4) )即
* tty_table[1 or 2].read_q.head,将串口读队列中head成员赋给ebx寄存器中。*/
movl head(%ecx),%ebx
/* 将从串口中所读数据写入*( (char *)(&tty_table[1 or 2].read_q + 16 + head) )
* 即tty_table[1 or 2].read_q.buf[ebx]中,即将从串口所读数据写入串口读队列的buf成员中。*/
movb %al,buf(%ecx,%ebx)
incl %ebx /* tty_table[1 or 2].read_q.buf数据头索引增1 */
andl $size-1,%ebx /* ebx=ebx & size - 1,即以循环队列的方式使用队列中的buf */
cmpl tail(%ecx),%ebx /* 判断tty_table[1 or 2].read_q.tail是否等于ebx即buf中数据头索引 */
je 1f /* 若数据头索引等于数据尾索引表示buf已满则向前跳转标号1处, */
movl %ebx,head(%ecx) /* 将数据头索引赋值给tty_table[1 or 2].read_q.head */
1: pushl %edx /* table_list读队列下标(1 or 2)作为do_tty_interrupt函数的参数 */
call _do_tty_interrupt /* kernel/chr_drv/tty_io.c */
addl $4,%esp /* 清参数edx的栈内存 */
ret
.align 2
write_char:
movl 4(%ecx),%ecx /* ecx=&tty_table[1 or 2].write_q */
/* ebx=*( (long *)(&tty_table[1 or 2].write_q + 4) )即
* ebx=串口写队列head成员 */
movl head(%ecx),%ebx
/* ebx=写队列中的数据个数 */
subl tail(%ecx),%ebx
andl $size-1,%ebx # nr chars in queue
je write_buffer_empty /* 若写队列为空则跳转write_buffer_empty处 */
cmpl $startup,%ebx /* if (写队列数据元素 < 256) 则向前跳转标号1处 */
ja 1f
/* ebx=*( (long *)(&tty_table[1 or 2].write_q + 12) )即
* 取串口写队列任务指针成员proc_list赋值给ebx。*/
movl proc_list(%ecx),%ebx # wake up sleeping process
/* 若任务指针值为空则向前跳转到标号1处 */
testl %ebx,%ebx # is there any?
je 1f
/* movl $0, *proc_list 即proc_list所指内存段首4字节置为0,
* 即将proc_list所指任务的state成员置位0-将所指任务置为就绪状态 */
movl $0,(%ebx)
/* 将写队列数据尾的数据写往al,然后将其 */
1: movl tail(%ecx),%ebx
movb buf(%ecx,%ebx),%al
outb %al,%dx /* 写发送器保持寄存器0x3f8(0x2f8) */
incl %ebx /* 数据尾索引增1 */
andl $size-1,%ebx /* 以循环队列的方式使用队列中的buf */
movl %ebx,tail(%ecx) /* 将数据尾索引赋给串口写队列tail成员 */
cmpl head(%ecx),%ebx /* 比较串口写队列中指向数据头和数据尾成员,若两者相等表示队列空 */
je write_buffer_empty /* 队列空则跳转write_buffer_empty处 */
ret /* 将串口写队列中数据传输一个到串口让其发送出去 */
.align 2
write_buffer_empty:
/* 将串口写队列的proc_list成员赋值给ebx,
* 检查ebx是否为空,为空则向前跳转标号1处,
* 若不为空则赋值0给proc_list所指任务的state成员,
* 即将该任务置位可运行状态,即唤醒该任务。*/
movl proc_list(%ecx),%ebx # wake up sleeping process
testl %ebx,%ebx # is there any?
je 1f
movl $0,(%ebx)
1: incl %edx /* edx=0x3f9(0x2f9) */
inb %dx,%al /* 读中断允许标志寄存器 */
jmp 1f
1: jmp 1f
1: andb $0xd,%al /* disable transmit interrupt */
outb %al,%dx /* 禁止串口发送寄存器空中断,因为此时写队列中的数据已发送完毕 */
ret
serial.c
/* * linux/kernel/serial.c * * (C) 1991 Linus Torvalds */ /* * serial.c * * This module implements the rs232 io functions * void rs_write(struct tty_struct * queue); * void rs_init(void); * and all interrupts pertaining to serial IO. */ #include
#include #include #include #define WAKEUP_CHARS (TTY_BUF_SIZE/4) /* 以typedef void (fun)(void)函数类型 * 声明以下符号,其定义在rs_io.s中。*/ extern void rs1_interrupt(void); extern void rs2_interrupt(void); /* init, * 初始化串口通信模式,如串口 * 终端设备以中断方式和CPU通信,数据速率=2400bps。*/ static void init(int port) { /* PC/AT分配给UART2和UART1的端口地址空间分别为 * [0x2f0, 0x2ff]和[0x3f8, 0x3ff]。实际 芯片使用 * 端口低3位用于寄存器寻址,UART2只使用了[0x2f8, 0x2fe], * UART1只使用了[0x3f8, 0x3fe]。*/ /* 写3FBH(2FBH) 0x80, * 写线路控制寄存器, 确定异步通信的数据格式, * DLAB=1, 无奇偶, 停止位为1位, 数据5位。 * * DLAB=1, 写3F8H(2F8H)/3F9H(2F9H) 0x30/0x00, * 写波特率因子LSB/MSB, * MSB=0x00, LSB=0x30 --> 数据速率=2400bps。 * (波特率因子: 接收/发送一个bit所需时钟数, * 由此顺便计算下此时的时钟频率=2400bps * 48 = 115200Hz) * * 写3FBH(2FBH) - 写线路控制寄存器 0x03, * DLAB = 0, 无奇偶, 数据位为8位。 * * 写3FCH(2FCH) - 写MODEM控制寄存器 0x0b, (RS-232) * bit[3]=1, UART为中断I/O方式, * bit[1]=1, 数据终端就绪, DTR输出有效, * bit[0]=1, 请求发送, RTS输出有效。 * * DLAB=0,写3F9H(2F9H) 0x0d, * 写中断允许寄存器, * bit[3]=1, 允许MODEM状态变化中断, * bit[2]=1, 允许接收有错或间断条件中断, * bit[1]=0, 禁止发送器保持寄存器空中断, * bit[0]=1, 允许接收器数据就绪中断, * 当UART为中断I/O方式, 满足以上某中断条件时, * 芯片的INTRPT(中断请求)端输出高电平向8259A(IRQ4/IRQ3中断发生), * 并在中断标识寄存器中设置相应标识位标识当前中断。 * * DLAB=0, * 读3F8H(2F8H), 读接收数据寄存器, * 将接收数据寄存器的内容读出以恢复接收数据寄存器无数据状态?*/ outb_p(0x80,port+3); /* set DLAB of line control reg */ outb_p(0x30,port); /* LS of divisor (48 -> 2400 bps */ outb_p(0x00,port+1); /* MS of divisor */ outb_p(0x03,port+3); /* reset DLAB */ outb_p(0x0b,port+4); /* set DTR,RTS, OUT_2 */ outb_p(0x0d,port+1); /* enable all intrs but writes */ (void)inb(port); /* read data port to reset things (?) */ } /* rs_init, * UART初始化, * 在IDT中设置串口中断处理入口程序, * 设置PIC使能串口中断,并设置串口通信相关参数。*/ void rs_init(void) { /* 在IDT[24h..23h]中设置串口2和串口1 * 的中断处理入口程序。*/ set_intr_gate(0x24,rs1_interrupt); set_intr_gate(0x23,rs2_interrupt); /* 编程设置UART,建立异步串行通信模式。 * 以中断方式和CPU通信,数据传输速率为2400bps。*/ init(tty_table[1].read_q.data); /* 0x3f8 */ init(tty_table[2].read_q.data); /* 0x2f8 */ /* 设置PIC允许IRQ3和IRQ4即串口2和串口1中断 */ outb(inb_p(0x21)&0xE7,0x21); } /* * This routine gets called when tty_write has put something into * the write_queue. It must check wheter the queue is empty, and * set the interrupt register accordingly * * void _rs_write(struct tty_struct * tty); */ /* rs_write, * 设置tty对应的UART允许其发送保持寄存器空闲时的中断。 * * 该函数在tty_write往写队列tty->write_queue写一些数 * 据后被调用,当UART发送保持寄存器空闲时就会向PIC输出 * 中断从而让CPU执行串口中断处理入口程序rs*_interrupt, * 待tty->write_queue队列中无数据时将再禁止UART发送保 * 持寄存器空闲时中断。*/ void rs_write(struct tty_struct * tty) { cli(); /* 当串口写队列不为空时, * 通过0x3f9(0x2f9)写UART发送保持寄存器 * bit[1]以允许为发送保持寄存器空时中断。*/ if (!EMPTY(tty->write_q)) outb(inb_p(tty->write_q.data+1)|0x02,tty->write_q.data+1); sti(); }
console.c
/*
* 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.
*/
/* 本文件实现了控制台I/O操作函数
* 'void con_init(void)'
* 'void con_write(struct tty_queue * queue)'
* 希望这是一个相当完整的VT102版实现。*/
/*
* 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.
*/
/* 注,在放置数据到显卡I/O时会禁止CPU处理中断,这些代码也会在键盘中断中运行。
* 由于使用陷阱门,所以其实在键盘中断中没有使能CPU处理中断。希望这种重复禁止
* 的方式可以正常运行。*/
/*
* Code to check for different video-cards mostly by Galen Hunt,
*
*/
#include
#include
#include
#include
/*
* These are set up by the setup-routine at boot-time:
*/
/* 以下这些宏读取在setup.s中通过BIOS获取并存储的跟显示相关的信息 */
#define ORIG_X (*(unsigned char *)0x90000) /* 光标x方向位置 */
#define ORIG_Y (*(unsigned char *)0x90001) /* 光标y方向位置 */
#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) /* EGA显存大小 */
#define ORIG_VIDEO_EGA_CX (*(unsigned short *)0x9000c) /* 属性等设置 */
#define VIDEO_TYPE_MDA 0x10 /* Monochrome Text Display */
#define VIDEO_TYPE_CGA 0x11 /* CGA Display */
#define VIDEO_TYPE_EGAM 0x20 /* EGA/VGA in Monochrome Mode */
#define VIDEO_TYPE_EGAC 0x21 /* EGA/VGA in Color Mode */
#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; /* 屏幕内容左上角对应的显存地址 */
static unsigned long scr_end; /* 屏幕内容末端内容对应的显存地址 */
static unsigned long pos; /* 屏幕坐标(x,y)对应的显存地址 */
static unsigned long x,y; /* 屏幕坐标 */
static unsigned long top,bottom; /* 屏幕内容的顶端和底部坐标 */
static unsigned long state=0; /* 解析终端写队列中数据的状态/步骤 */
static unsigned long npar,par[NPAR];
static unsigned long ques=0;
static unsigned char attr=0x07;
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 */
/* gotoxy,
* 更新光标在屏幕上的位置(x,y),并计算该位置所对应的显存地址。*/
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,y用于记录光标在屏幕上的坐标 */
x=new_x;
y=new_y;
/* 计算屏幕坐标(x,y)对应的显存地址(1列用2字节显存表示) */
pos=origin + y*video_size_row + (x<<1);
}
/* set_origin,
* 设置终端屏幕起始显存地址,以将整屏对应的内容显示在终端上。*/
static inline void set_origin(void)
{
cli();
/* 选择显示控制数据寄存器r12,
* 写入终端将要显示内容所在显存中的偏移地址的高字节。*/
outb_p(12, video_port_reg);
outb_p(0xff&((origin-video_mem_start)>>9), video_port_val);
/* 选择显示控制数据寄存器r3,
* 写入终端将要显示内容所在显存中的偏移地址的低字节(1列2字节)。*/
outb_p(13, video_port_reg);
outb_p(0xff&((origin-video_mem_start)>>1), video_port_val);
sti();
}
/* scrup,
* 将终端窗口向上移动一行。
*
* 终端内容向下继续显示一行,
* 若终端屏幕已满,则向上移动一行,
* 终端屏幕下方出现的新行用空格填充。*/
static void scrup(void)
{
/* EGA显卡支持终端区域和整屏窗口移动, */
if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
{
/* 若终端已全屏显示,*/
if (!top && bottom == video_num_lines) {
/* 则将终端内容左上角,坐标位置,终端内容末端
* 的显存地址更新到下一行 */
origin += video_size_row;
pos += video_size_row;
scr_end += video_size_row;
/* 若控制终端内容末端显存地址已超过显存末尾地址,
* 则将终端除第1行以外的内容重新写入显存起始
* 地址处(新行用空格填充),并更新终端内容对应的显存内存段。*/
if (scr_end > video_mem_end) {
__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 {
__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();
/* 若EGA显卡下,终端内容未满屏,不用整屏移动,
* 此时将开始于top+1到bottom区域中的内容向
* 上移动一行,用空格填充新出现的行。*/
} 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");
}
}
/* 非EGA显卡, 诸如MDA显卡控制器只支持整屏滚动,
* 但其会自动调整超出显存范围的情况。*/
else /* Not EGA/VGA */
{
__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");
}
}
/* scrdown,
* 将终端窗口向下移动一行。
*
* 终端内容向上继续显示一行,
* 若终端屏幕已满,则向下移动一行,
* 终端屏幕上方出现的新行用空格填充。*/
static void scrdown(void)
{
if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
{
__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");
}
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");
}
}
/* lf,
* 针对输出的换行符进行换行。*/
static void lf(void)
{
/* 若还未到屏幕底部则
* 更新y方向坐标和对应的显存地址 */
if (y+1top) {
y--;
pos -= video_size_row;
return;
}
/* 若光标在终端内容顶端,
* 则将终端窗口向下移动一行。*/
scrdown();
}
/* cr,
* 将光标置于行首。*/
static void cr(void)
{
/* 显存地址减去
* 行首到x位置处的显存字节数 */
pos -= x<<1;
x=0;
}
/* del,
* 更新删除1字符后光标的位置。*/
static void del(void)
{
if (x) {
pos -= 2;
x--;
*(unsigned short *)pos = video_erase_char;
}
}
/* csi_J,
* 以光标为基准,删除终端上的内容。
*
* 'ESC [par J',
* par=0, 删除光标处到终端底端的内容;
* par=1, 删除终端开始到光标处的内容;
* par=2, 删除终端整屏。*/
static void csi_J(int par)
{
long count __asm__("cx");
long start __asm__("di");
/* 根据par参数值,计算删除字符数及对应显存区域 */
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;
}
/* 用擦除字符video_erase_char填充终端上指定区域的字符 */
__asm__("cld\n\t"
"rep\n\t"
"stosw\n\t"
::"c" (count),
"D" (start),"a" (video_erase_char)
:"cx","di");
}
/* csi_K,
* 以光标位置为基准,删除光标所在行的内容。
*
* 'ESC [par K',
* par=0,删除光标到行尾内容;
* par=1,行首到光标段内容;
* par=2,删除光标所在行。*/
static void csi_K(int par)
{
long count __asm__("cx");
long start __asm__("di");
/* 根据par值计算删除的字符数及对应的显存区域 */
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>9), video_port_val);
/* 选择显示控制器数据寄存器r14写入鼠标在显存中偏移的低字节*/
outb_p(15, video_port_reg);
outb_p(0xff&((pos-video_mem_start)>>1), video_port_val);
sti();
}
/* respond,
* 向主机响应终端的设备属性(主机通过'ESC Z'等控制序列请求)。*/
static void respond(struct tty_struct * tty)
{
char * p = RESPONSE;
cli();
/* 将应答序列放入读队列中, */
while (*p) {
PUTCH(*p,tty->read_q);
p++;
}
sti();
/* 将含应答序列读队列中的内容转换到辅助队列中 */
copy_to_cooked(tty);
}
/* insert_char,
* 在光标位置插入擦除字符,将光标原后续字符皆后移。*/
static void insert_char(void)
{
int i=x;
unsigned short tmp, old = video_erase_char; /* 擦除字符 */
unsigned short * p = (unsigned short *) pos; /* 光标对应显存地址 */
/* 将擦除字符插入光标位置处 */
while (i++=video_num_columns)
return;
/* 将光标之后字符依次左移,在行尾用擦除字符填充 */
i = x;
while (++i < video_num_columns) {
*p = *(p+1);
p++;
}
*p = video_erase_char;
}
/* delete_line,
* 删除光标所在行。*/
static void delete_line(void)
{
int oldtop,oldbottom;
oldtop=top;
oldbottom=bottom;
/* 从光标所在行开始,将中断窗口上移一行 */
top=y;
bottom = video_num_lines;
scrup();
/* 恢复光标位置 */
top=oldtop;
bottom=oldbottom;
}
/* csi_at,
* 在光标处插入nr个擦除字符。
* 光标右边的字符往右移,超过终端右边界即列数的字符将消失。
*
* 'ESC [nr @',nr为插入字符个数。*/
static void csi_at(unsigned int nr)
{
if (nr > video_num_columns)
nr = video_num_columns;
else if (!nr)
nr = 1;
while (nr--)
insert_char();
}
/* csi_L,
* 在光标位置处插入nr行。
*
* 'ESC [nr L'。*/
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();
}
/* csi_P,
* 删除光标处的nr个字符。
*
* 'ESC [nr P'。*/
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();
}
/* csi_M,
* 删除光标处的nr行。
*
* 'ESC [ nr M'。*/
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;
/* save_cur,
* 同步光标当前位置。*/
static void save_cur(void)
{
saved_x=x;
saved_y=y;
}
/* restore_cur,
* 恢复所保存的光标位置。*/
static void restore_cur(void)
{
gotoxy(saved_x, saved_y);
}
/* con_write,
* 往控制终端写队列中写入数据后的回调函数。
*
* 解析终端写队列中的数据,若是控制字符,转义字符,
* 控制序列则在终端实现这些字符对应的功能。*/
void con_write(struct tty_struct * tty)
{
int nr;
char c;
/* 控制终端写队列中的字符数 */
nr = CHARS(tty->write_q);
while (nr--) {
/* 从写队列中读取1个字符 */
GETCH(tty->write_q,c);
switch(state) {
case 0:
/* 在state=0阶段,若读取到普通字符
* 则直接显示在光标位置处,若已到行
* 尾则将字符写到下一行。显示字符后
* 更新光标和其对应的显存地址。*/
if (c>31 && c<127) {
if (x>=video_num_columns) {
x -= video_num_columns;
pos -= video_size_row;
lf();
}
/* 将字符显示在经计算的位置 */
__asm__("movb _attr,%%ah\n\t"
"movw %%ax,%1\n\t"
::"a" (c),"m" (*(short *)pos)
:"ax");
pos += 2;
x++;
/* 为转义字符时,置state=1 */
} else if (c==27)
state=1;
/* 若为换行符,纵向制表符,换页符则将光标移到下一行 */
else if (c==10 || c==11 || c==12)
lf();
/* 若为回车符, 则将光标移到行首 */
else if (c==13)
cr();
/* 若是擦除字符,则擦除光标前1字符 */
else if (c==ERASE_CHAR(tty))
del();
/* 若是退格字符,则左移光标1字符位置 */
else if (c==8) {
if (x) {
x--;
pos -= 2;
}
/* 若字符为水平制表符则将光标移到最近列数为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;
/* 若为响铃字符则调用蜂鸣函数蜂鸣一下 */
} else if (c==7)
sysbeep();
break;
/* 若在state=0阶段解析到转义字符序列,*/
case 1:
state=0; /* 恢复state 0状态 */
/* 若继state=0解析到ESC后又解析到'['则置state=2 */
if (c=='[')
state=2;
else if (c=='E') /* ESC 'E' */
gotoxy(0,y+1);
else if (c=='M') /* ESC 'M'*/
ri();
else if (c=='D') /* ESC 'D'*/
lf();
else if (c=='Z') /* ESC 'Z'*/
respond(tty);
else if (x=='7') /* ESC '7'*/
save_cur();
else if (x=='8') /* ESC '8'*/
restore_cur();
break;
/* ESC [ */
case 2:
/* 初始化par数组供case 3使用 */
for(npar=0;npar='0' && c<='9') {
par[npar]=10*par[npar]+c-'0';
break;
/* 字符不为';'或数字字符则置state=4 */
} else state=4;
/* 解析转义字符序列的最后一个字符,该字符表示具体命令 */
case 4:
state=0; /* 复位state=0 */
switch(c) {
/* ESC [ par 'G'或'`',光标水平移动 */
case 'G': case '`':
if (par[0]) par[0]--;
gotoxy(par[0],y);
break;
/* ESC [ par 'A',光标上移 */
case 'A':
if (!par[0]) par[0]++;
gotoxy(x,y-par[0]);
break;
/* ESC [ par 'B' 或 'e', 光标下移 */
case 'B': case 'e':
if (!par[0]) par[0]++;
gotoxy(x,y+par[0]);
break;
/* ESC [ par 'C' 或 'a', 光标右移 */
case 'C': case 'a':
if (!par[0]) par[0]++;
gotoxy(x+par[0],y);
break;
/* ESC [ par 'D', 光标左移 */
case 'D':
if (!par[0]) par[0]++;
gotoxy(x-par[0],y);
break;
/* ESC [ par 'E', 光标下移并回行首 */
case 'E':
if (!par[0]) par[0]++;
gotoxy(0,y+par[0]);
break;
/* ESC [ par 'F', 光标上移并回行首 */
case 'F':
if (!par[0]) par[0]++;
gotoxy(0,y-par[0]);
break;
/* ESC [ par 'd', 当前列设置行位置 */
case 'd':
if (par[0]) par[0]--;
gotoxy(x,par[0]);
break;
/* ESC [ par 'H' 或 'f', 光标定位 */
case 'H': case 'f':
if (par[0]) par[0]--;
if (par[1]) par[1]--;
gotoxy(par[1],par[0]);
break;
/* ESC [ par 'j', 删除操作 */
case 'J':
csi_J(par[0]);
break;
/* ESC [ par 'K', 行内删除 */
case 'K':
csi_K(par[0]);
break;
/* ESC [ par 'L', 插入行 */
case 'L':
csi_L(par[0]);
break;
/* ESC [ par 'M', 删除行 */
case 'M':
csi_M(par[0]);
break;
/* ESC [ par 'P', 删除字符 */
case 'P':
csi_P(par[0]);
break;
/* ESC [ par '@', 插入字符 */
case '@':
csi_at(par[0]);
break;
/* ESC [ par 'm', 设置显示字符属性 */
case 'm':
csi_m();
break;
/* ESC [ par '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;
/* ESC [ par 's', 保存光标位置 */
case 's':
save_cur();
break;
/* ESC [ par '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.
*/
/* con_init,
* 初始化控制台终端。
*
* 初始化画面显示信息,光标位置,键盘中断,使能键盘等。*/
void con_init(void)
{
register unsigned char a;
char *display_desc = "????";
char *display_ptr;
/* 获取在setup.s通过BIOS所获取到的显卡
* 所支持的显示参数并保存在全局变量中。*/
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;
/* 判断所设置显卡的显示模式,并根据显示模式作相应设置 */
/* 单色模式 */
if (ORIG_VIDEO_MODE == 7) /* Is this a monochrome display? */
{
video_mem_start = 0xb0000;
video_port_reg = 0x3b4;
video_port_val = 0x3b5;
if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
{
video_type = VIDEO_TYPE_EGAM;
video_mem_end = 0xb8000;
display_desc = "EGAm";
}
else
{
video_type = VIDEO_TYPE_MDA;
video_mem_end = 0xb2000;
display_desc = "*MDA";
}
}
else /* 彩色模式 */
{
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 (mos