终端控制台体系
串口之前要知道终端控制台,在Linux中,tty(终端)是一类字符设备的统称,包括3种类型: 控制台、串口和伪终端。
控制台:内核信息打印到的地方,printk打印的地方,控制台可以指定,通过console=ttys0指定串口0终端为控制台,是虚拟的要绑定真实的设备,比如串口、显示器。
伪终端:成对出现的,通常用来做远程通信,类似管道。
终端体系:包含:tty core,tty线路规程(tty line discipline),tty驱动。tty core:发送数据时从用户拿到数据交给线路规程,线路规程处理完后把数据还给tty core,tty core再交给tty driver,driver同硬件通信把数据发走,从硬件接收到的数据向上通过tty 驱动,进入线路规程,再进入tty core,最后被用户获取。
当通过串口发送数据时,操作的设备文件是/dev/ttys0,往设备文件中写入数据,数据沿着红线往下走首先进入tty_io.c(代表tty core),tty core 把数据交给线路规程(n_tty.c),他把数据按照某些规定协议(PPP点对点,蓝牙。。。。。)处理数据,处理完后把数据较还给tty_io.c(tty core) ,tty core 再交给tty driver (serial.c),通过硬件发送走。
字符设备数据的read()是系统调用一直往下达到设备的驱动程序里面,然后把数据返回给用户空间,tty设备是不管有无read()的需求,只要受到数据就把数据往上传,传到tty_flip_buffer类型结构把数据存起来,当用户需要数据,仅仅从receive_buf()里取。
Linux内核使用uart_driver描述串口驱动,串口驱动属于字符设备。
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
struct uart_state *state;
struct tty_driver *tty_driver;
};
int uart_register_driver (struct uart_driver *drv) 注册串口驱动
uart_port用于描述一个串口。
struct uart_port {
spinlock_t lock ;//端口锁
unsigned int iobase ; //I/O端口基地址
unsigned char _iomem *membase ; //IO内存基地址
unsigned int irq ; //中断号
unsigned char fifosize ; //传输fifo大小
const struct uart_ops *ops ; //串口操作
......................................................
}
实现串口驱动关键就是实现下面的函数指针:
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
..........................................................
};
int uart_add_one_port (struct uart_driver *drv, struct uart_port *port )添加一个串口,通过第一个参数drv同驱动建立起联系。
串口驱动设计:1:定义uart_driver,并初始化。2:使用uart_register注册驱动。3:初始化uart_port和他附带的ops函数表。4:调用uart_add_one_port把初始化的端口添加到系统。
数据的发送:从循环buffer取数据------>(驱动完成)放到发送fifo----->(硬件)发送移位寄存器
接收:接收移位寄存器------>(硬件)接收fifo---------->(驱动)filp_buf
uart line control register串口数据的格式 (停止位、奇偶校验位、数据长度)
uart control register:DMA,中断触发等等。
uart fifo control register:2440数据的收和发都有16个字节的fifo,什么时候产生中断在这里设置,Rx FIFO Trigger Level(接收中断) : 指定收到4、8、16.....个字节产生接收中断。
Tx FIFO Trigger Level(发送中断):设置发送FIFO低于16、12、8......个字节产生发送中断。
先产生中断告诉你可以再发送一些数据,然后再发送。
UART TX/RX Status Register :判断有无数据等待发送
函数介绍
模块初始化函数:
static int __init s3c2410uart_init(void)
{
return uart_register_driver(&s3c2410_reg);
}
使用uart_register_driver注册串口驱动。
static struct uart_driver s3c2410_reg = {
owner: THIS_MODULE,
normal_major: SERIAL_S3C2410_MAJOR,
normal_name: "ttyS%d",
callout_name: "cua%d",
normal_driver: &normal,
callout_major: CALLOUT_S3C2410_MAJOR,
callout_driver: &callout,
table: s3c2410_table,
termios: s3c2410_termios,
termios_locked: s3c2410_termios_locked,
minor: MINOR_START,
nr: UART_NR,
port: s3c2410_ports, //驱动管理的端口
cons: S3C2410_CONSOLE,
};
static struct uart_port s3c2410_ports[UART_NR] = {
{
iobase: (unsigned long)(UART0_CTL_BASE),
iotype: SERIAL_IO_PORT,
irq: IRQ_RXD0,
uartclk: 130252800,
fifosize: 16,
ops: &s3c2410_pops, //端口操作函数集
type: PORT_S3C2410,
flags: ASYNC_BOOT_AUTOCONF,
},
。。。。。。 。。。。。。。 。。。。。。。
};
下面是驱动程序的重点
static struct uart_ops s3c2410_pops = {
tx_empty: s3c2410uart_tx_empty,
set_mctrl: s3c2410uart_set_mctrl,
get_mctrl: s3c2410uart_get_mctrl,
stop_tx: s3c2410uart_stop_tx,
start_tx: s3c2410uart_start_tx,
stop_rx: s3c2410uart_stop_rx,
enable_ms: s3c2410uart_enable_ms,
break_ctl: s3c2410uart_break_ctl,
startup: s3c2410uart_startup,
shutdown: s3c2410uart_shutdown,
change_speed: s3c2410uart_change_speed,
type: s3c2410uart_type,
config_port: s3c2410uart_config_port,
release_port: s3c2410uart_release_port,
request_port: s3c2410uart_request_port,
};
停止发送函数 uart_stop_tx
statuc void s3c2410uart_stop_tx (struct uart_port *port , u_int from_tty) {
disable_irq(TX_IRQ(port)) ; //关闭发送中断,数据的发送依靠发送中断
}
发送使能函数uart_stat_tx (struct uart_port *port , u_int noempty, u_int (from_tty)) {
enable_irq(TX_IRQ(port)) ;
}
阻止接收中断uart_stop_tx
statuc void uart_stop_tx (struct uart_port *port) {
disable_irq(RX_IRQ(port)) ;
}
发送缓冲fifo空判断函数uart_tx_empty
static u_int s3c2410uart_tx_empty (struct uart_port *port ) {
return (UART_UTRSTAT(uart_port) & UTRSTAT_TR_EMP ? 0:
TIOCSER_TEMT) ; //读发送fifo的状态判空
}
接收是收到的数据达到触发水平以后,产生接收中断,接收是把数据送进flip_buffer。
static void s3c2410uart_rx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct uart_info *info = dev_id;
struct tty_struct *tty = info->tty;
unsigned int status, ch, max_count = 256;
struct uart_port *port = info->port;
status = UART_UTRSTAT(port);
while ((status & UTRSTAT_RX_RDY) && max_count--){
if (tty->flip.count >= TTY_FLIPBUF_SIZE) { /把数据送进flip_buf之前看flip_buf有无数据空间,tty->flip.count :已经存放的数据量,当没有空间时。
tty->flip.tqueue.routine((void *) tty); //数据满时,切换到另一个flip_buffer。
if (tty->flip.count >= TTY_FLIPBUF_SIZE) { //判断切换后的是否已满
printk(KERN_WARNING "TTY_DONT_FLIP set\n");
return; } }
ch = UART_URXH(port); //从串口取数据一个字节
*tty->flip.char_buf_ptr = ch; //保存进flip_buffer
*tty->flip.flag_buf_ptr = TTY_NORMAL;
port->icount.rx++;
tty->flip.flag_buf_ptr++;
tty->flip.char_buf_ptr++;
tty->flip.count++;
status = UART_UTRSTAT(port); //读状态,有无数据可读}
tty_flip_buffer_push(tty); //把flip_buffer数据放入tty_buffer供用户取走
return; } //一次中断尽可能处理多的字符
发送在中断产生以后进行的,有一个中断处理函数实现。
发送中断函数uart_tx_interrupt
tatic void s3c2410uart_tx_interrupt(int irq, void *dev_id,
struct pt_regs *reg) {
struct uart_info *info = dev_id;
struct uart_port *port = info->port;
int count;
if (port->x_char) { //判断发送字符是否为发送结束符
UART_UTXH(port) = port->x_char;
port->icount.tx++;
port->x_char = 0;
return;
}
if (info->xmit.head == info->xmit.tail
|| info->tty->stopped || info->tty->hw_stopped) { //没有数据发送或停止发送
s3c2410uart_stop_tx(info->port, 0);
return;
}
count = port->fifosize >> 1; //发送接收fifosize大小为16字节
do {
UART_UTXH(port) = info->xmit.buf[info->xmit.tail];
info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
if (info->xmit.head == info->xmit.tail)
break;
} while (--count > 0); //一次最多循环8个字符
if (CIRC_CNT(info->xmit.head, info->xmit.tail, //计算循环buffer里剩余的数据量
UART_XMIT_SIZE) < WAKEUP_CHARS)
uart_event(info, EVT_WRITE_WAKEUP); //小于时,告诉上层还可以写数据量
if (info->xmit.head == info->xmit.tail)
s3c2410uart_stop_tx(info->port, 0);
}
do {
UART_UTXH(port) = info->xmit.buf[info->xmit.tail];
//从循环buffer里取字符送入相应寄存器
info->xmit.tail = (info->xmit.tail + 1) & (UART_XMIT_SIZE - 1);
port->icount.tx++;
if (info->xmit.head == info->xmit.tail) //如果已经为空
break;
} while (--count > 0);
if (CIRC_CNT(info->xmit.head, info->xmit.tail,
UART_XMIT_SIZE) < WAKEUP_CHARS)
uart_event(info, EVT_WRITE_WAKEUP);
if (info->xmit.head == info->xmit.tail)
s3c2410uart_stop_tx(info->port, 0);
}