
Linux下tty串口驱动数据的发送、接收过程源码实例详解
简介本文主要时讲解tty驱动文件的打开、数据的发送、数据的接收相关的源码解析,从用户层到硬件层的源码追踪过程。具体的读写操作可先大致看一下流程图,下面的源码分析也依然是围绕该流程进行函数追踪。一、tty数据接收流程分析:对于tty设备的打开操作,即用户调用read函数来读取设备的文件的数据,首先经过vfs层、字符设备驱动层,到达tty_open()函数,经过tty_core层、serial_cor
一、简介
本文主要时讲解tty驱动文件的打开、数据的发送、数据的接收相关的源码解析,从用户层到硬件层的源码追踪过程。
具体的读写操作可先大致看一下流程图,下面的源码分析也依然是围绕该流程进行函数追踪。
二、tty数据接收流程分析
对于tty设备的打开操作,即用户调用read函数来读取设备的文件的数据,首先经过vfs层、字符设备驱动层,到达tty_open()函数,经过tty_core层、serial_core等层后,主要完成一下工作:
1、首先通过dev_t,再tty_driver链表中查找对应的驱动,并返回本设备在该driver中的index即序号;
2、初始化tty_struct,建立tty_struct和tty_driver之间的关联,经tty_struct->ldisc、tty_struct->port进行赋值,最后调用tty_struct->ld->ops->open()设置通讯参数:速率speed、校验位flag等等;
3、初始化tty_file_private,并将priv添加到tty_struct->tty_files;
4、调用tty_struct.ops.open()即n_tty_ops.open=n_tty_open完成设备的打开工作,此时已在tty线路规程,最终调用uart_startup();
打开文件流程图如下:
2.1 详细代码解析
从流程图中分析,再tty_read()中,代码先通过tty_struct()得到调用线路规程中的read操作:ld->ops->read(),即n_tty_read(),具体代码如下:
/**
* n_tty_read - read function for tty
* @tty: tty device
* @file: file object
* @buf: userspace buffer pointer
* @nr: size of I/O
*
* Perform reads for the line discipline. We are guaranteed that the
* line discipline will not be closed under us but we may get multiple
* parallel readers and must handle this ourselves. We may also get
* a hangup. Always called in user context, may sleep.
*
* This code must be sure never to sleep through a hangup.
*/
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
int minimum, time;
ssize_t retval = 0;
ssize_t size;
long timeout;
unsigned long flags;
int packet;
do_it_again:
BUG_ON(!tty->read_buf);
c = job_control(tty, file);
if (c < 0)
return c;
minimum = time = 0;
timeout = MAX_SCHEDULE_TIMEOUT;
if (!tty->icanon) { //默认为icanon则该if不会执行
time = (HZ / 10) * TIME_CHAR(tty);
minimum = MIN_CHAR(tty);
if (minimum) {
if (time)
tty->minimum_to_wake = 1;
else if (!waitqueue_active(&tty->read_wait) ||
(tty->minimum_to_wake > minimum))
tty->minimum_to_wake = minimum;
} else {
timeout = 0;
if (time) {
timeout = time;
time = 0;
}
tty->minimum_to_wake = minimum = 1;
}
}
/*
* Internal serialization of reads.
*/
if (file->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&tty->atomic_read_lock))
return -EAGAIN;
} else {
if (mutex_lock_interruptible(&tty->atomic_read_lock))
return -ERESTARTSYS;
}
packet = tty->packet; //=0,未初始化该成员
//等待队列的操作
add_wait_queue(&tty->read_wait, &wait);
while (nr) {
/* First test for status change. */
if (packet && tty->link->ctrl_status) {//packet为0,if不满足
unsigned char cs;
if (b != buf)
break;
spin_lock_irqsave(&tty->link->ctrl_lock, flags);
cs = tty->link->ctrl_status;
tty->link->ctrl_status = 0;
spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
if (tty_put_user(tty, cs, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
break;
}
/* This statement must be first before checking for input
so that any interrupt will set the state back to
TASK_RUNNING. */
set_current_state(TASK_INTERRUPTIBLE);
if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf));
//此if保证用户空间的读进程一直要等待数据可读,否则一直睡眠
if (!input_available_p(tty, 0)) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags &O_NONBLOCK) {//无数据可读、且非阻塞打开,代表设备正“忙”,则直接返回退出
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {//本线程是因为信号而被唤醒,不是由于数据可读,也要直接返回退出
retval = -ERESTARTSYS;
break;
}
/* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
continue;
}
__set_current_state(TASK_RUNNING);
/* Deal with packet mode. */
if (packet && b == buf) {
if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (tty->icanon) {
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol;
eol = test_and_clear_bit(tty->read_tail,
tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &
(N_TTY_BUF_SIZE-1));
tty->read_cnt--;
if (eol) {
/* this test should be redundant:
* we shouldn't be reading data if
* canon_data is 0
*/
if (--tty->canon_data < 0)
tty->canon_data = 0;
}
spin_unlock_irqrestore(&tty->read_lock, flags);
if (!eol || (c != __DISABLED_CHAR)) {
if (tty_put_user(tty, c, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
int uncopied;
/* The copy function takes the read lock and handles
locking internally for this case */
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);//投递数据到用户空间
if (uncopied) {
retval = -EFAULT;
break;
}
}
/* If there is enough space in the read buffer now, let the
* low-level driver know. We use n_tty_chars_in_buffer() to
* check the buffer, as it now knows about canonical mode.
* Otherwise, if the driver is throttled and the line is
* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
* we won't get any more characters.
*/
if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
n_tty_set_room(tty);
check_unthrottle(tty);
}
if (b - buf >= minimum)//由于minimum=0,因此只要有1个以上的数据就返回
break;
if (time)
timeout = time;
}
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait);//删除队列
if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum;
__set_current_state(TASK_RUNNING);
size = b - buf;
if (size) {
retval = size;
if (nr)
clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
goto do_it_again;
n_tty_set_room(tty);
return retval;
}
该函数就是典型的等待队列使用套路:先查询数据是否可用,不可用就让出cpu,等待其他线程(即终端ISR中间调用flush_to_ldisc())来唤醒。当硬件接收到数据后,在中断处理之后,会将数据投递都ldata.read_buf[]中,然后唤醒该线程,之后本线程就调用copy_from_read_buf()完成数据成ldisc层(ldata.read_buf[])至应用程序缓冲区的复制。
static int s3c24xx_serial_startup(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
int ret;
dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",
port->mapbase, port->membase);
rx_enabled(port) = 1; // 使能接收
ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,
s3c24xx_serial_portname(port), ourport); // 为数据接收注册中断程序
if (ret != 0) {
printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);
return ret;
}
ourport->rx_claimed = 1; // 使能发送
dbg("requesting tx irq...\n");
tx_enabled(port) = 1;
ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,
s3c24xx_serial_portname(port), ourport); // 为数据发送注册中断程序
if (ret) {
printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);
goto err;
}
ourport->tx_claimed = 1;
dbg("s3c24xx_serial_startup ok\n");
/* the port reset code should have done the correct
* register setup for the port controls */
if (port->line == 2) {
s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);
s3c2410_gpio_pullup(S3C2410_GPH(6), 1);
s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);
s3c2410_gpio_pullup(S3C2410_GPH(7), 1);
}
return ret;
err:
s3c24xx_serial_shutdown(port);
return ret;
}
上面函数主要完成下面的工作:
1、使能接收rx_enabled
2、为数据接收注册中断程序request_irq
3、使能发送tx_enabled
4、为数据发送注册中断程序request_irq
2.2 中断处理程序唤醒底层线程
串口接收数据是通过s3c24xx_serial_rx_chars在中断里面进行的
static irqreturn_t
s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
struct s3c24xx_uart_port *ourport = dev_id;
struct uart_port *port = &ourport->port;
struct tty_struct *tty = port->state->port.tty;
unsigned int ufcon, ch, flag, ufstat, uerstat;
int max_count = 64;
while (max_count-- > 0) {
ufcon = rd_regl(port, S3C2410_UFCON);
ufstat = rd_regl(port, S3C2410_UFSTAT);
if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
break;
uerstat = rd_regl(port, S3C2410_UERSTAT);
ch = rd_regb(port, S3C2410_URXH);
if (port->flags & UPF_CONS_FLOW) {
int txe = s3c24xx_serial_txempty_nofifo(port);
if (rx_enabled(port)) {
if (!txe) {
rx_enabled(port) = 0;
continue;
}
} else {
if (txe) {
ufcon |= S3C2410_UFCON_RESETRX;
wr_regl(port, S3C2410_UFCON, ufcon);
rx_enabled(port) = 1;
goto out;
}
continue;
}
}
/* insert the character into the buffer */
flag = TTY_NORMAL;
port->icount.rx++;
if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
ch, uerstat);
/* check for break */
if (uerstat & S3C2410_UERSTAT_BREAK) {
dbg("break!\n");
port->icount.brk++;
if (uart_handle_break(port))
goto ignore_char;
}
if (uerstat & S3C2410_UERSTAT_FRAME)
port->icount.frame++;
if (uerstat & S3C2410_UERSTAT_OVERRUN)
port->icount.overrun++;
uerstat &= port->read_status_mask;
if (uerstat & S3C2410_UERSTAT_BREAK)
flag = TTY_BREAK;
else if (uerstat & S3C2410_UERSTAT_PARITY)
flag = TTY_PARITY;
else if (uerstat & (S3C2410_UERSTAT_FRAME |
S3C2410_UERSTAT_OVERRUN))
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(port, ch))
goto ignore_char;
uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
ch, flag);
ignore_char:
continue;
}
tty_flip_buffer_push(tty);
out:
return IRQ_HANDLED;
}
该部分源码分析:
1、读取UFCON寄存器;
2、读取UFSTAT寄存器;
3、然后读取接收fifo的数据量s3c24xx_serial_rx_fifocnt(ourport, ufstat),如果数据量为0,则退出处理;
4、读取错误状态寄存器uerstat = rd_regl(port, S3C2410_UERSTAT);
5、然后取出接收到的字符ch = rd_regb(port, S3C2410_URXH);
6、if (port->flags & UPF_CONS_FLOW),这一段代码其实是在做流控的处理;
7、if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) ,这段代码判断错误发生的类型;
8、if (uart_handle_sysrq_char(port, ch))如果接收到的是sysrq这个特殊字符,则进行特殊处理;
9、uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,ch, flag);这一步把接收到的字符送到串口驱动的buf中,这里面大循环就结束了;
10、tty_flip_buffer_push(tty);把串口驱动中的数据送到read_buf中;
tty_flip_buffer_push 有两种方式调用到 flush_to_ldisc ,一种直接调用,另一种使用延时工作队列,在很久很久以前,我们初始化了这么一个工作队列~(tty_open 初始化 tty_struct 时前面有提到)
void tty_flip_buffer_push(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags);
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work.work);
else
schedule_delayed_work(&tty->buf.work, 1);
}
2.3 数据发送过程分析
写过程也可以分为两个线程,但与读过程又有区别。
1、对于传统的串口硬件驱动,发送中断平时都是关闭的,只有用户执行写操作后,才会主动打开TX中断,同时,TX-ISR里自动检测xmit状态,为空则主动关闭TX中断;
2、对于由用户发起的写进程:首先将数据从用户buf填充至uart_state->xmit.buf[];
3、如果用户buf数据过大(>page_size),则wait_woken()该线程,与此同时,只要xmit已满或者用户buf被填充完毕,就开始flush数据至TX-FIFO;
4、对于TX-ISR线程:每发出一个bite,就检查xmit数据状态,当xmit为空时,关闭TX中断,当xmit不够时,唤醒上面的写进场,继续往xmit填充数据,具体流程如下:
具体函数实现如下:
首先时tty_core层的调用
static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct tty_struct *tty;
struct inode *inode = file->f_path.dentry->d_inode;
ssize_t ret;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write)
ret = -EIO;
else
/* 调用 线路规程 n_tty_write 函数 */
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
然后调用线路规程的n_tty_write:
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
// 将当前进程添加到等待队列
add_wait_queue(&tty->write_wait, &wait);
while (1) {
// 设置当前进程为可中断的
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
/* 自行定义了输出方式 */
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
....
} else {
while (nr > 0) {
/* 调用到 uart_write */
c = tty->ops->write(tty, b, nr);
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
// 进程调度 开始休眠
schedule();
}
}
最终调用uart_write函数:
static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
uart_start(tty);
return ret;
}
static void uart_start(struct tty_struct *tty)
{
__uart_start(tty);
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
/* 调用到最底层的 start_tx */
port->ops->start_tx(port);
}
uart_write 又调用到了最底层的 uart_port->ops->start_tx 函数
三、其他相关链接
Linux系统TTY串口驱动实例详解
更多推荐
所有评论(0)