程序10-4 linux/kernel/chr_drv/rs_io.s
1 /*
2 * linux/kernel/rs_io.s
3 *
4 * (C) 1991 Linus Torvalds
5 */
6
7 /*
8 * rs_io.s
9 *
10 * This module implements the rs232 io interrupts.
11 */
/*
* 该模块实现rs232输入输出中断处理程序。
*/
12
13 .text
14 .globl _rs1_interrupt,_rs2_interrupt
15
// size是读写队列缓冲区的字节长度。该值必须是2的次方,并且必须与tty_io.c中的匹配。
16 size = 1024 /* must be power of two !
17 and must match the value
18 in tty_io.c!!! */
19
20 /* these are the offsets into the read/write buffer structures */
/* 以下这些是读写缓冲队列结构中的偏移量 */
// 对应 include/linux/tty.h 文件中 tty_queue 结构中各字段的字节偏移量。其中rs_addr
// 对应tty_queue结构的data字段。对于串行终端缓冲队列,该字段存放着串行端口基地址。
21 rs_addr = 0 // 串行端口号字段偏移(端口是0x3f8或0x2f8)。
22 head = 4 // 缓冲区中头指针字段偏移。
23 tail = 8 // 缓冲区中尾指针字段偏移。
24 proc_list = 12 // 等待该缓冲的进程字段偏移。
25 buf = 16 // 缓冲区字段偏移。
26
// 当一个写缓冲队列满后,内核就会把要往写队列填字符的进程设置为等待状态。当写缓冲队列
// 中还剩余最多256个字符时,中断处理程序就可以唤醒这些等待进程继续往写队列中放字符。
27 startup = 256 /* chars left in write queue when we restart it */
/* 当我们重新开始写时,队列里最多还剩余字符个数。*/
28
29 /*
30 * These are the actual interrupt routines. They look where
31 * the interrupt is coming from, and take appropriate action.
32 */
/*
* 这些是实际的中断处理程序。程序首先检查中断的来源,然后执行
* 相应的处理。
*/
//// 串行端口1中断处理程序入口点。
// 初始化时rs1_interrupt地址被放入中断描述符0x24中,对应8259A的中断请求IRQ4引脚。
// 这里首先把tty表中串行终端1(串口1)读写缓冲队列指针的地址入栈(tty_io.c,81),
// 然后跳转到rs_int继续处理。这样做可以让串口1和串口2的处理代码公用。字符缓冲队列
// 结构tty_queue格式请参见include/linux/tty.h,第22行。
33 .align 2
34 _rs1_interrupt:
35 pushl $_table_list+8 // tty表中串口1读写缓冲队列指针地址入栈。
36 jmp rs_int
37 .align 2
//// 串行端口2中断处理程序入口点。
38 _rs2_interrupt:
39 pushl $_table_list+16 // tty表中串口2读写缓冲队列指针地址入栈。
// 这段代码首先让段寄存器ds、es指向内核数据段,然后从对应读写缓冲队列data字段取出
// 串行端口基地址。该地址加2即是中断标识寄存器IIR的端口地址。若位0 = 0,表示有需
// 要处理的中断。于是根据位2、位1使用指针跳转表调用相应中断源类型处理子程序。在每
// 个子程序中会在处理完后复位UART 的相应中断源。在子程序返回后这段代码会循环判断是
// 否还有其他中断源(位0 = 0?)。如果本次中断还有其他中断源,则IIR的位0仍然是0。
// 于是中断处理程序会再调用相应中断源子程序继续处理。直到引起本次中断的所有中断源都
// 被处理并复位,此时UART会自动地设置IIR的位0 =1,表示已无待处理的中断,于是中断
// 处理程序即可退出。
40 rs_int:
41 pushl %edx
42 pushl %ecx
43 pushl %ebx
44 pushl %eax
45 push %es
46 push %ds /* as this is an interrupt, we cannot */
47 pushl $0x10 /* know that bs is ok. Load it */
48 pop %ds /* 由于这是一个中断程序,我们不知道ds是否正确,*/
49 pushl $0x10 /* 所以加载它们(让ds、es指向内核数据段) */
50 pop %es
51 movl 24(%esp),%edx // 取上面35或39行入栈的相应串口缓冲队列指针地址。
52 movl (%edx),%edx // 取读缓冲队列结构指针(地址)èedx。
53 movl rs_addr(%edx),%edx // 取串口1(或串口2)端口基地址èedx。
54 addl $2,%edx /* interrupt ident. reg */ /* 指向中断标识寄存器 */
// 中断标识寄存器端口地址是0x3fa(0x2fa)。
55 rep_int:
56 xorl %eax,%eax
57 inb %dx,%al // 取中断标识字节,以判断中断来源(有4种中断情况)。
58 testb $1,%al // 首先判断有无待处理中断(位0 = 0有中断)。
59 jne end // 若无待处理中断,则跳转至退出处理处end。
60 cmpb $6,%al /* this shouldn't happen, but ... */ /*这不会发生,但…*/
61 ja end // al值大于6,则跳转至end(没有这种状态)。
62 movl 24(%esp),%ecx // 调用子程序之前把缓冲队列指针地址放入ecx。
63 pushl %edx // 临时保存中断标识寄存器端口地址。
64 subl $2,%edx // edx中恢复串口基地址值0x3f8(0x2f8)。
65 call jmp_table(,%eax,2) /* NOTE! not *4, bit0 is 0 already */
// 上面语句是指,当有待处理中断时,al中位0=0,位2、位1是中断类型,因此相当于已经将
// 中断类型乘了2,这里再乘2,获得跳转表(第79行)对应各中断类型地址,并跳转到那里去
// 作相应处理。中断来源有4种:modem状态发生变化;要写(发送)字符;要读(接收)字符;
// 线路状态发生变化。允许发送字符中断通过设置发送保持寄存器标志实现。在serial.c程序
// 中,当写缓冲队列中有数据时,rs_write()函数就会修改中断允许寄存器内容,添加上发送保
// 持寄存器中断允许标志,从而在系统需要发送字符时引起串行中断发生。
66 popl %edx // 恢复中断标识寄存器端口地址0x3fa(或0x2fa)。
67 jmp rep_int // 跳转,继续判断有无待处理中断并作相应处理。
68 end: movb $0x20,%al // 中断退出处理。向中断控制器发送结束中断指令EOI。
69 outb %al,$0x20 /* EOI */
70 pop %ds
71 pop %es
72 popl %eax
73 popl %ebx
74 popl %ecx
75 popl %edx
76 addl $4,%esp # jump over _table_list entry # 丢弃队列指针地址。
77 iret
78
// 各中断类型处理子程序地址跳转表,共有4种中断来源:
// modem状态变化中断,写字符中断,读字符中断,线路状态有问题中断。
79 jmp_table:
80 .long modem_status,write_char,read_char,line_status
81
// 由于modem状态发生变化而引发此次中断。通过读modem状态寄存器MSR对其进行复位操作。
82 .align 2
83 modem_status:
84 addl $6,%edx /* clear intr by reading modem status reg */
85 inb %dx,%al /* 通过读modem状态寄存器进行复位(0x3fe) */
86 ret
87
// 由于线路状态发生变化而引起这次串行中断。通过读线路状态寄存器LSR对其进行复位操作。
88 .align 2
89 line_status:
90 addl $5,%edx /* clear intr by reading line status reg. */
91 inb %dx,%al /* 通过读线路状态寄存器进行复位(0x3fd) */
92 ret
93
// 由于UART芯片接收到字符而引起这次中断。对接收缓冲寄存器执行读操作可复位该中断源。
// 这个子程序将接收到的字符放到读缓冲队列read_q头指针(head)处,并且让该指针前移一
// 个字符位置。若head指针已经到达缓冲区末端,则让其折返到缓冲区开始处。最后调用C函
// 数do_tty_interrupt()(也即copy_to_cooked()),把读入的字符经过处理放入规范模式缓
// 冲队列(辅助缓冲队列secondary)中。
94 .align 2
95 read_char:
96 inb %dx,%al // 读取接收缓冲寄存器RBR中字符èal。
97 movl %ecx,%edx // 当前串口缓冲队列指针地址èedx。
98 subl $_table_list,%edx // 当前串口队列指针地址 - 缓冲队列指针表首址 èedx,
99 shrl $3,%edx // 差值/8,得串口号。对于串口1是1,对于串口2是2。
100 movl (%ecx),%ecx # read-queue // 取读缓冲队列结构地址èecx。
101 movl head(%ecx),%ebx // 取读队列中缓冲头指针èebx。
102 movb %al,buf(%ecx,%ebx) // 将字符放在缓冲区中头指针所指位置处。
103 incl %ebx // 将头指针前移(右移)一字节。
104 andl $size-1,%ebx // 用缓冲区长度对头指针取模操作。
105 cmpl tail(%ecx),%ebx // 缓冲区头指针与尾指针比较。
106 je 1f // 若指针移动后相等,表示缓冲区满,不保存头指针,跳转。
107 movl %ebx,head(%ecx) // 保存修改过的头指针。
108 1: addl $63,%edx // 串口号转换成tty号(63或64)并作为参数入栈。
109 pushl %edx
110 call _do_tty_interrupt // 调用tty中断处理C函数(tty_io.c,342行)。
111 addl $4,%esp // 丢弃入栈参数,并返回。
112 ret
113
// 由于设置了发送保持寄存器允许中断标志而引起此次中断。说明对应串行终端的写字符缓冲队
// 列中有字符需要发送。于是计算出写队列中当前所含字符数,若字符数已小于256个,则唤醒
// 等待写操作进程。然后从写缓冲队列尾部取出一个字符发送,并调整和保存尾指针。如果写缓
// 冲队列已空,则跳转到write_buffer_empty处处理写缓冲队列空的情况。
114 .align 2
115 write_char:
116 movl 4(%ecx),%ecx # write-queue // 取写缓冲队列结构地址èecx。
117 movl head(%ecx),%ebx // 取写队列头指针èebx。
118 subl tail(%ecx),%ebx // 头指针 - 尾指针 = 队列中字符数。
119 andl $size-1,%ebx # nr chars in queue
120 je write_buffer_empty // 若头指针 = 尾指针,说明写队列空,跳转处理。
121 cmpl $startup,%ebx // 队列中字符数还超过256个?
122 ja 1f // 超过则跳转处理。
123 movl proc_list(%ecx),%ebx # wake up sleeping process # 唤醒等待的进程。
// 取等待该队列的进程指针,并判断是否为空。
124 testl %ebx,%ebx # is there any? # 有等待写的进程吗?
125 je 1f // 是空的,则向前跳转到标号1处。
126 movl $0,(%ebx) // 否则将进程置为可运行状态(唤醒进程)。
127 1: movl tail(%ecx),%ebx // 取尾指针。
128 movb buf(%ecx,%ebx),%al // 从缓冲中尾指针处取一字符èal。
129 outb %al,%dx // 向端口0x3f8(0x2f8)写到发送保持寄存器中。
130 incl %ebx // 尾指针前移。
131 andl $size-1,%ebx // 尾指针若到缓冲区末端,则折回。
132 movl %ebx,tail(%ecx) // 保存已修改过的尾指针。
133 cmpl head(%ecx),%ebx // 尾指针与头指针比较,
134 je write_buffer_empty // 若相等,表示队列已空,则跳转。
135 ret
// 处理写缓冲队列write_q已空的情况。若有等待写该串行终端的进程则唤醒之,然后屏蔽发
// 送保持寄存器空中断,不让发送保持寄存器空时产生中断。
// 如果此时写缓冲队列write_q已空,表示当前无字符需要发送。于是我们应该做两件事情。
// 首先看看有没有进程正等待写队列空出来,如果有就唤醒之。另外,因为现在系统已无字符
// 需要发送,所以此时我们要暂时禁止发送保持寄存器THR空时产生中断。当再有字符被放入
// 写缓冲队列中时,serial.c中的rs_write()函数会再次允许发送保持寄存器空时产生中断,
// 因此UART就又会“自动”地来取写缓冲队列中的字符,并发送出去。
136 .align 2
137 write_buffer_empty:
138 movl proc_list(%ecx),%ebx # wake up sleeping process # 唤醒等待的进程。
// 取等待该队列的进程的指针,并判断是否为空。
139 testl %ebx,%ebx # is there any? # 有等待的进程吗?
140 je 1f // 无,则向前跳转到标号1处。
141 movl $0,(%ebx) // 否则将进程置为可运行状态(唤醒进程)。
142 1: incl %edx // 指向端口0x3f9(0x2f9)。
143 inb %dx,%al // 读取中断允许寄存器IER。
144 jmp 1f // 稍作延迟。
145 1: jmp 1f /* 屏蔽发送保持寄存器空中断(位1) */
146 1: andb $0xd,%al /* disable transmit interrupt */
147 outb %al,%dx // 写入0x3f9(0x2f9)。
148 ret