/*
* 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 <ctype.h>
#include <errno.h>
#include <signal.h>
#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))//tty发出的停止进程信号屏蔽位
#include <linux/sched.h>
#include <linux/tty.h>
#include <asm/segment.h>
#include <asm/system.h>
//获取三种模式标志集之一,或者判断一个标志集是否有置位标志
#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)//输出模式
//取本地模式 标志集中的一个标志
#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)
//取输入模式 标志集中的一个标志
#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)
//取输出模式 标志集中的一个标志
#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)
//有3个,分别为串口1和串口2
struct tty_struct tty_table[] = {
{
{ICRNL, /* change incoming CR to NL *///输入模式
OPOST|ONLCR, /* change outgoing NL to CRNL *///输出模式
0,//控制模式
ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,//本地模式
0, /* console termio *///线路规程0--TTY
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,
0,
0,
INIT_C_CC},
0,
0,
rs_write,
{0x3f8,0,0,0,""}, /* rs 1 */
{0x3f8,0,0,0,""},
{0,0,0,0,""}
},{
{0, /* no translation */
0, /* no translation */
B2400 | CS8,
0,
0,
INIT_C_CC},
0,
0,
rs_write,
{0x2f8,0,0,0,""}, /* rs 2 */
{0x2f8,0,0,0,""},
{0,0,0,0,""}
}
};//3
/*
* these are the tables used by the machine code handlers.
* you can implement pseudo-tty's or something by changing
* them. Currently not done.
*/
struct tty_queue * table_list[]={//由key_board.S line 91 调用,取得读写缓冲区队列地址
&tty_table[0].read_q, &tty_table[0].write_q,//控制台
&tty_table[1].read_q, &tty_table[1].write_q,//串口1
&tty_table[2].read_q, &tty_table[2].write_q//串口2
};
void tty_init(void)//tyy终端初始化函数,由main.c line 130 调用
{
rs_init();//初始化串口
con_init();//初始化控制台
}
void tty_intr(struct tty_struct * tty, int mask)//tty键盘中断字符(ctrl C)处理函数,mask通常为SIGINT
{
int i;
if (tty->pgrp <= 0)//TTY里程组号小于0不存在,等于0时为初始INIT进程,不可能发生SIGINT
return;
for (i=0;i<NR_TASKS;i++)//向进程组中每个进程的组进程号为TTY的组进程号的发送指定的信号mask
if (task[i] && task[i]->pgrp==tty->pgrp)
task[i]->signal |= mask;
}
static void sleep_if_empty(struct tty_queue * queue)//如果队列缓冲区空则让进程进入可中断睡眠状态
{
cli();//关中断
while (!current->signal && EMPTY(*queue))//当前进程没有信号要处理,并且指定的队列缓冲区为空
interruptible_sleep_on(&queue->proc_list);//将当前任务置为可中断的等待状态,并将此放入等待队列queue->proc_list中,因为当前的缓冲区为空,没有数据可读,所以要将此进程睡眠,等待有数据可读时再唤醒
sti();//开中断
}
static void sleep_if_full(struct tty_queue * queue)//如果队列缓冲区满则让进程可中断睡眠状态
{
if (!FULL(*queue))
return;
cli();
while (!current->signal && LEFT(*queue)<128)//当前进程没有信号要处理,并且指定的队列缓冲区当前长度小于128
interruptible_sleep_on(&queue->proc_list);//将当前任务置为可中断的等待状态,并将此放入等待队列queue->proc_list中,因为当前的缓冲区为满时,没有地方可放数据,所以要将此进程睡眠,等待有空闲空间放数据 时再唤醒
sti();
}
void wait_for_keypress(void)//等待按键
{
sleep_if_empty(&tty_table[0].secondary);//如果队列缓冲区空则让进程进入可中断睡眠状态
}
//复制成规范模式字符序列,从缓冲区读队列取数据 ,处理完后放入辅助队列中,有必要还要放写队列中
void copy_to_cooked(struct tty_struct * tty)
{
signed char c;
while (!EMPTY(tty->read_q) && !FULL(tty->secondary)) {//读队列不空或辅助队列不满
GETCH(tty->read_q,c);//从读队列中取一个字符到C 中
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)) {//键盘终止控制字符KILL (ctrl U)
/* deal with killing the input line */
while(!(EMPTY(tty->secondary) ||
(c=LAST(tty->secondary))==10 ||
c==EOF_CHAR(tty))) {//文件结束符(ctrl D)
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)) {//删除控制字符EARSE (ctrl H)
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;
}
if (c==STOP_CHAR(tty)) {//停止控制字符STOP (ctrl S)
tty->stopped=1;
continue;
}
if (c==START_CHAR(tty)) {//开始字符(ctrl Q)
tty->stopped=0;
continue;
}
}
if (L_ISIG(tty)) {//本地模式信号置位
if (c==INTR_CHAR(tty)) {//ctrl C 中断信号
tty_intr(tty,INTMASK);
continue;
}
if (c==QUIT_CHAR(tty)) {// ctrl \ 退出信号
tty_intr(tty,QUITMASK);
continue;
}
}
if (c==10 || c==EOF_CHAR(tty))//ctrl D 文件结束符
tty->secondary.data++;
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);//唤醒等待该辅助队列的进程
}
int tty_read(unsigned channel, char * buf, int nr)//channel 子设备号,从辅助队列中读取指定数量的字符放到用户指定的缓冲区
{
struct tty_struct * tty;
char c, * b=buf;
int minimum,time,flag=0;
long oldalarm;
if (channel>2 || nr<0) return -1;//判断参数的有效性
tty = &tty_table[channel];//tty指向子设备号对应tty结构上
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+jiffies<oldalarm))
current->alarm = time+jiffies;
}
if (minimum>nr)
minimum=nr;//确切的至少需要读取 擦子符个数
while (nr>0) {//从辅助队列中循环读取字符放到用户缓冲区buf中
if (flag && (current->signal & ALRMMASK)) {//flag已设置且进程此时已收到定时报警信号
current->signal &= ~ALRMMASK;//复位进程的定时报警信号
break;
}
if (current->signal)//进程 定时到,或收到其他信号
break;
if (EMPTY(tty->secondary) || (L_CANON(tty) &&
!tty->secondary.data && LEFT(tty->secondary)>20)) {//辅助队列 为空或(置为本地规范模式且辅助队列空闲队列大于20),则让当前进程进入可中断睡眠状态
sleep_if_empty(&tty->secondary);
continue;
}
do {
GETCH(tty->secondary,c);//取一字符
if (c==EOF_CHAR(tty) || c==10)//不为结束符,也不为换行符
tty->secondary.data--;//字符行减1
if (c==EOF_CHAR(tty) && L_CANON(tty))//在本地规范模式下且是换行符也退出
return (b-buf);
else {
put_fs_byte(c,b++);//放入buf 中
if (!--nr)
break;
}
} while (nr>0 && !EMPTY(tty->secondary));//
if (time && !L_CANON(tty))
if (flag=(!oldalarm || time+jiffies<oldalarm))
current->alarm = 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);//返回读的个数
}
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;//tty指向对应设备的tty结构处
while (nr>0) {
sleep_if_full(&tty->write_q);//如果队列缓冲区满则让进程可中断睡眠状态
if (current->signal)//当前进程有信号要处理则退出循环
break;
while (nr>0 && !FULL(tty->write_q)) {//要取的还大于0,且写队列不满时
c=get_fs_byte(b);//从缓冲区取一个字符
if (O_POST(tty)) {//l输出模式,执行输出处理标志POST 置位
if (c=='\r' && O_CRNL(tty))//c=回车符 回车符转换行符标志置位
c='\n';
else if (c=='\n' && O_NLRET(tty))////c=换行符 换行符转回车符标志置位
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);//放入写队列中
}
tty->write(tty);//调用对应的TTY写函数,或者把写队列中的数据显示在控制台屏幕上(con_write),或者通过串行端口发出去(rs_write)
if (nr>0)//如果还有字符要写(此时写队列已满),则等待写队列中的字符取走,所以这里调用schedule()函数,先去执行其他任务
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.
*/
void do_tty_interrupt(int tty)//tty中断处理调用函数
{
copy_to_cooked(tty_table+tty);
}
void chr_dev_init(void)//字符设备初始化函数
{
}
|