kernel源码(十九)字符设备-serial.c
该源文件主要处理串行数据的发送和接收。
0 显卡和CGA
参考:https://blog.csdn.net/cy295957410/article/details/108436730
CGA(Color Graphics Adapter)彩色图形接口。是CGA显卡,提供基本的彩色文本显示
EGA(Enhance Graphics Adapter)增强图形接口,比CGA显示性能要高,支持彩色文本和图形
VGA(Video Graphics Array)视频图形阵列,三者中显示性能最高的一种显卡。
当然目前有性能更优的显卡,这里不做介绍了
对于我们接下来要学习的linux0.11,我们只了解CGA就足够了,源码同样适用于EGA和VGA。
在console中要显示文字,需要显示器和显卡两种设备。显卡用于提供显示内容,显示器负责将显卡中的内容显示出来。
在https://www.cnblogs.com/zhenjingcool/p/15938330.html这片博文中我们介绍过,低1M内存空间有一部分内存被外设占用。其中0xA0000-0xBFFFF被图形视频缓冲区占用。如下图
其中0xB8000-0xBFFFF这32kb空间被CGA使用。也就是说,这个内存区域就是显卡的显存(这里IO设备显卡和内存统一编址),我们把要显示的数据写到这里,显示器负责显示这里的内容。
当显卡初始化的时候,会自动初始化80*25模式(屏幕有25行,每一行80个字符),一个屏幕共可显示2000个字符,一个字符占用2字节,共需要4000字节
从0xB8000开始,每两个字节表示一个字符,从屏幕上第一行开始对应,一行一行的对应下去。
而这两个字节前一个字节是字符的ASCII码,后一个字节控制这个字符的颜色和属性的控制信息,各个位的含义如下:
显卡的显存是和主存统一编址,但是显卡的控制信息是独立编址的(独立编址:每种IO设备都有自己的存储空间,往往控制信息是写在IO设备自己的存储空间中的)。但是显卡自己的存储空间(显卡的控制寄存器)有几百个。因此我们使用0x3D4作为显卡内部寄存器的索引,再通过0x3D5端口设置相应寄存器的值。具体做法是:先向0x3D4端口写入要访问的显卡上的寄存器编号,再通过0x3D5端口来读写寄存器数据。
CGA使用的是MC6845芯片,其端口寄存器如下
0 UART异步串行通信控制器
PC机上一般有两个符合rs232c标准的串行口,使用UART芯片来进行串行数据的收发工作。UART作用是把数据按照串行方式发送或接收。
传输一个字符的时候由[起始位+数据位+奇偶校验位+停止位]组成
下图中第一列为串口1(括号中是串口2)
1 源码
/* * 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 <linux/tty.h> #include <linux/sched.h> #include <asm/system.h> #include <asm/io.h> #define WAKEUP_CHARS (TTY_BUF_SIZE/4) extern void rs1_interrupt(void); extern void rs2_interrupt(void); static void init(int port) { 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 (?) */ } void rs_init(void) { set_intr_gate(0x24,rs1_interrupt); set_intr_gate(0x23,rs2_interrupt); init(tty_table[1].read_q.data); init(tty_table[2].read_q.data); 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); */ void rs_write(struct tty_struct * tty) { cli(); if (!EMPTY(tty->write_q)) outb(inb_p(tty->write_q.data+1)|0x02,tty->write_q.data+1); sti(); }
#define WAKEUP_CHARS (TTY_BUF_SIZE/4) //一个宏定义,表示达到TTY_BUF_SIZE/4时就开始传送 extern void rs1_interrupt(void); //串口1和串口2的中断处理程序 extern void rs2_interrupt(void);
初始化串行的端口,从上面介绍的UART,我们以串口1为例,port=0x3f8
static void init(int port) { outb_p(0x80,port+3); //port+3=0x3fb,为写线路控制寄存器,写入的值为1000 0000,即设置DLAB=1/* set DLAB of line control reg */ outb_p(0x30,port); //指定串行端口传输的波特率是2400/* LS of divisor (48 -> 2400 bps */ outb_p(0x00,port+1); //波特率因子的高字节 /* MS of divisor */ outb_p(0x03,port+3); //DLAB复位,且指定数据位长度11表示8位数据位 /* 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 (?) */ }
初始化串行中断程序
void rs_init(void) { set_intr_gate(0x24,rs1_interrupt); //设置串口1的中断向量是int 0x24,中断处理程序是rs1_interrupt set_intr_gate(0x23,rs2_interrupt); //设置串口2的中断向量是int 0x23,中断处理程序是rs2_interrupt init(tty_table[1].read_q.data);//init是上面定义的函数,分别初始化串口1和串口2 init(tty_table[2].read_q.data); outb(inb_p(0x21)&0xE7,0x21); //允许主8259A响应中断请求 }
在tty_write把数据放到write_queue中时,会调用该函数实现串行数据的发送输出
/* * 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); */ void rs_write(struct tty_struct * tty) { cli(); if (!EMPTY(tty->write_q)) outb(inb_p(tty->write_q.data+1)|0x02,tty->write_q.data+1); sti(); }