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
View Code

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

 

posted @ 2022-04-05 18:36  zhenjingcool  阅读(35)  评论(0编辑  收藏  举报