kernel源码(二十)字符设备-rs_io.s
rs232串行通信处理的过程。它把串行线路上接收到的字符放入串行终端的读缓冲队列read_q当中,或者把写缓冲队列read_q发送到远端的穿行的终端设备
源码
/* * linux/kernel/rs_io.s * * (C) 1991 Linus Torvalds */ /* * rs_io.s * * This module implements the rs232 io interrupts. */ .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 jmp rs_int .align 2 _rs2_interrupt: pushl $_table_list+16 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 pushl $0x10 pop %es movl 24(%esp),%edx movl (%edx),%edx movl rs_addr(%edx),%edx addl $2,%edx /* interrupt ident. reg */ rep_int: xorl %eax,%eax inb %dx,%al testb $1,%al jne end cmpb $6,%al /* this shouldn't happen, but ... */ ja end movl 24(%esp),%ecx pushl %edx subl $2,%edx 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 */ pop %ds pop %es popl %eax popl %ebx popl %ecx popl %edx addl $4,%esp # jump over _table_list entry iret jmp_table: .long modem_status,write_char,read_char,line_status .align 2 modem_status: addl $6,%edx /* clear intr by reading modem status reg */ inb %dx,%al ret .align 2 line_status: addl $5,%edx /* clear intr by reading line status reg. */ inb %dx,%al ret .align 2 read_char: inb %dx,%al movl %ecx,%edx subl $_table_list,%edx shrl $3,%edx movl (%ecx),%ecx # read-queue movl head(%ecx),%ebx movb %al,buf(%ecx,%ebx) incl %ebx andl $size-1,%ebx cmpl tail(%ecx),%ebx je 1f movl %ebx,head(%ecx) 1: pushl %edx call _do_tty_interrupt addl $4,%esp ret .align 2 write_char: movl 4(%ecx),%ecx # write-queue movl head(%ecx),%ebx subl tail(%ecx),%ebx andl $size-1,%ebx # nr chars in queue je write_buffer_empty cmpl $startup,%ebx ja 1f movl proc_list(%ecx),%ebx # wake up sleeping process testl %ebx,%ebx # is there any? je 1f movl $0,(%ebx) 1: movl tail(%ecx),%ebx movb buf(%ecx,%ebx),%al outb %al,%dx incl %ebx andl $size-1,%ebx movl %ebx,tail(%ecx) cmpl head(%ecx),%ebx je write_buffer_empty ret .align 2 write_buffer_empty: movl proc_list(%ecx),%ebx # wake up sleeping process testl %ebx,%ebx # is there any? je 1f movl $0,(%ebx) 1: incl %edx inb %dx,%al jmp 1f 1: jmp 1f 1: andb $0xd,%al /* disable transmit interrupt */ outb %al,%dx ret
tty_queue当中各个字段的偏移值
/* these are the offsets into the read/write buffer structures */ rs_addr = 0 head = 4 tail = 8 proc_list = 12 buf = 16
写缓冲队列达到256时,就禁止再往里面写
startup = 256 /* chars left in write queue when we restart it */
串行端口1和串行端口2中断处理程序的入口点
.align 2 _rs1_interrupt: pushl $_table_list+8 //long类型,table_list+8表示串口1的读缓冲队列地址 jmp rs_int .align 2 _rs2_interrupt: pushl $_table_list+16 //串口2的读缓冲队列地址
其中table_list是在tty_io.c中定义的,有8个元素,分别是[console的读缓冲队列,console的写缓冲队列,串口1的读缓冲队列,串口1的写缓冲队列,串口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 };
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 //数据段选择符为0x10,即用户空间GDT表index=2,即GDT表的第二项 pushl $0x10 pop %es //附加段选择符0x10 movl 24(%esp),%edx //esp+24当中的内容赋给edx。其含义是堆栈指针向上移动24,即指向$table_list+8或者$table_list+16的地址,把这个地址指向edx。这个地址是缓冲队列的结构指针的地址 movl (%edx),%edx //取edx指向的内容赋给edx movl rs_addr(%edx),%edx //缓冲队列结构的基地址赋给edx addl $2,%edx //如果为串口1,则edx=0x3f8,加2为0x3fa,为读中断标志寄存器 /* interrupt ident. reg */
rep_int: xorl %eax,%eax inb %dx,%al //读取中断标识符的字节到al当中 testb $1,%al //判断有没有中断要处理,当al第0位=0时表示有中断(参考上一篇对UART的介绍) jne end //如果没有中断要处理,则跳转到end cmpb $6,%al //判断al中的内容是否大于6,参考上一篇博文提到的UART介绍,0x3fa最大是110 /* this shouldn't happen, but ... */ ja end //如果大于6,跳转 movl 24(%esp),%ecx //缓冲队列指针的地址放入ecx当中 pushl %edx //中断标识寄存器端口的地址,也就是0x3fa或者0x2fa,入栈 subl $2,%edx //再把edx改回来,改回0x3f8或者0x2f8 call jmp_table(,%eax,2) //eax值为[110,100,010,000]之一,乘以2后为[1100,1000,0100,0000]也就是[12,8,4,0] ,对应jmp_table的第3,2,1,0项,分别对应不同的处理函数 /* NOTE! not *4, bit0 is 0 already */ popl %edx //函数返回之后,弹出edx jmp rep_int //继续判断有没有要处理的中断,如果有继续处理,如果没有则跳转到end
jmp_table: .long modem_status,write_char,read_char,line_status
如果没有中断要处理,则执行end
end: movb $0x20,%al outb %al,$0x20 //给8259A发送EOI指令 /* EOI */ pop %ds //弹出寄存器 pop %es popl %eax popl %ebx popl %ecx popl %edx addl $4,%esp //esp+4,跃过_table_list # jump over _table_list entry iret
下面4个函数在jmp_table中提到过
.align 2 modem_status: //modem状态发生变化引起的中断 addl $6,%edx //edx+6就是0x3fe(见上一节UART相关介绍) /* clear intr by reading modem status reg */ inb %dx,%al //通过读他的内容把状态寄存器进行复位 ret .align 2 line_status: //线路的状态变化引起的中断 addl $5,%edx //edx+5就是0x3fd /* clear intr by reading line status reg. */ inb %dx,%al //通过读0x3fd的内容来恢复线路状态寄存器 ret .align 2 read_char: //读字符引发的中断,由于UART芯片接收到了字符引发的这个中断 inb %dx,%al //dx目前存放的是0x3f8处的值,把这个值放到al当中 movl %ecx,%edx //ecx存放的是缓冲队列指针的地址 subl $_table_list,%edx //当前缓冲队列指针的地址-缓冲队列指针表的首地址,放入edx中 shrl $3,%edx //差值除以8得到的是串口号,要么是1,要么是2 movl (%ecx),%ecx //缓冲队列的地址赋给ecx # read-queue movl head(%ecx),%ebx movb %al,buf(%ecx,%ebx) //把字符放到缓冲区头指针所指向的位置,buf是偏移量,ebx是指针的头 incl %ebx //ebx指针向前移动 andl $size-1,%ebx cmpl tail(%ecx),%ebx //看一下缓冲区长度是否超过缓冲区了 je 1f movl %ebx,head(%ecx) 1: pushl %edx call _do_tty_interrupt //调用do_tty_interrupt,tty中断处理 addl $4,%esp ret .align 2 write_char: movl 4(%ecx),%ecx //ecx+4指向的是写缓冲队列的地址 # write-queue movl head(%ecx),%ebx //取写队列的头指针放到ebx中 subl tail(%ecx),%ebx //头指针和尾指针进行相减,得到的是队列当中字符的数量 andl $size-1,%ebx //对指针进行比较,看头指针和尾指针是否相等 # nr chars in queue je write_buffer_empty //如果相等,说明队列已经空了 cmpl $startup,%ebx //$startup为256,前面定义的 ja 1f //如果队列超过256个,跳转 movl proc_list(%ecx),%ebx //等待队列任务头指针赋给ebx//唤醒等待队列 # wake up sleeping process testl %ebx,%ebx //测试ebx是否有进程 # is there any? je 1f //没有等待它的进程 movl $0,(%ebx) //(%ebx)为等待进程的基地址,基地址偏移0的位置是该进程的状态。这里把状态置0,即进程的状态处于可运行状态。 1: movl tail(%ecx),%ebx //缓冲区尾指针赋给ebx movb buf(%ecx,%ebx),%al //从缓冲区尾指针位置取一个字符,放到al中 outb %al,%dx //向0x3f8写字符,写到发送保持寄存器当中 incl %ebx //指针向前移动 andl $size-1,%ebx movl %ebx,tail(%ecx) cmpl head(%ecx),%ebx //尾指针和头指针比较 je write_buffer_empty //如果相等,说明队列已经空了,跳转 ret
write_buffer_empty: movl proc_list(%ecx),%ebx //要唤醒的缓冲队列任务的头指针存放到ebx中 # wake up sleeping process testl %ebx,%ebx //测试是否有等待队列 # is there any? je 1f //如果没有进程在等待,则跳转到1位置 movl $0,(%ebx) //否则进程的state赋值0,使进程处于可运行状态 1: incl %edx //edx+1,指向0x3f9 inb %dx,%al //读取中断允许寄存器到al中(见UART寄存器说明) jmp 1f //延迟 1: jmp 1f 1: andb $0xd,%al /* disable transmit interrupt */ outb %al,%dx //屏蔽发送保持寄存器的空中断 ret