原创kylin_zeng:http://blog.csdn.net/kylin_fire_zeng 本文参考国嵌视频教程,再此感谢国嵌教育。
一、重点概述:
在Linux中,TTY(终端)是一类字符设备的统称,包括了3种类型:控制台,串口和伪终端。
1)控制台:供内核使用的终端为控制台。控制台在Linux启动时,通过命令
console=…指定,如果没有指定控制台,系统把第一个注册的终端(tty)作为控制台。 如printk打印到哪里哪里就是控制台,如lcd屏幕,终端软件的那个等
1. 控制台是一个虚拟的终端,它必须映射到真正的终端上。
2. 控制台可以简单的理解为printk输出的地方。
3. 控制台是个只输出的设备,功能很简单,只能在内核中访问。
2)伪终端:
伪终端设备是一种特殊的终端设备, 由主-从两个成对的设备构成, 当打开主设备时, 对应的从设备随之打开, 形
成连接状态。输入到主设备的数据成为从设备的输出, 输入到从设备的数据成为主设备的输出, 形成双向管
道。伪终端设备常用于远程登录服务器来建立网络和终端的关联。当通过telnet远程登录到另一台主机时,
telnet进程与远程主机的telnet服务器相连接. telnet服务器使用某个主设备并通过对应的从设备与telnet进程相互通信
3)终端体系:
图:
在Linux中,TTY体系分为:TTY核心,TTY线路规程,TTY驱动3部分。TTY核心从用户
获取要发送给TTY设备的数据,然后把数据传递给TTY线路规程, 它对数据进行处理后,
负责把数据传递到TTY驱动程序,TTY驱动程序负责格式化数据,并通过硬件发送出去。
从硬件收到的数据向上通过TTY驱动, 进入TTY线路规程, 再进入TTY核心, 最后被用户获取。TTY驱动
可以直接和TTY核心通讯, 但是通常TTY线路规程会修改在两者之间传送的数据。TTY驱动不能直接和
线路规程通信,甚至不知道它的存在,线路规程的工作是格式化从用户或者硬件收到的数据. 这种格
式化常常实现为一个协议, 如PPP或Bluetooth
形象图:
4)数据流:
如图:
读操作:TTY驱动从硬件收到数据后,负责把数据传递到TTY 核
心,TTY核心将从TTY驱动收到的数据缓存到一个tty_flip_buffer 类型的结构中。该结构包含两个数据数
组。从TTY设备接收到的数据被存储于第一个数组,当这个数组满, 等待数据的用户将被通知。当用户从这个
数组读数据时, 任何从TTY驱动新来的数据将被存储在第2个数组。当第二个数组存满后,数据再次提交给用
户, 并且驱动又开始填充第1个数组,以此交替
5)驱动描述:
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;
}
6)注册驱动:
Linux为串口驱动注册提供了如下接口:
int uart_register_driver(struct uart_driver *drv)
7)端口描述:
uart_port用于描述一个UART端口(一个串口)的地址、FIFO大
小、端口类型等信息
struct uart_port
{
spinlock_t lock; /* 端口锁*/
unsigned int iobase; /* IO端口基地址*/
unsigned char __iomem *membase; /* IO内存基地址*/
unsigned int irq; /* 中断号*/
unsigned char fifosize; /* 传输fifo大小*/
const structuart_ops *ops;
…………………………………………………………
}
8)操作串口:
uart_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); //发送xchar
void(*stop_rx)(struct uart_port*); //停止接收
.......
}
9)添加端口:
串口核心层提供如下函数来添加1个端口:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
10)串口驱动程序操作流程:
定义一个uart_driver的变量,并初始化;
2. 使用uart_register_driver来注册这个驱动;
3. 初始化uart_port和ops函数表;
4. 调用uart_add_one_port()添加初始化好的uart_port。
二、串口驱动程序分析
1)发送和接收:
发送: 循环buffer -(驱动做)-> 发送 fifi -(硬件自己做)-> 发送移位寄存器
把数据写到发送fifo中。fifo把收到的数据传给发送移位寄存器(自动的,非driver控制),然后每个时钟脉冲往串口线上写一个bit数据
所以是一直是中断告知还可以发送几个。当不足时一直引发中断。
Tx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如fifo 低于 到4个或8 16 个字节等时中断
接收: 接收移位寄存器 -(硬件自己做)-> 接收fifo -(驱动做)-> flip_buf
接收移位寄存器收到数据后,发送给接收fifo,接收fifo事先设置好触发门限,当里面的数据量超过门限时,就会触发一个中断,调用驱动里的中断处理函数,把数据写到flip_buf中。
Rx FIFO trigger level选择fifo触发水平,选择什么时候引发中断,如收到4个或8 16 个字节等时中断
2)模块初始化函数:
static int __int s3c2410uart_init(void)
{
return uart_register_driver(&s3c2410_reg);
}
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,
};
3)阻止发送函数uart_stop_tx
static void s3c2410uart_stop_tx(struct uart_port *port, u_int from_tty)
{
disable_irq(TX_IRQ(port)); //因为是中断产生说能再发送才能发送的,所以关闭中断就阻止了发送
}
4) 发送使能函数uart_start_tx
static void s3c2410uart_start_tx(struct uart_port *port, u_int nonempty, u_int from_tty)
{
enable_irq(TX_IRQ(port));
}
5)阻止接收函数uart_stop_rx
static void s3c2410uart_stop_rx(struct uart_port *port)
{
disable_irq(RX_IRQ(port));
}
6)发送缓冲空判断函数uart_tx_empty
static u_int s3c2410uart_tx_empty(struct uart_port *port)
{
return (UART_UTRSTAT(port) & UTRSTAT_TR_EMP 0 : TIOCSER_TEMT);
}
如果发送缓冲为空则返回0,否则返回1。
7) 获取控制信息函数uart_get_mctrl
static u_int s3c2410uart_get_mctrl(struct uart_port *port)
{
return (TIOCM_CTS | TIOCM_DSR | TIOCM_CAR);
}
获得控制信息, TIOCM_CTS ,TIOCM_DSR 和TIOCM_CAR,这几个宏代表串口的控制信息, 分别是clear to send,data set ready和data carrier detect(详见Serial Programming Guide for POSIX Operating Systems)
8)接收中断函数uart_rx_interrupt
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)
{
tty->flip.tqueue.routine((void *) tty);
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;
*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); //传送给上一层
return;
}
功能:主要是是while大循环,首先看循环判断条件status & UTRSTAT_RX_RDY,前面有status = UART_UTRSTAT(port),查2410的datasheet, status & UTRSTAT_RX_RDY这个位是判断接收buffer内是否还有有效数据 按道理一次中断只是把接收的fifobuffer中的数据放到flipbuffer中去,接收的fifo的中断门限是4-12字节,进行一次接收往往要中断好多次,这样中断开销比较大,所以在while的循环条件中判断一下是否还有接收的有效数据,如果有,就继续在中断程序中继续接收,当然,永远都在接收中断中(如果一直有数据要接收)也不合适,所以while循环还有计数,最多循环256次。
在循环中,首先是要判断一下接收数据用的flip-buffer是不是已经满了, if (tty->flip.count >= TTY_FLIPBUF_SIZE)如果满了,就要跳到另一个buffer上去, tty->flip.tqueue.routine((void *) tty)是用来实现跳到另一个buffer上的功能,然后把收到的数据写到flip-buffer中,相应的状态,统计数据都要改,接着再来while 循环,循环结束后就要调用
tty_flip_buffer_push(tty)来让用户把存在缓冲里的数据取走,接收一次都要把缓存清空。
9)发送中断函数uart_tx_interrupt
static 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;
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);
if (CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) < WAKEUP_CHARS)
uart_event(info, EVT_WRITE_WAKEUP); //CIRC_CNT是kernel自动计算环形队列还剩多少空间。需传入头,尾,长度
if (info->xmit.head == info->xmit.tail)
s3c2410uart_stop_tx(info->port, 0);
}
(1) 首先查看port中的x_char是不是为0,不为0则把x_char发送出去。x_char是xon/xoff的意思,每发一个字节时在开始前先发xon信号,在结束时发xoff。
(2) 如果x_char没有被设置,再看环形缓冲区是否为空,或者info->tty->stopped 和 info->tty->hw_stopped 两个位是不是为1,如果这些条件成立的话,就停止发送。Tty->stop指示tty设备是否停止,tty->hw_stop指示tty设备的硬件是否停止了,以上两个位都可以通过ttydriver来设定,否则的话说明有数据要发送。
(3) 如果以上条件都通过了,就利用一个while循环正式发送数据了,从环形缓冲尾巴上取一个数赋给UART_UTXH(port)(发送FIFO), UART_UTXH(port) = info->xmit.buf[info->xmit.tail],这条语句就是把数据送到发送FIFO中,然后计数++,循环一共进行fifosize/2次,也就是一次只能发送8 byte。
(4)循环传送完一次后,再查看缓冲器里还剩余多少数据,如果少于WAKEUP_CHARS(256)的话,就执行uart_event(info, 0),告诉TTY核心,可以接受更多数据了。这里可以看出,tty_driver和tty_core之间的层次,tty_driver可以知道缓冲空还是满,但是它没有权力让发送数据过来,它只能是通知tty_core,让它来处理。
(5) 最后再察看一下环形寄存器,如果serial core 没有发送来更多的数据,就关闭发送